mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-12 08:11:10 +02:00
7826 lines
235 KiB
C++
7826 lines
235 KiB
C++
|
|
#include "precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "prey_local.h"
|
|
|
|
#define DAMAGE_INDICATOR_TIME 1100 // Update this in hud_damageindicator.guifragment too
|
|
|
|
const idEventDef EV_PlayWeaponAnim( "playWeaponAnim", "sd" );
|
|
const idEventDef EV_RechargeHealth( "<rechargehealth>", NULL );
|
|
const idEventDef EV_RechargeRifleAmmo( "<rechargeRifleAmmo>", NULL );
|
|
const idEventDef EV_Cinematic( "cinematic", "dd" );
|
|
const idEventDef EV_DialogStart( "dialogStart", "ddd" );
|
|
const idEventDef EV_DialogStop( "dialogStop", NULL );
|
|
const idEventDef EV_LotaTunnelMode( "lotaTunnelMode", "d" );
|
|
const idEventDef EV_DrainSpiritPower( "<drainspiritpower>", NULL );
|
|
const idEventDef EV_SpawnDeathWraith( "<spawndeathwraith>", NULL );
|
|
const idEventDef EV_PrepareToResurrect( "prepareToResurrect" );
|
|
const idEventDef EV_ResurrectScreenFade( "<resurrectScreenFade>" );
|
|
const idEventDef EV_Resurrect( "resurrect" );
|
|
const idEventDef EV_PrepareForDeathWorld( "<prepareForDeathWorld>", NULL );
|
|
const idEventDef EV_EnterDeathWorld( "<enterDeathWorld>", NULL );
|
|
const idEventDef EV_AdjustSpiritPowerDeathWalk( "<adjustSpiritPowerDeathWalk>" );
|
|
const idEventDef EV_SetOverlayMaterial( "setOverlayMaterial", "sd" );
|
|
const idEventDef EV_SetOverlayTime( "setOverlayTime", "fd" );
|
|
const idEventDef EV_SetOverlayColor( "setOverlayColor", "ffff" );
|
|
const idEventDef EV_DDAHeartbeat( "<ddaHeartBeat>", NULL );
|
|
const idEventDef EV_ShouldRemainAlignedToAxial( "shouldRemainAlignedToAxial", "d" );
|
|
const idEventDef EV_StartHudTranslation( "<starthudtranslation>" );
|
|
const idEventDef EV_Unfreeze( "<unfreeze>" );
|
|
const idEventDef EV_GetSpiritPower( "getSpiritPower", "", 'f' ); //rww
|
|
const idEventDef EV_SetSpiritPower( "setSpiritPower", "f" ); //rww
|
|
const idEventDef EV_OnGround( "onGround", NULL, 'f' ); // bg
|
|
const idEventDef EV_BindUnfroze( "<bindUnfroze>", "e" ); // mdl
|
|
const idEventDef EV_LockWeapon( "lockWeapon", "d" ); // mdl
|
|
const idEventDef EV_UnlockWeapon( "unlockWeapon", "d" ); // mdl
|
|
const idEventDef EV_SetPrivateCameraView( "setPrivateCameraView", "Ed" ); //rdr
|
|
const idEventDef EV_SetCinematicFOV( "setCinematicFOV", "ffff" ); //rdr
|
|
const idEventDef EV_StopSpiritWalk( "stopSpiritWalk" ); //rww
|
|
const idEventDef EV_DamagePlayer( "damagePlayer", "eevsfd" ); //rww
|
|
const idEventDef EV_GetSpiritProxy( "getSpiritProxy", "", 'e' ); //jsh
|
|
const idEventDef EV_IsSpiritWalking( "isSpiritWalking", "", 'd' ); // pdm
|
|
const idEventDef EV_IsDeathWalking( "isDeathWalking", "", 'd' ); // bjk
|
|
const idEventDef EV_GetDDAValue( "getDDAValue", "", 'f' ); // cjr
|
|
const idEventDef EV_AllowLighter( "allowLighter", "d" ); // jsh
|
|
const idEventDef EV_DisableSpirit( "disableSpirit" ); // mdl
|
|
const idEventDef EV_EnableSpirit( "enableSpirit" ); // mdl
|
|
const idEventDef EV_UpdateDDA( "<updateDDA>" ); // cjr
|
|
const idEventDef EV_AllowDamage( "<allowDamage>" ); // mdl
|
|
const idEventDef EV_IgnoreDamage( "<ignoreDamage>" ); // mdl
|
|
const idEventDef EV_RespawnCleanup( "<respawncleanup>" ); //rww
|
|
const idEventDef EV_ReturnToWeapon( "returnToWeapon", "", 'd' ); //bjk
|
|
const idEventDef EV_CanAnimateTorso( "canAnimateTorso", "", 'd' ); //rww
|
|
|
|
CLASS_DECLARATION( idPlayer, hhPlayer )
|
|
EVENT( EV_PlayWeaponAnim, hhPlayer::Event_PlayWeaponAnim )
|
|
EVENT( EV_RechargeHealth, hhPlayer::Event_RechargeHealth )
|
|
EVENT( EV_RechargeRifleAmmo, hhPlayer::Event_RechargeRifleAmmo )
|
|
EVENT( EV_Cinematic, hhPlayer::Event_Cinematic )
|
|
EVENT( EV_DialogStart, hhPlayer::Event_DialogStart )
|
|
EVENT( EV_DialogStop, hhPlayer::Event_DialogStop )
|
|
EVENT( EV_LotaTunnelMode, hhPlayer::Event_LotaTunnelMode )
|
|
EVENT( EV_DrainSpiritPower, hhPlayer::Event_DrainSpiritPower )
|
|
EVENT( EV_SpawnDeathWraith, hhPlayer::Event_SpawnDeathWraith )
|
|
EVENT( EV_PrepareToResurrect, hhPlayer::Event_PrepareToResurrect )
|
|
EVENT( EV_ResurrectScreenFade, hhPlayer::Event_ResurrectScreenFade )
|
|
EVENT( EV_Resurrect, hhPlayer::Event_Resurrect )
|
|
EVENT( EV_EnterDeathWorld, hhPlayer::Event_EnterDeathWorld )
|
|
EVENT( EV_PrepareForDeathWorld, hhPlayer::Event_PrepareForDeathWorld )
|
|
EVENT( EV_AdjustSpiritPowerDeathWalk, hhPlayer::Event_AdjustSpiritPowerDeathWalk )
|
|
EVENT( EV_ShouldRemainAlignedToAxial, hhPlayer::Event_ShouldRemainAlignedToAxial )
|
|
EVENT( EV_OrientToGravity, hhPlayer::Event_OrientToGravity )
|
|
EVENT( EV_ResetGravity, hhPlayer::Event_ResetGravity )
|
|
EVENT( EV_SetOverlayMaterial, hhPlayer::Event_SetOverlayMaterial )
|
|
EVENT( EV_SetOverlayTime, hhPlayer::Event_SetOverlayTime )
|
|
EVENT( EV_SetOverlayColor, hhPlayer::Event_SetOverlayColor )
|
|
EVENT( EV_DDAHeartbeat, hhPlayer::Event_DDAHeartBeat )
|
|
EVENT( EV_StartHudTranslation, hhPlayer::Event_StartHUDTranslation)
|
|
EVENT( EV_Unfreeze, hhPlayer::Event_Unfreeze)
|
|
EVENT( EV_GetSpiritPower, hhPlayer::Event_GetSpiritPower) //rww
|
|
EVENT( EV_SetSpiritPower, hhPlayer::Event_SetSpiritPower) //rww
|
|
EVENT( EV_OnGround, hhPlayer::Event_OnGround ) // bg
|
|
EVENT( EV_LockWeapon, hhPlayer::Event_LockWeapon ) // mdl
|
|
EVENT( EV_UnlockWeapon, hhPlayer::Event_UnlockWeapon ) // mdl
|
|
EVENT( EV_SetPrivateCameraView, hhPlayer::Event_SetPrivateCameraView ) //rdr
|
|
EVENT( EV_SetCinematicFOV, hhPlayer::Event_SetCinematicFOV ) //rdr
|
|
EVENT( EV_StopSpiritWalk, hhPlayer::Event_StopSpiritWalk ) //rww
|
|
EVENT( EV_DamagePlayer, hhPlayer::Event_DamagePlayer ) //rww
|
|
EVENT( EV_GetSpiritProxy, hhPlayer::Event_GetSpiritProxy ) //rww
|
|
EVENT( EV_IsSpiritWalking, hhPlayer::Event_IsSpiritWalking ) //pdm
|
|
EVENT( EV_IsDeathWalking, hhPlayer::Event_IsDeathWalking ) //bjk
|
|
EVENT( EV_GetDDAValue, hhPlayer::Event_GetDDAValue ) // cjr
|
|
EVENT( EV_AllowLighter, hhPlayer::Event_AllowLighter ) // jsh
|
|
EVENT( EV_DisableSpirit, hhPlayer::Event_DisableSpirit ) // mdl
|
|
EVENT( EV_EnableSpirit, hhPlayer::Event_EnableSpirit ) // mdl
|
|
EVENT( EV_UpdateDDA, hhPlayer::Event_UpdateDDA ) // cjr
|
|
EVENT( EV_AllowDamage, hhPlayer::Event_AllowDamage ) // mdl
|
|
EVENT( EV_IgnoreDamage, hhPlayer::Event_IgnoreDamage ) // mdl
|
|
EVENT( EV_RespawnCleanup, hhPlayer::Event_RespawnCleanup ) //rww
|
|
EVENT( EV_ReturnToWeapon, hhPlayer::Event_ReturnToWeapon ) //bjk
|
|
EVENT( EV_CanAnimateTorso, hhPlayer::Event_CanAnimateTorso ) //rww
|
|
END_CLASS
|
|
|
|
//rww
|
|
CLASS_DECLARATION( hhPlayer, hhArtificialPlayer )
|
|
END_CLASS
|
|
|
|
/*
|
|
=================
|
|
hhPlayer::hhPlayer
|
|
=================
|
|
*/
|
|
hhPlayer::hhPlayer( void ) :
|
|
thirdPersonCameraClipBounds( idTraceModel(idBounds(idVec3(-4, -8, -8), idVec3(8, 8, 8))) ) {
|
|
|
|
hand = NULL; // nla
|
|
spiritProxy = NULL;
|
|
possessedTommy = NULL;
|
|
talon = NULL;
|
|
handNext = NULL;
|
|
deathLookAtEntity = NULL;
|
|
guiWantsControls = NULL;
|
|
lighterHandle = -1;
|
|
nextTalonAttackCommentTime = 0;
|
|
bTalonAttackComment = false;
|
|
bSpiritWalk = false;
|
|
bDeathWalk = false;
|
|
bReallyDead = false;
|
|
bPossessed = false;
|
|
bInCinematic = false;
|
|
bFrozen = false;
|
|
bScopeView = false; //rww
|
|
bInDeathwalkTransition = false;
|
|
lastAppliedBobCycle = 0;
|
|
#if GAMEPAD_SUPPORT // VENOM BEGIN
|
|
lastAutoLevelTime = 0;
|
|
lastAccelTime = 0;
|
|
lastAccelFactor = 1.0f;
|
|
#endif // VENOM END
|
|
cinematicFOV.Init( gameLocal.time, 0.f, 0.f, 0.f, 90.f, 90.f ); //rdr
|
|
mpHitFeedbackTime = 0; //rww
|
|
bAllowSpirit = true; //mdl
|
|
bCollidingWithPortal = false;
|
|
bPlayingLowHealthSound = false;
|
|
bLotaTunnelMode = false;
|
|
|
|
for (int ix=0; ix<MAX_TRACKED_ATTACKERS; ix++) {
|
|
lastAttackers[ix].attacker = NULL;
|
|
lastAttackers[ix].time = 0;
|
|
lastAttackers[ix].displayed = false;
|
|
}
|
|
|
|
forcePredictionButtons = 0; //rww
|
|
//HUMANHEAD PCF mdl 05/02/06 - Initialize weaponFlags to prevent problems during spawn
|
|
weaponFlags = -1;
|
|
|
|
//HUMANHEAD PCF rww 05/03/06 - initialize bob to 0
|
|
bob = 0.0f;
|
|
}
|
|
|
|
void hhPlayer::LinkScriptVariables( void ) {
|
|
idPlayer::LinkScriptVariables();
|
|
|
|
AI_ASIDE.LinkTo( scriptObject, "AI_ASIDE" );
|
|
}
|
|
|
|
/*
|
|
==============
|
|
hhPlayer::Spawn
|
|
|
|
Prepare any resources used by the player.
|
|
==============
|
|
*/
|
|
void hhPlayer::Spawn( void ) {
|
|
|
|
SetupWeaponInfo();
|
|
|
|
spiritDrainHeartbeatMS = SEC2MS(spawnArgs.GetFloat( "spiritDrainHeartbeat", "0.15" ));
|
|
|
|
ddaHeartbeatMS = SEC2MS(spawnArgs.GetFloat( "dda_heartbeat", "1" ));
|
|
ddaNumEnemies = 0;
|
|
ddaProbabilityAccum = 1.0f;
|
|
|
|
// Start DDA heartbeat events, only in single player
|
|
if ( !gameLocal.isMultiplayer ) {
|
|
PostEventMS( &EV_DDAHeartbeat, ddaHeartbeatMS );
|
|
}
|
|
|
|
//Setup crashland stuff
|
|
float gravityMagnitude = physicsObj.GetGravity().Length();
|
|
crashlandSpeed_fatal = hhUtils::DetermineFinalFallVelocityMagnitude( spawnArgs.GetFloat("fallDist_fatal"), gravityMagnitude );
|
|
crashlandSpeed_soft = hhUtils::DetermineFinalFallVelocityMagnitude( spawnArgs.GetFloat("fallDist_soft"), gravityMagnitude );;
|
|
crashlandSpeed_jump = hhUtils::DetermineFinalFallVelocityMagnitude( spawnArgs.GetFloat("fallDist_jump"), gravityMagnitude );;
|
|
|
|
spiritwalkSoundController.SetLoopOnlyOnLocal(entityNumber); //rww
|
|
spiritwalkSoundController.SetOwner( this );
|
|
spiritwalkSoundController.SetLeadIn( spawnArgs.GetString("snd_spiritWalkActivate") );
|
|
spiritwalkSoundController.SetLoop( spawnArgs.GetString("snd_spiritWalkIdle") );
|
|
spiritwalkSoundController.SetLeadOut( spawnArgs.GetString("snd_spiritWalkDeactivate") );
|
|
spiritwalkSoundController.bPlaying = false;
|
|
|
|
deathwalkSoundController.SetOwner( this );
|
|
deathwalkSoundController.SetLeadIn( spawnArgs.GetString("snd_deathWalkActivate") );
|
|
deathwalkSoundController.SetLoop( spawnArgs.GetString("snd_deathWalkIdle") );
|
|
deathwalkSoundController.SetLeadOut( spawnArgs.GetString("snd_deathWalkDeactivate") );
|
|
deathwalkSoundController.bPlaying = false;
|
|
|
|
wallwalkSoundController.SetOwner( this );
|
|
wallwalkSoundController.SetLeadIn( spawnArgs.GetString("snd_wallwalkActivate") );
|
|
wallwalkSoundController.SetLoop( spawnArgs.GetString("snd_wallwalkIdle") );
|
|
wallwalkSoundController.SetLeadOut( spawnArgs.GetString("snd_wallwalkDeactivate") );
|
|
wallwalkSoundController.bPlaying = false;
|
|
|
|
lastResurrectTime = 0;
|
|
|
|
SetVehicleInterface( &vehicleInterfaceLocal );
|
|
|
|
kickSpring = spawnArgs.GetFloat( "kickSpring" ); //HUMANHEAD bjk
|
|
kickDamping = spawnArgs.GetFloat( "kickDamping" ); //HUMANHEAD bjk
|
|
|
|
// mdl: Make sure the lighter is cleared
|
|
memset( &lighter, 0, sizeof( lighter ) );
|
|
|
|
SetupWeaponFlags();
|
|
if ( idealWeapon < 1 || idealWeapon > 8 ) {
|
|
// Go to the highest weapon available if no weapon is current selected (NOTE: This will be ignored if all weapons are locked)
|
|
NextBestWeapon(); //HUMANHEAD bjk
|
|
}
|
|
|
|
//HUMANHEAD PCF mdl 04/26/06 - Made this a separate if block from above so we can disallow locked weapons
|
|
if ( ! ( weaponFlags & ( 1 << ( idealWeapon - 1 ) ) ) ) {
|
|
// If the current weapon is invalid now, try the next weapon
|
|
NextWeapon();
|
|
if ( ! ( weaponFlags & ( 1 << ( idealWeapon - 1 ) ) ) ) {
|
|
// No weapons available
|
|
if ( weapon.GetEntity() ) {
|
|
weapon.GetEntity()->PutAway();
|
|
weapon.GetEntity()->HideWeapon();
|
|
}
|
|
idealWeapon = 0;
|
|
currentWeapon = -1;
|
|
}
|
|
}
|
|
|
|
nextSpiritTime = 0;
|
|
|
|
mpHitFeedbackTime = 0; //rww
|
|
|
|
airAttackerTime = 0;
|
|
bDeathWalkStage2 = false;
|
|
|
|
physicsObj.SetInwardGravity(-1); //rww
|
|
}
|
|
|
|
void hhPlayer::RestorePersistantInfo( void ) {
|
|
int num;
|
|
|
|
idPlayer::RestorePersistantInfo();
|
|
|
|
// Update persistent amoo maximums
|
|
num = GetWeaponNum("weaponobj_soulstripper");
|
|
assert(num);
|
|
weaponInfo[ num ].ammoMax = spawnArgs.GetInt( "max_ammo_energy" );
|
|
|
|
num = GetWeaponNum("weaponobj_bow");
|
|
assert(num);
|
|
weaponInfo[ num ].ammoMax = spawnArgs.GetInt( "max_ammo_spiritpower" );
|
|
}
|
|
|
|
/*
|
|
==============
|
|
hhPlayer::SpawnTalon()
|
|
==============
|
|
*/
|
|
void hhPlayer::TrySpawnTalon() {
|
|
// TODO: Deal with co-op issues. Have multiple Talons in co-op, or just one?
|
|
if (inventory.requirements.bCanSummonTalon && !talon.IsValid()) {
|
|
talon = (hhTalon *)gameLocal.SpawnObject( spawnArgs.GetString( "def_hawkpower" ), NULL );
|
|
if( talon.IsValid() ) {
|
|
talon->SetOwner( this );
|
|
talon->SummonTalon();
|
|
bTalonAttackComment = true;
|
|
nextTalonAttackCommentTime = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
hhPlayer::TalonAttackComment
|
|
|
|
Talon is attacking an enemy, so have Tommy make a comment
|
|
==============
|
|
*/
|
|
void hhPlayer::TalonAttackComment() {
|
|
if ( IsDeathWalking() ) {
|
|
return;
|
|
}
|
|
|
|
// Play a sound on the player to encourage Talon to attack
|
|
if ( bTalonAttackComment && gameLocal.GetTime() > nextTalonAttackCommentTime ) { // Player can make an attack comment
|
|
int num = spawnArgs.GetInt( "numAttackComments", "6" );
|
|
|
|
StartSound( va( "snd_talonattack%d", gameLocal.random.RandomInt( num ) ), SND_CHANNEL_VOICE, 0, false, NULL );
|
|
nextTalonAttackCommentTime = gameLocal.GetTime() + spawnArgs.GetInt( "talonAttackCommentTime", "30000" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
hhPlayer::~hhPlayer()
|
|
|
|
Release any resources used by the player.
|
|
==============
|
|
*/
|
|
hhPlayer::~hhPlayer() {
|
|
//rww - remove me if i'm a pilot
|
|
if (InVehicle() && GetVehicleInterface() && GetVehicleInterface()->GetVehicle()) {
|
|
GetVehicleInterface()->GetVehicle()->EjectPilot();
|
|
}
|
|
|
|
StopSpiritWalk();
|
|
|
|
RemoveResources();
|
|
}
|
|
|
|
|
|
void hhPlayer::RemoveResources() {
|
|
LighterOff();
|
|
SAFE_REMOVE( hand ); //FIXME: Should this be something other than a safe remove? (ie, remove hand?)
|
|
SAFE_REMOVE( talon );
|
|
SAFE_REMOVE( spiritProxy );
|
|
SAFE_REMOVE( possessedTommy );
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
hhPlayer::Init()
|
|
|
|
Called every time a client is placed fresh in the world:
|
|
after the first ClientBegin, and after each respawn
|
|
Initializes all non-persistant parts of playerState
|
|
Called during SpawnToPoint
|
|
==============
|
|
*/
|
|
void hhPlayer::Init() {
|
|
oldCmdAngles.Zero();
|
|
|
|
idPlayer::Init();
|
|
|
|
// initialize the script variables
|
|
AI_ASIDE = false;
|
|
|
|
bInDeathwalkTransition = false;
|
|
bShowProgressBar = false;
|
|
progressBarValue = 0.0f;
|
|
progressBarState = 0;
|
|
progressBarGuiValue.Init(gameLocal.time, 0, 0.0f, 0.0f);
|
|
|
|
LighterOff(); //rww - make sure lighter is removed first (primarily for mp)
|
|
|
|
bobFrac = 0.0f;
|
|
lighterTemperature = 0;
|
|
lighterHandle = -1;
|
|
bPossessed = false; // cjr - player is unpossessed at the start
|
|
possessionTimer = 0; // cjr - player is unpossessed at the start
|
|
possessionFOV = 0; // cjr - no FOV change for possession at start
|
|
nextTalonAttackCommentTime = 0;
|
|
bTalonAttackComment = false; // cjr - If Tommy can make comments about talon attacking enemies
|
|
bSpiritWalk = false; // cjr - initialize spiritwalk variables
|
|
spiritProxy = NULL; // cjr - initialize spiritwalk variables
|
|
possessedTommy = NULL; // cjr - initialize possessed tommy
|
|
lastWeaponSpirit = 0; // cjr - initialize spiritwalk variables
|
|
guiWantsControls = NULL; // pdm - gui that controls should be routed to
|
|
bClampYaw = false; // pdm - whether do clamp yaw to master's axis (for rail rides)
|
|
maxRelativeYaw = 180.0f; // pdm - max deviation from master axis allowed when clamping yaw
|
|
maxRelativePitch = 0.0f; // rww - max deviation from master axis allowed when clamping pitch
|
|
bInCinematic = false; // pdm - our simple cinematic system (position locking)
|
|
lockView = false;
|
|
preCinematicWeapon = 0;
|
|
preCinematicWeaponFlags = 0;
|
|
guiOverlay = NULL; // pdm - current overlay gui
|
|
spiritWalkToggleTime = 0;
|
|
bLotaTunnelMode = false;
|
|
|
|
StopSound(SND_CHANNEL_HEART, false); //rww - make sure this is stopped here, could conceivably still be playing on respawn
|
|
bPlayingLowHealthSound = false;
|
|
|
|
// Set shuttle view off
|
|
if (entityNumber == gameLocal.localClientNum) { //rww
|
|
renderSystem->SetShuttleView( false );
|
|
}
|
|
|
|
if (gameLocal.isMultiplayer) { //rww - reset my really-deadness when i respawn in MP
|
|
bReallyDead = false;
|
|
SetSkinByName(NULL); //make sure we are not using spiritwalk skin.
|
|
}
|
|
|
|
SetViewAnglesSensitivity(1.0f);
|
|
cameraInterpolator.SetSelf( this );
|
|
|
|
for (int ix=0; ix<MAX_TRACKED_ATTACKERS; ix++) {
|
|
lastAttackers[ix].attacker = NULL;
|
|
lastAttackers[ix].time = 0;
|
|
lastAttackers[ix].displayed = false;
|
|
}
|
|
|
|
physicsObj.SetInwardGravity(-1); //rww
|
|
}
|
|
|
|
/*
|
|
===========
|
|
hhPlayer::SpawnToPoint
|
|
|
|
Called every time a client is placed fresh in the world:
|
|
after the first ClientBegin, and after each respawn
|
|
Initializes all non-persistant parts of playerState
|
|
|
|
HUMANHEAD pdm: Many things are initialized in here as this is called before hhPlayer::Spawn. (from idPlayer::Spawn())
|
|
Most of it probably belongs in ::Init() for clarity.
|
|
PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge (big chunk taken from idPlayer::SpawnToPoint)
|
|
============
|
|
*/
|
|
void hhPlayer::SpawnToPoint( const idVec3 &spawn_origin, const idAngles &spawn_angles ) {
|
|
|
|
RemoveResources();
|
|
|
|
// HUMANHEAD aob
|
|
SetAngles( spawn_angles ); // added because we might die while wallwalking
|
|
SetUntransformedViewAngles( spawn_angles ); // Must be initialized before first think below
|
|
SetUntransformedViewAxis( idAngles(0.0f, spawn_angles.yaw, 0.0f).ToMat3() );
|
|
SetViewAnglesSensitivity( 1.0f ); // Must be initialized before first think below
|
|
//HUMANHEAD END
|
|
|
|
// idPlayer::SpawnToPoint() code ------------------------------
|
|
idVec3 spec_origin;
|
|
|
|
assert( !gameLocal.isClient );
|
|
|
|
respawning = true;
|
|
|
|
//HUMANHEAD rww - moved this up above call to Init, since things in there can query player's viewpos.
|
|
kickSpring = spawnArgs.GetFloat( "kickSpring" ); //HUMANHEAD bjk
|
|
kickDamping = spawnArgs.GetFloat( "kickDamping" ); //HUMANHEAD bjk
|
|
|
|
Init();
|
|
|
|
fl.noknockback = false;
|
|
|
|
// stop any ragdolls being used
|
|
StopRagdoll();
|
|
|
|
// set back the player physics
|
|
SetPhysics( &physicsObj );
|
|
|
|
physicsObj.SetClipModelAxis();
|
|
physicsObj.EnableClip();
|
|
|
|
if ( !spectating ) {
|
|
SetCombatContents( true );
|
|
}
|
|
|
|
physicsObj.SetLinearVelocity( vec3_origin );
|
|
|
|
// setup our initial view
|
|
if ( !spectating ) {
|
|
SetOrigin( spawn_origin );
|
|
} else {
|
|
spec_origin = spawn_origin;
|
|
spec_origin[ 2 ] += pm_normalheight.GetFloat();
|
|
spec_origin[ 2 ] += SPECTATE_RAISE;
|
|
SetOrigin( spec_origin );
|
|
}
|
|
|
|
// if this is the first spawn of the map, we don't have a usercmd yet,
|
|
// so the delta angles won't be correct. This will be fixed on the first think.
|
|
viewAngles = ang_zero;
|
|
SetDeltaViewAngles( ang_zero );
|
|
SetViewAngles( spawn_angles );
|
|
spawnAngles = spawn_angles;
|
|
spawnAnglesSet = false;
|
|
|
|
legsForward = true;
|
|
legsYaw = 0.0f;
|
|
idealLegsYaw = 0.0f;
|
|
oldViewYaw = viewAngles.yaw;
|
|
|
|
if ( spectating ) {
|
|
Hide();
|
|
} else {
|
|
Show();
|
|
}
|
|
|
|
if ( gameLocal.isMultiplayer ) {
|
|
#ifndef HUMANHEAD //jsh We don't have a teleport effect
|
|
if ( !spectating ) {
|
|
// we may be called twice in a row in some situations. avoid a double fx and 'fly to the roof'
|
|
if ( lastTeleFX < gameLocal.time - 1000 ) {
|
|
idEntityFx::StartFx( spawnArgs.GetString( "fx_spawn" ), &spawn_origin, NULL, this, true );
|
|
lastTeleFX = gameLocal.time;
|
|
}
|
|
}
|
|
#endif
|
|
AI_TELEPORT = false;//true; //HUMANHEAD rww - we don't have a teleport anim either, ha ha ha (well we do, but it's not done)
|
|
} else {
|
|
AI_TELEPORT = false;
|
|
}
|
|
|
|
// kill anything at the new position
|
|
if ( !spectating ) {
|
|
physicsObj.SetClipMask( MASK_PLAYERSOLID ); // the clip mask is usually maintained in Move(), but KillBox requires it
|
|
gameLocal.KillBox( this );
|
|
}
|
|
|
|
// don't allow full run speed for a bit
|
|
physicsObj.SetKnockBack( 100 );
|
|
|
|
// set our respawn time and buttons so that if we're killed we don't respawn immediately
|
|
minRespawnTime = gameLocal.time;
|
|
maxRespawnTime = gameLocal.time;
|
|
if ( !spectating ) {
|
|
forceRespawn = false;
|
|
}
|
|
|
|
privateCameraView = NULL;
|
|
|
|
BecomeActive( TH_THINK );
|
|
|
|
// HUMANHEAD pdm: Think moved after our initialization
|
|
|
|
respawning = false;
|
|
lastManOver = false;
|
|
lastManPlayAgain = false;
|
|
isTelefragged = false;
|
|
// idPlayer::SpawnToPoint() code end---------------------------
|
|
// ------------------------------------------------------------
|
|
|
|
// aob - added because we might die while wallwalking
|
|
physicsObj.SetGravity( -(spawn_angles.ToMat3()[2]) * gameLocal.GetGravity().Length() );
|
|
physicsObj.SetAxis( idAngles( spawn_angles.pitch, 0.0f, spawn_angles.roll ).ToMat3() );
|
|
physicsObj.CheckWallWalk( true );
|
|
|
|
SetEyeHeight( pm_normalviewheight.GetFloat() );
|
|
|
|
bob = 0.0f;
|
|
lastAppliedBobCycle = 0.0f;
|
|
cameraInterpolator.SetSelf( this );
|
|
cameraInterpolator.Setup( p_camRotRateScale.GetFloat(), IT_VariableMidPointSinusoidal );
|
|
cameraInterpolator.Reset( spawn_origin, idAngles(spawn_angles.pitch, 0.0f, spawn_angles.roll).ToMat3()[2], EyeHeightIdeal() );
|
|
|
|
// Needed after parent call, as these are set in the parent call
|
|
//HUMANHEAD: aob
|
|
prevStepUpOrigin = cameraInterpolator.GetCurrentPosition();
|
|
//HUMANHEAD END
|
|
|
|
if ( !gameLocal.isMultiplayer ) { // Only recharge health and rifle ammo in single player
|
|
lastDamagedTime = gameLocal.time; // CJR PCF 04/27/06: Ensure that lastDamagedTime is set probably at level load
|
|
PostEventSec( &EV_RechargeHealth, spawnArgs.GetFloat( "healthRechargeRate", "0.5" ) );
|
|
PostEventSec( &EV_RechargeRifleAmmo, spawnArgs.GetFloat( "rifleAmmoRechargeRate", "2" ) );
|
|
|
|
// Start the DDA update code
|
|
PostEventSec( &EV_UpdateDDA, spawnArgs.GetFloat( "updateDDARate", "0.25" ) );
|
|
}
|
|
|
|
//HUMANHEAD pdm: Moved from id code above to be after our initialization
|
|
// run a client frame to drop exactly to the floor,
|
|
// initialize animations and other things
|
|
Think();
|
|
}
|
|
|
|
/*
|
|
==============
|
|
hhPlayer::SetupWeaponEntity
|
|
PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge
|
|
==============
|
|
*/
|
|
void hhPlayer::SetupWeaponEntity( void ) {
|
|
weapon.Clear();
|
|
InvalidateCurrentWeapon();
|
|
}
|
|
|
|
/*
|
|
================
|
|
hhPlayer::ApplyImpulse
|
|
================
|
|
*/
|
|
void hhPlayer::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ) {
|
|
if( ent && (ent->IsType(hhForceField::Type) || ent->IsType(hhDeathWraith::Type)) ) {
|
|
idPlayer::ApplyImpulse( ent, id, point, impulse );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
hhPlayer::CL_UpdateProgress
|
|
Client function to update progress related variables
|
|
called after getting a snapshot, or directly if a local client
|
|
================
|
|
*/
|
|
void hhPlayer::CL_UpdateProgress(bool bBar, float value, int state) {
|
|
idUserInterface *_hud = NULL;
|
|
|
|
if (InVehicle() && GetVehicleInterfaceLocal() && GetVehicleInterfaceLocal()->GetHUD()) {
|
|
_hud = GetVehicleInterfaceLocal()->GetHUD();
|
|
}
|
|
else {
|
|
_hud = hud;
|
|
}
|
|
|
|
// Update progress bar
|
|
if (bBar && !bShowProgressBar) {
|
|
_hud->HandleNamedEvent("ShowProgressBar");
|
|
}
|
|
else if (!bBar && bShowProgressBar) {
|
|
_hud->HandleNamedEvent("HideProgressBar");
|
|
}
|
|
|
|
// See if we just entered a victory state
|
|
if (state != progressBarState) {
|
|
switch (state) {
|
|
case 0:
|
|
_hud->HandleNamedEvent("ProgressNone");
|
|
break;
|
|
case 1:
|
|
_hud->HandleNamedEvent("ProgressVictory");
|
|
break;
|
|
case 2:
|
|
_hud->HandleNamedEvent("ProgressFailure");
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Interpolate gui value to target value
|
|
float curValue = progressBarGuiValue.GetCurrentValue(gameLocal.time);
|
|
progressBarGuiValue.Init(gameLocal.time, 500, curValue, value);
|
|
|
|
// Update variables
|
|
bShowProgressBar = bBar;
|
|
progressBarValue = value;
|
|
progressBarState = state;
|
|
}
|
|
|
|
// This called once per tick, put things in here that should be updated regardless of frame rate
|
|
// PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge
|
|
void hhPlayer::UpdateHud( idUserInterface *_hud ) {
|
|
idPlayer *aimed;
|
|
|
|
if ( !_hud ) {
|
|
return;
|
|
}
|
|
|
|
if ( entityNumber != gameLocal.localClientNum ) {
|
|
return;
|
|
}
|
|
|
|
#define PICKUPTIME_FADEIN_START 0
|
|
#define PICKUPTIME_FADEIN_END 500
|
|
#define PICKUPTIME_FADEOUT_START 1750
|
|
#define PICKUPTIME_FADEOUT_END 2000
|
|
#define MULTIPLEITEM_SPEEDUP 7
|
|
int c = inventory.pickupItemNames.Num();
|
|
int time;
|
|
int i;
|
|
int pickupTimeFadeInStart, pickupTimeFadeInStop, pickupTimeFadeOutStart, pickupTimeFadeOutStop;
|
|
float scale = 1.0f / (((c-1.0f)/9.0f) * (MULTIPLEITEM_SPEEDUP-1.0f) + 1.0f);
|
|
pickupTimeFadeInStart = PICKUPTIME_FADEIN_START;
|
|
pickupTimeFadeInStop = PICKUPTIME_FADEIN_END;
|
|
pickupTimeFadeOutStart = PICKUPTIME_FADEOUT_START * scale;
|
|
pickupTimeFadeOutStop = PICKUPTIME_FADEOUT_END * scale;
|
|
|
|
// Determine fade in color for icons
|
|
if (c > 0) {
|
|
for ( i = 0; i < c; i++ ) {
|
|
inventory.pickupItemNames[i].time += gameLocal.msec;
|
|
time = inventory.pickupItemNames[i].time;
|
|
|
|
if (time < pickupTimeFadeInStart) {
|
|
inventory.pickupItemNames[i].matcolorAlpha = 0.0f;
|
|
}
|
|
else if (time < pickupTimeFadeInStop) {
|
|
inventory.pickupItemNames[i].matcolorAlpha = ((float)(time-pickupTimeFadeInStart))/(pickupTimeFadeInStop-pickupTimeFadeInStart);
|
|
}
|
|
else {
|
|
inventory.pickupItemNames[i].matcolorAlpha = 1.0f;
|
|
}
|
|
}
|
|
|
|
// Determine fadeout of slot zero icon
|
|
inventory.pickupItemNames[0].slotZeroTime += gameLocal.msec;
|
|
time = inventory.pickupItemNames[0].slotZeroTime;
|
|
if (time < pickupTimeFadeOutStart) {
|
|
inventory.pickupItemNames[0].matcolorAlpha = 1.0f;
|
|
}
|
|
else if (time < pickupTimeFadeOutStop) {
|
|
inventory.pickupItemNames[0].matcolorAlpha = 1.0f - ((float)(time-pickupTimeFadeOutStart))/(pickupTimeFadeOutStop-pickupTimeFadeOutStart);
|
|
}
|
|
else {
|
|
inventory.pickupItemNames[0].matcolorAlpha = 0.0f;
|
|
}
|
|
|
|
// Remove any expired icons
|
|
time = inventory.pickupItemNames[0].slotZeroTime;
|
|
if (time > pickupTimeFadeOutStop) {
|
|
// icon has faded out, remove from list and slide them down
|
|
inventory.pickupItemNames.RemoveIndex(0);
|
|
}
|
|
}
|
|
|
|
// HUMANHEAD pdm: Changed aim logic to always show color-coded aimee
|
|
if ( MPAim != -1 && gameLocal.entities[ MPAim ]
|
|
&& gameLocal.entities[ MPAim ]->IsType( idPlayer::Type ) ) {
|
|
|
|
aimed = static_cast< idPlayer * >( gameLocal.entities[ MPAim ] );
|
|
_hud->SetStateString( "aim_text", gameLocal.userInfo[ MPAim ].GetString( "ui_name" ) );
|
|
idVec4 teamColor = aimed->GetTeamColor();
|
|
_hud->SetStateFloat( "aim_R", teamColor.x );
|
|
_hud->SetStateFloat( "aim_G", teamColor.y );
|
|
_hud->SetStateFloat( "aim_B", teamColor.z );
|
|
|
|
//HUMANHEAD rww - health display on hud for gameplay testing
|
|
if (g_showAimHealth.GetBool()) {
|
|
_hud->SetStateInt("aim_health", gameLocal.entities[MPAim]->health);
|
|
}
|
|
//HUMANHEAD END
|
|
}
|
|
else {
|
|
_hud->SetStateString( "aim_text", "" );
|
|
|
|
//HUMANHEAD rww
|
|
_hud->SetStateInt("aim_health", 0);
|
|
//HUMANHEAD END
|
|
}
|
|
|
|
#if !GOLD
|
|
_hud->SetStateInt( "g_showProjectilePct", g_showProjectilePct.GetInteger() );
|
|
if ( numProjectilesFired ) {
|
|
_hud->SetStateString( "projectilepct", va( "Hit %% %.1f", ( (float) numProjectileHits / numProjectilesFired ) * 100 ) );
|
|
} else {
|
|
_hud->SetStateString( "projectilepct", "Hit % 0.0" );
|
|
}
|
|
#endif
|
|
|
|
if (gameLocal.isNewFrame) { //HUMANHEAD rww - only on newframe
|
|
// Update low health sound
|
|
if ( health > 0 && health < 25 && !IsSpiritOrDeathwalking() ) {
|
|
if (!bPlayingLowHealthSound) {
|
|
StartSound("snd_lowhealth", SND_CHANNEL_HEART, 0, false, NULL);
|
|
bPlayingLowHealthSound = true;
|
|
}
|
|
}
|
|
else {
|
|
if (bPlayingLowHealthSound) {
|
|
StopSound(SND_CHANNEL_HEART, false);
|
|
bPlayingLowHealthSound = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle damage indictors
|
|
idVec3 localDamageVector;
|
|
idVec3 toEnemy;
|
|
for (int ix=0; ix<MAX_TRACKED_ATTACKERS; ix++) {
|
|
if (lastAttackers[ix].attacker.IsValid()) {
|
|
|
|
if (!lastAttackers[ix].displayed) {
|
|
_hud->HandleNamedEvent( va("Attacked%i", ix) );//Notify hud we were attacked
|
|
_hud->SetStateBool("displayDamageIndicators", true);
|
|
lastAttackers[ix].displayed = true;
|
|
}
|
|
|
|
toEnemy = lastAttackers[ix].attacker->GetOrigin() - GetOrigin();
|
|
toEnemy.z = 0.0f;
|
|
GetAxis().ProjectVector( toEnemy, localDamageVector );
|
|
float yawToEnemy = idMath::AngleNormalize180(localDamageVector.ToYaw());
|
|
_hud->SetStateFloat( va("yawToEnemy%i", ix), yawToEnemy);
|
|
if (gameLocal.time > lastAttackers[ix].time + DAMAGE_INDICATOR_TIME) {
|
|
lastAttackers[ix].attacker = NULL;
|
|
|
|
bool allExpired = true;
|
|
for (int jx=0; jx<MAX_TRACKED_ATTACKERS; jx++) {
|
|
if (lastAttackers[jx].attacker.IsValid()) {
|
|
allExpired = false;
|
|
break;
|
|
}
|
|
}
|
|
if (allExpired) {
|
|
_hud->SetStateBool("displayDamageIndicators", false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (gameLocal.isMultiplayer) {
|
|
bool bLagged = isLagged && gameLocal.isMultiplayer && (gameLocal.localClientNum == entityNumber);
|
|
_hud->SetStateBool("hudLag", bLagged);
|
|
}
|
|
// HUMANHEAD PCF pdm 06-26-06: Fix for multiplayer shuttle hud elements appearing in SP after a MP session
|
|
else {
|
|
_hud->SetStateBool( "ismultiplayer", false );
|
|
}
|
|
// HUMANHEAD END
|
|
}
|
|
|
|
// PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge
|
|
void hhPlayer::UpdateHudWeapon(bool flashWeapon) {
|
|
idUserInterface *_hud = hhPlayer::hud;
|
|
|
|
// if updating the hud of a followed client
|
|
if ( gameLocal.localClientNum >= 0 && gameLocal.entities[ gameLocal.localClientNum ] && gameLocal.entities[ gameLocal.localClientNum ]->IsType( idPlayer::Type ) ) {
|
|
idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.localClientNum ] );
|
|
if ( p->spectating && p->spectator == entityNumber ) {
|
|
assert( p->hud );
|
|
_hud = p->hud;
|
|
}
|
|
}
|
|
|
|
if ( _hud ) {
|
|
// HUMANHEAD pdm: changed to suit our needs
|
|
_hud->SetStateInt( "currentweapon", GetCurrentWeapon() );
|
|
_hud->SetStateInt( "idealweapon", GetIdealWeapon() );
|
|
//HUMANHEAD PCF mdl 05/05/06 - Added !IsLocked( GetIdealWeapon() )
|
|
if ( flashWeapon && !IsLocked( GetIdealWeapon() ) ) {
|
|
_hud->HandleNamedEvent( "weaponChange" );
|
|
}
|
|
}
|
|
}
|
|
|
|
// PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge
|
|
void hhPlayer::UpdateHudAmmo(idUserInterface *_hud) {
|
|
assert( _hud );
|
|
float ammoPct, altPct;
|
|
int ammoType, altAmmoType;
|
|
float ammo, altAmmo;
|
|
bool ammoLow, altAmmoLow;
|
|
|
|
// HUMANHEAD pdm: Weapon switch overlay, using rover to spread the expense over frames
|
|
static int rover = 0;
|
|
if (++rover > 9) {
|
|
rover = 0;
|
|
}
|
|
|
|
if (rover) {
|
|
bool bHeld = false;
|
|
ammoPct = 0.0f;
|
|
altPct = 0.0f;
|
|
ammoType = weaponInfo[rover].ammoType;
|
|
altAmmoType = altWeaponInfo[rover].ammoType;
|
|
ammo = inventory.ammo[ammoType];
|
|
altAmmo = inventory.ammo[altAmmoType];
|
|
ammoLow = false;
|
|
altAmmoLow = false;
|
|
|
|
if ( inventory.weapons & ( 1 << rover ) ) {
|
|
// have this weapon
|
|
bHeld = true;
|
|
ammoPct = ammo / weaponInfo[rover].ammoMax;
|
|
altPct = altAmmo / altWeaponInfo[rover].ammoMax;
|
|
ammoLow = ammo > 0 && ammo <= weaponInfo[rover].ammoLow;
|
|
altAmmoLow = altAmmo > 0 && altAmmo <= altWeaponInfo[rover].ammoLow;
|
|
}
|
|
|
|
// Spirit ammo doesn't display in a bar
|
|
if ( ammoType == 1 ) {
|
|
ammoType = 0;
|
|
altAmmoType = 0;
|
|
}
|
|
|
|
// Zero out alt-ammo bar if appropriate
|
|
if (altAmmoType == 0 || altAmmoType == ammoType) {
|
|
altPct = 0.0f;
|
|
}
|
|
|
|
_hud->SetStateBool (va("weapon%d_held", rover), bHeld);
|
|
_hud->SetStateBool (va("weapon%d_ammolow", rover), ammoLow);
|
|
_hud->SetStateBool (va("weapon%d_altammolow", rover), altAmmoLow);
|
|
_hud->SetStateFloat(va("weapon%d_ammo", rover), ammoPct);
|
|
_hud->SetStateFloat(va("weapon%d_altammo", rover), altPct);
|
|
_hud->SetStateBool (va("weapon%d_ammoempty", rover), ammoType != 0 && ammo == 0 && altAmmo == 0);
|
|
}
|
|
|
|
// Update the current weapon's ammo status
|
|
bool bDisallowAmmoBars = false;
|
|
ammoPct = 0.0f;
|
|
altPct = 0.0f;
|
|
ammoLow = false;
|
|
altAmmoLow = false;
|
|
ammoType = 0;
|
|
altAmmoType = 0;
|
|
ammo = 0;
|
|
altAmmo = 0;
|
|
|
|
if( bLotaTunnelMode || privateCameraView || IsLocked(idealWeapon) || !weapon.IsValid() || !weapon->IsLinked() || currentWeapon == -1) {
|
|
// Don't display ammo bar for invalid weapons, or when weapons are locked
|
|
bDisallowAmmoBars = true;
|
|
}
|
|
else {
|
|
if (currentWeapon >= 0 && currentWeapon < 15) {
|
|
ammoType = weaponInfo[currentWeapon].ammoType;
|
|
altAmmoType = altWeaponInfo[currentWeapon].ammoType;
|
|
ammo = inventory.ammo[ammoType];
|
|
altAmmo = inventory.ammo[altAmmoType];
|
|
ammoPct = ammo / weaponInfo[currentWeapon].ammoMax;
|
|
altPct = altAmmo / altWeaponInfo[currentWeapon].ammoMax;
|
|
ammoLow = ammo > 0 && ammo <= weaponInfo[currentWeapon].ammoLow;
|
|
altAmmoLow = altAmmo > 0 && altAmmo <= altWeaponInfo[currentWeapon].ammoLow;
|
|
}
|
|
|
|
if (ammoType == 1) {
|
|
bDisallowAmmoBars = true;
|
|
}
|
|
}
|
|
|
|
if ( bDisallowAmmoBars ) {
|
|
_hud->SetStateBool( "player_ammobar", false);
|
|
_hud->SetStateBool( "player_altammobar", false );
|
|
}
|
|
else {
|
|
_hud->SetStateBool( "player_ammobar", ammoType != 0);
|
|
_hud->SetStateBool( "player_altammobar", altAmmoType != 0 && altAmmoType != ammoType );
|
|
|
|
_hud->SetStateFloat( "player_ammopercent", ammoPct );
|
|
_hud->SetStateFloat( "player_altammopercent", altPct );
|
|
_hud->SetStateString( "player_ammoamounttext", ammo<0 ? "" : va("%d", ammo) );
|
|
_hud->SetStateString( "player_altammoamounttext", altAmmo<0 ? "" : va("%d", altAmmo) );
|
|
_hud->SetStateBool( "player_ammolow", ammoLow );
|
|
_hud->SetStateBool( "player_altammolow", altAmmoLow );
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
================
|
|
hhPlayer::UpdateHudStats
|
|
PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge
|
|
================
|
|
*/
|
|
void hhPlayer::UpdateHudStats( idUserInterface *_hud ) {
|
|
|
|
assert( _hud );
|
|
|
|
int spiritPower = GetSpiritPower();
|
|
|
|
// Disallow health if wrench is locked on a non-lota map --or-- if in a private camera
|
|
bool disallowHealth = bLotaTunnelMode || (IsLocked(1) && !gameLocal.IsLOTA()) || privateCameraView != NULL;
|
|
bool disallowProgress = bLotaTunnelMode || privateCameraView != NULL || IsDeathWalking();
|
|
|
|
if (disallowHealth) {
|
|
int blah = 0;
|
|
}
|
|
else {
|
|
int blah2 = 0;
|
|
}
|
|
|
|
_hud->SetStateBool("invehicle", InVehicle());
|
|
_hud->SetStateBool("deathwalking", IsDeathWalking());
|
|
_hud->SetStateBool("spiritwalking", IsSpiritWalking());
|
|
_hud->SetStateBool("lighter", IsLighterOn() );
|
|
_hud->SetStateBool("showhealth", !disallowHealth);
|
|
_hud->SetStateBool("allowprogress", !disallowProgress);
|
|
_hud->SetStateBool("showspiritpower", (!disallowHealth && !InVehicle() && (inventory.requirements.bCanSpiritWalk || IsDeathWalking())));
|
|
_hud->SetStateFloat("lightertemp", lighterTemperature);
|
|
_hud->SetStateFloat( "player_spiritpercent", ((float)spiritPower)/inventory.maxSpirit );
|
|
_hud->SetStateFloat( "player_healthpercent", ((float)health)/inventory.maxHealth );
|
|
_hud->SetStateFloat( "player_healthR", inventory.maxHealth > 100 ? 0.75f : 0.6f );
|
|
_hud->SetStateFloat( "player_healthG", inventory.maxHealth > 100 ? 0.85f : 0.0f );
|
|
_hud->SetStateFloat( "player_healthB", inventory.maxHealth > 100 ? 1.0f : 0.0f );
|
|
_hud->SetStateFloat( "player_healthPulseR", inventory.maxHealth > 100 ? 1.0f : 1.0f );
|
|
_hud->SetStateFloat( "player_healthPulseG", inventory.maxHealth > 100 ? 1.0f : 0.0f );
|
|
_hud->SetStateFloat( "player_healthPulseB", inventory.maxHealth > 100 ? 1.0f : 0.0f );
|
|
_hud->SetStateInt( "player_health", health );
|
|
_hud->SetStateInt( "player_maxhealth", inventory.maxHealth ); //rww
|
|
_hud->SetStateString( "player_spirit", spiritPower<0 ? "0" : va("%d", spiritPower) );
|
|
_hud->SetStateFloat( "progress", progressBarGuiValue.GetCurrentValue(gameLocal.time) );
|
|
|
|
if ( healthPulse ) {
|
|
_hud->HandleNamedEvent( "healthPulse" );
|
|
StartSound( "snd_healthpulse", SND_CHANNEL_ITEM, 0, false, NULL );
|
|
healthPulse = false;
|
|
}
|
|
if ( spiritPulse ) {
|
|
_hud->HandleNamedEvent( "spiritPulse" );
|
|
StartSound( "snd_spiritpulse", SND_CHANNEL_ITEM, 0, false, NULL );
|
|
spiritPulse = false;
|
|
}
|
|
|
|
if ( inventory.ammoPulse ) {
|
|
_hud->HandleNamedEvent( "ammoPulse" );
|
|
inventory.ammoPulse = false;
|
|
}
|
|
if ( inventory.weaponPulse ) {
|
|
// We need to update the weapon hud manually, but not
|
|
// the armor/ammo/health because they are updated every
|
|
// frame no matter what
|
|
UpdateHudWeapon();
|
|
_hud->HandleNamedEvent( "weaponPulse" );
|
|
inventory.weaponPulse = false;
|
|
}
|
|
|
|
UpdateHudAmmo( _hud );
|
|
|
|
// Determine slots for our pickup icons
|
|
int c = inventory.pickupItemNames.Num();
|
|
for ( int i=0; i<10; i++ ) {
|
|
if (i<c) {
|
|
_hud->SetStateString( va( "itemicon%i", i ), inventory.pickupItemNames[i].icon );
|
|
_hud->SetStateFloat( va( "itemalpha%i", i ), inventory.pickupItemNames[i].matcolorAlpha );
|
|
_hud->SetStateBool( va( "itemwide%i", i), inventory.pickupItemNames[i].bDoubleWide );
|
|
}
|
|
else {
|
|
_hud->SetStateString( va( "itemicon%i", i ), "" );
|
|
_hud->SetStateFloat( va( "itemalpha%i", i ), 0.0f );
|
|
_hud->SetStateBool( va( "itemwide%i", i), false );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::DrawHUD
|
|
HUMANHEAD: This run only for local players
|
|
PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge
|
|
===============
|
|
*/
|
|
void hhPlayer::DrawHUD( idUserInterface *_hud ) {
|
|
|
|
if (guiOverlay) {
|
|
guiOverlay->Redraw(gameLocal.realClientTime);
|
|
sysEvent_t ev;
|
|
const char *command;
|
|
ev = sys->GenerateMouseMoveEvent( -2000, -2000 );
|
|
command = guiOverlay->HandleEvent( &ev, gameLocal.time );
|
|
// HandleGuiCommands( this, command );
|
|
return;
|
|
}
|
|
|
|
// HUMANHEAD pdm: removed weapon ptr check here since ours is invalid when in a vehicle
|
|
// Also removed privateCameraView, since we want subtitles when they are on
|
|
if ( gameLocal.GetCamera() || !_hud || !g_showHud.GetBool() || pm_thirdPerson.GetBool() ) {
|
|
return;
|
|
}
|
|
|
|
UpdateHudStats( _hud );
|
|
|
|
|
|
if(weapon.IsValid()) { // HUMANHEAD
|
|
bool allowGuiUpdate = true;
|
|
//rww - update the weapon gui only if the owner is being spectated by this client, or is this client
|
|
if ( gameLocal.localClientNum != entityNumber ) {
|
|
// if updating the hud for a followed client
|
|
if ( gameLocal.localClientNum >= 0 && gameLocal.entities[ gameLocal.localClientNum ] && gameLocal.entities[ gameLocal.localClientNum ]->IsType( idPlayer::Type ) ) {
|
|
idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.localClientNum ] );
|
|
if ( !p->spectating || p->spectator != entityNumber ) {
|
|
allowGuiUpdate = false;
|
|
}
|
|
} else {
|
|
allowGuiUpdate = false;
|
|
}
|
|
}
|
|
|
|
if (allowGuiUpdate) {
|
|
weapon->UpdateGUI();
|
|
}
|
|
}
|
|
|
|
_hud->SetStateInt( "s_debug", cvarSystem->GetCVarInteger( "s_showLevelMeter" ) );
|
|
|
|
//HUMANHEAD aob: vehicle logic
|
|
if ( InVehicle() ) {
|
|
DrawHUDVehicle( _hud );
|
|
}
|
|
else {
|
|
_hud->Redraw( gameLocal.realClientTime );
|
|
}
|
|
//HUMANHEAD END
|
|
|
|
// weapon targeting crosshair
|
|
if ( !GuiActive() ) {
|
|
if ( cursor ) {
|
|
UpdateCrosshairs();
|
|
cursor->Redraw( gameLocal.realClientTime );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::DrawHUDVehicle
|
|
HUMANHEAD: This run only for local players
|
|
===============
|
|
*/
|
|
void hhPlayer::DrawHUDVehicle( idUserInterface* _hud ) {
|
|
if (GetVehicleInterfaceLocal()) {
|
|
GetVehicleInterfaceLocal()->DrawHUD( _hud );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::FireWeapon
|
|
===============
|
|
*/
|
|
void hhPlayer::FireWeapon( void ) {
|
|
idMat3 axis;
|
|
idVec3 muzzle;
|
|
|
|
if ( privateCameraView ) {
|
|
return;
|
|
}
|
|
|
|
if ( g_editEntityMode.GetInteger() ) {
|
|
GetViewPos( muzzle, axis );
|
|
if ( gameLocal.editEntities->SelectEntity( muzzle, axis[0], this ) ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
//HUMANHEAD: aob - removed ammo check because we allow weapons to change modes
|
|
if( weapon.IsValid() ) {
|
|
AI_ATTACK_HELD = true;
|
|
weapon->BeginAttack();
|
|
}
|
|
//HUMANEHAD END
|
|
}
|
|
|
|
//=========================================================================
|
|
//
|
|
// hhPlayer::FireWeaponAlt
|
|
//
|
|
//=========================================================================
|
|
|
|
void hhPlayer::FireWeaponAlt( void ) {
|
|
idMat3 axis;
|
|
idVec3 muzzle;
|
|
|
|
if ( privateCameraView ) {
|
|
return;
|
|
}
|
|
|
|
if ( g_editEntityMode.GetBool() ) {
|
|
GetViewPos( muzzle, axis );
|
|
if ( gameLocal.editEntities->SelectEntity( muzzle, axis[0], NULL ) ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( gameLocal.isMultiplayer && spectating ) {
|
|
static int lastSpectatorSwitch = 0;
|
|
if ( gameLocal.time > lastSpectatorSwitch + 500 ) {
|
|
spectator = gameLocal.GetNextClientNum( spectator );
|
|
idPlayer *player = gameLocal.GetClientByNum( spectator );
|
|
while ( player->spectating && player != this ) {
|
|
player = gameLocal.GetClientByNum(spectator);
|
|
}
|
|
lastSpectatorSwitch = gameLocal.time;
|
|
}
|
|
return;
|
|
}
|
|
|
|
//HUMANHEAD: aob - removed ammo check because we allow weapons to change modes
|
|
if( weapon.IsValid() ) {
|
|
AI_ATTACK_HELD = true;
|
|
weapon->BeginAltAttack();
|
|
}
|
|
//HUMANEHAD END
|
|
|
|
if ( hud ) {
|
|
hud->HandleNamedEvent( "closeObjective" );
|
|
}
|
|
}
|
|
|
|
void hhPlayer::StopFiring( void ) {
|
|
idPlayer::StopFiring();
|
|
if ( weapon.GetEntity() && weapon->IsType( hhWeaponRifle::Type ) ) {
|
|
static_cast<hhWeaponRifle*>( weapon.GetEntity() )->ZoomOut();
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::HasAmmo
|
|
===============
|
|
*/
|
|
int hhPlayer::HasAmmo( ammo_t type, int amount ) {
|
|
return inventory.HasAmmo( type, amount );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::UseAmmo
|
|
===============
|
|
*/
|
|
bool hhPlayer::UseAmmo( ammo_t type, int amount ) {
|
|
return inventory.UseAmmo( type, amount );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::NextBestWeapon
|
|
===============
|
|
*/
|
|
void hhPlayer::NextBestWeapon( void ) {
|
|
if ( ActiveGui() || IsSpiritOrDeathwalking() ) {
|
|
return;
|
|
}
|
|
idPlayer::NextBestWeapon();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::SelectWeapon
|
|
===============
|
|
*/
|
|
void hhPlayer::SelectWeapon( int num, bool force ) {
|
|
if ( ! ( weaponFlags & ( 1 << ( num - 1 ) ) ) ) {
|
|
return;
|
|
}
|
|
if ( bFrozen || ActiveGui() || ( SkipWeapon(num) || IsSpiritOrDeathwalking() ) ) {
|
|
return;
|
|
}
|
|
idPlayer::SelectWeapon( num, force );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::NextWeapon
|
|
===============
|
|
*/
|
|
void hhPlayer::NextWeapon( void ) {
|
|
if ( ActiveGui() || IsSpiritOrDeathwalking() || bFrozen ) {
|
|
// NOTE: Spirit weapon is disallowed from being cycled to using the weapon*_cycle key
|
|
return;
|
|
}
|
|
//idPlayer::NextWeapon();
|
|
if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || privateCameraView || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) {
|
|
return;
|
|
}
|
|
|
|
if ( gameLocal.isClient ) {
|
|
return;
|
|
}
|
|
|
|
// check if we have any weapons
|
|
if ( !inventory.weapons ) {
|
|
return;
|
|
}
|
|
|
|
const char *weap;
|
|
int w, start;
|
|
|
|
w = idealWeapon;
|
|
start = w;
|
|
while( 1 ) {
|
|
w++;
|
|
if ( w >= MAX_WEAPONS ) {
|
|
// No weapon selected and nothing to select
|
|
if ( start == -1 ) {
|
|
idealWeapon = 0;
|
|
currentWeapon = 0;
|
|
return;
|
|
}
|
|
w = 0;
|
|
}
|
|
// Keep us from an infinite loop if no weapons are valid
|
|
if ( w == start ) {
|
|
if ( ! ( weaponFlags & ( 1 << ( w - 1 ) ) ) ) {
|
|
idealWeapon = 0;
|
|
currentWeapon = 0;
|
|
}
|
|
return;
|
|
}
|
|
weap = spawnArgs.GetString( va( "def_weapon%d", w ) );
|
|
if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) {
|
|
continue;
|
|
}
|
|
if ( !weap[ 0 ] ) {
|
|
continue;
|
|
}
|
|
if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) {
|
|
continue;
|
|
}
|
|
// Make sure the weapon is valid for this level
|
|
if ( ! ( weaponFlags & ( 1 << ( w - 1 ) ) ) ) {
|
|
continue;
|
|
}
|
|
if ( inventory.HasAmmo( weap ) || inventory.HasAltAmmo( weap ) || spawnArgs.GetBool( va( "weapon%d_allowempty", w ) ) ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) {
|
|
idealWeapon = w;
|
|
weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY;
|
|
UpdateHudWeapon();
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::PrevWeapon
|
|
===============
|
|
*/
|
|
void hhPlayer::PrevWeapon( void ) {
|
|
if ( ActiveGui() || IsSpiritOrDeathwalking() || bFrozen ) {
|
|
// NOTE: Spirit weapon is disallowed from being cycled to using the weapon*_cycle key
|
|
return;
|
|
}
|
|
//idPlayer::PrevWeapon();
|
|
if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || privateCameraView || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) {
|
|
return;
|
|
}
|
|
|
|
if ( gameLocal.isClient ) {
|
|
return;
|
|
}
|
|
|
|
// check if we have any weapons
|
|
if ( !inventory.weapons ) {
|
|
return;
|
|
}
|
|
|
|
const char *weap;
|
|
int w = idealWeapon, start = w;
|
|
if (w == -1) {
|
|
w = MAX_WEAPONS - 1;
|
|
}
|
|
while( 1 ) {
|
|
w--;
|
|
if ( w < 0 ) {
|
|
// No weapon selected and nothing to select
|
|
if ( start == -1 ) {
|
|
idealWeapon = 0;
|
|
currentWeapon = 0;
|
|
return;
|
|
}
|
|
|
|
w = MAX_WEAPONS - 1;
|
|
}
|
|
// Keep us from an infinite loop if no weapons are valid
|
|
if ( w == start ) {
|
|
if ( ! ( weaponFlags & ( 1 << ( w - 1 ) ) ) ) {
|
|
idealWeapon = 0;
|
|
currentWeapon = 0;
|
|
}
|
|
return;
|
|
}
|
|
weap = spawnArgs.GetString( va( "def_weapon%d", w ) );
|
|
if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) {
|
|
continue;
|
|
}
|
|
if ( !weap[ 0 ] ) {
|
|
continue;
|
|
}
|
|
if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) {
|
|
continue;
|
|
}
|
|
// Make sure the weapon is valid for this level
|
|
if ( ! ( weaponFlags & ( 1 << ( w - 1 ) ) ) ) {
|
|
continue;
|
|
}
|
|
if ( inventory.HasAmmo( weap ) || inventory.HasAltAmmo( weap ) || spawnArgs.GetBool( va( "weapon%d_allowempty", w ) ) ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) {
|
|
idealWeapon = w;
|
|
weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY;
|
|
UpdateHudWeapon();
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::SkipWeapon
|
|
===============
|
|
*/
|
|
bool hhPlayer::SkipWeapon( int weaponNum ) const {
|
|
//No bow if not in spirit mode
|
|
return !IsSpiritOrDeathwalking() && weaponNum == spawnArgs.GetInt("spirit_weapon");
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::SnapDownCurrentWeapon
|
|
|
|
HUMANHEAD: aob
|
|
===============
|
|
*/
|
|
void hhPlayer::SnapDownCurrentWeapon() {
|
|
if( weapon.IsValid() ) {
|
|
weapon->SnapDown();
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::SnapUpCurrentWeapon
|
|
|
|
HUMANHEAD: aob
|
|
===============
|
|
*/
|
|
void hhPlayer::SnapUpCurrentWeapon() {
|
|
if( weapon.IsValid() ) {
|
|
weapon->SnapUp();
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::SelectEtherealWeapon
|
|
|
|
HUMANHEAD: aob
|
|
===============
|
|
*/
|
|
void hhPlayer::SelectEtherealWeapon() {
|
|
if ( GetCurrentWeapon() != 0 ) {
|
|
lastWeaponSpirit = GetCurrentWeapon();
|
|
}
|
|
|
|
weaponHandState.SetPlayer( this );
|
|
if (!gameLocal.isClient) {
|
|
int spiritWeaponIndex = spawnArgs.GetInt("spirit_weapon");
|
|
const char *spiritWeaponName = NULL;
|
|
if (bDeathWalk || (inventory.weapons & (1 << spiritWeaponIndex)) ) {
|
|
spiritWeaponName = GetWeaponName(spiritWeaponIndex);
|
|
}
|
|
weaponHandState.Archive( spiritWeaponName, 0, (InGUIMode()) ? "guihand_normal" : NULL );
|
|
}
|
|
|
|
if ( weapon.IsValid() ) {
|
|
weapon->SetShaderParm( SHADERPARM_MODE, MS2SEC( gameLocal.time ) ); // Glow in the bow
|
|
weapon->SetShaderParm( SHADERPARM_MISC, 1.0f ); // Turn on the arrow
|
|
weapon->SetShaderParm( SHADERPARM_DIVERSITY, MS2SEC( gameLocal.time ) ); // Glow in the arrow
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::PutawayEtherealWeapon
|
|
|
|
HUMANHEAD: aob
|
|
===============
|
|
*/
|
|
void hhPlayer::PutawayEtherealWeapon() {
|
|
//If we haven't actually changed weapons yet, put current weapon up
|
|
if( GetCurrentWeapon() != lastWeaponSpirit ) {
|
|
SnapDownCurrentWeapon();
|
|
} else {
|
|
SnapUpCurrentWeapon();
|
|
}
|
|
|
|
weaponHandState.RestoreFromArchive();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::UpdateWeapon
|
|
PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge
|
|
===============
|
|
*/
|
|
void hhPlayer::UpdateWeapon( void ) {
|
|
|
|
if ( IsDead() ) { // HUMANHEAD cjr: Replaced health <= 0 with IsDead() call for deathwalk override
|
|
return;
|
|
}
|
|
|
|
assert( !spectating );
|
|
|
|
if ( gameLocal.isClient ) {
|
|
// clients need to wait till the weapon and it's world model entity
|
|
// are present and synchronized ( weapon.worldModel idEntityPtr to idAnimatedEntity )
|
|
if ( !weapon.GetEntity()->IsWorldModelReady() ) {
|
|
return;
|
|
}
|
|
}
|
|
else if (gameLocal.isMultiplayer) { //rww - projectile deferring
|
|
if (weapon.IsValid()) {
|
|
weapon->CheckDeferredProjectiles();
|
|
}
|
|
}
|
|
|
|
// always make sure the weapon is correctly setup before accessing it
|
|
if ( weapon.GetEntity() && !weapon.GetEntity()->IsLinked() ) {
|
|
if ( idealWeapon != -1 ) {
|
|
animPrefix = spawnArgs.GetString( va( "def_weapon%d", idealWeapon ) );
|
|
weapon.GetEntity()->GetWeaponDef( animPrefix, inventory.clip[ idealWeapon ] );
|
|
animPrefix.Strip( "weaponobj_" ); //HUMANHEAD rww
|
|
assert( weapon.GetEntity()->IsLinked() );
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
//HUMANEHAD rww
|
|
if (weapon.IsValid() && weapon->IsType(hhWeaponSoulStripper::Type)) {
|
|
hhWeaponSoulStripper *leechGun = static_cast<hhWeaponSoulStripper *>(weapon.GetEntity());
|
|
leechGun->CheckCans();
|
|
}
|
|
//HUMANHEAD END
|
|
|
|
if (!InVehicle()) {
|
|
if ( g_dragEntity.GetBool() ) {
|
|
StopFiring();
|
|
dragEntity.Update( this );
|
|
if ( weapon.IsValid() ) {
|
|
weapon.GetEntity()->FreeModelDef();
|
|
}
|
|
return;
|
|
} else if (ActiveGui()) {
|
|
// gui handling overrides weapon use
|
|
Weapon_GUI();
|
|
} else {
|
|
Weapon_Combat();
|
|
}
|
|
|
|
// Determine whether we are in aside state
|
|
if ( weapon.IsValid() && weapon->IsAside() ) {
|
|
if ( !AI_ASIDE ) {
|
|
AI_ASIDE = true;
|
|
SetState( "AsideWeapon" );
|
|
UpdateScript();
|
|
}
|
|
} else {
|
|
AI_ASIDE = false;
|
|
}
|
|
}
|
|
|
|
//HUMANHEAD: aob - added weapon validity check
|
|
if( weapon.IsValid() ) {
|
|
// update weapon state, particles, dlights, etc
|
|
weapon->PresentWeapon( showWeaponViewModel );
|
|
}
|
|
|
|
// nla
|
|
if ( hand.IsValid() ) {
|
|
hand->Present();
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::InGUIMode
|
|
nla - Used for the GUI hand to check if it should be up. (Coming back from spiritwalk)
|
|
===============
|
|
*/
|
|
bool hhPlayer::InGUIMode() {
|
|
|
|
// Logic adapted from UpdateWeapon
|
|
if ( ActiveGui() && !InVehicle() ) {
|
|
return( true );
|
|
}
|
|
|
|
return( false );
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::Weapon_Combat
|
|
PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge
|
|
===============
|
|
*/
|
|
void hhPlayer::Weapon_Combat( void ) {
|
|
idMat3 axis;
|
|
idVec3 muzzle;
|
|
idDict args;
|
|
|
|
if ( !weaponEnabled || gameLocal.inCinematic || privateCameraView ) {
|
|
return;
|
|
}
|
|
|
|
// HUMANHEAD nla
|
|
if ( hand.IsValid() && hand->IsType( hhGuiHand::Type ) && hand->IsAttached() && !hand->IsLowering() ) {
|
|
hand->RemoveHand();
|
|
}
|
|
// HUMANHEAD END
|
|
|
|
if( idealWeapon != 0 && IsLocked(idealWeapon) ) { //HUMANHEAD bjk PCF (4-30-06) - fix wrench up in roadhouse
|
|
NextWeapon();
|
|
}
|
|
|
|
if ( idealWeapon == 0 ) {
|
|
return;
|
|
}
|
|
|
|
RaiseWeapon();
|
|
if ( weapon.IsValid() ) {
|
|
weapon.GetEntity()->PutUpright();
|
|
}
|
|
|
|
if ( weapon.IsValid() && weapon->IsReloading() ) {
|
|
if ( !AI_RELOAD ) {
|
|
AI_RELOAD = true;
|
|
SetState( "ReloadWeapon" );
|
|
UpdateScript();
|
|
}
|
|
} else {
|
|
AI_RELOAD = false;
|
|
}
|
|
|
|
if ( idealWeapon != currentWeapon && (!gameLocal.isMultiplayer || inventory.lastShot[idealWeapon] < gameLocal.time) ) { //HUMANHEAD bjk PATCH 7-27-06
|
|
if ( weaponCatchup ) {
|
|
assert( gameLocal.isClient );
|
|
#ifndef HUMANHEAD //HUMANHEAD rww FIXME!!!! this also produces horrible memory leaks because of the dirty fire controller stuff
|
|
//HUMANHEAD rww - our crazy weapon system does not work ok with this. did we change the weapon dictionary parsing?
|
|
//it seems that it is very much dependant on the base class of weapon at the moment and that of course is going to be
|
|
//that of currentWeapon and not idealWeapon.
|
|
//currentWeapon = idealWeapon;
|
|
//HUMANHEAD END
|
|
weaponGone = false;
|
|
animPrefix = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) );
|
|
weapon.GetEntity()->GetWeaponDef( animPrefix, inventory.clip[ currentWeapon ] );
|
|
animPrefix.Strip( "weaponobj_" ); //HUMANHEAD pdm: changed from weapon_ to weaponobj_ per our naming convention
|
|
|
|
weapon.GetEntity()->NetCatchup();
|
|
const function_t *newstate = GetScriptFunction( "NetCatchup" );
|
|
if ( newstate ) {
|
|
SetState( newstate );
|
|
UpdateScript();
|
|
}
|
|
#else
|
|
assert( idealWeapon >= 0 );
|
|
assert( idealWeapon < MAX_WEAPONS );
|
|
|
|
animPrefix = spawnArgs.GetString( va( "def_weapon%d", idealWeapon ) );
|
|
const char *currentWeaponName = weapon->spawnArgs.GetString("classname", "");
|
|
//make sure the weapon i have from my snapshot, and the weapon i have selected are the same.
|
|
if (currentWeaponName && !idStr::Cmp(animPrefix, currentWeaponName)) {
|
|
weaponGone = false;
|
|
currentWeapon = idealWeapon;
|
|
|
|
weapon->GetWeaponDef( animPrefix, inventory.clip[ currentWeapon ] );
|
|
animPrefix.Strip( "weaponobj_" );
|
|
weapon->Raise();
|
|
#endif //HUMANHEAD END
|
|
weaponCatchup = false;
|
|
}
|
|
} else {
|
|
if ( weapon.IsValid() && (weapon->IsReady() || weapon->IsRising()) ) {
|
|
InvalidateCurrentWeapon();//Needed incase we change weapons quickly, we can go back to old weapon
|
|
weapon->PutAway();
|
|
}
|
|
|
|
if ( ( !weapon.IsValid() || weapon->IsHolstered() ) && !bDeathWalk && ! bReallyDead ) {
|
|
assert( idealWeapon >= 0 );
|
|
assert( idealWeapon < MAX_WEAPONS );
|
|
|
|
if ( currentWeapon > 0 && !spawnArgs.GetBool( va( "weapon%d_toggle", currentWeapon ) ) ) { //HUMANHEAD bjk
|
|
previousWeapon = currentWeapon;
|
|
}
|
|
//HUMANHEAD PCF rww 05/03/06 - the local client might get skippiness between switching weapons if we
|
|
//attempt to raise the weapon again before validating that the weapon ent is of the type that we
|
|
//already desire to switch to. this bug is introduced by the concept of switching out entities when
|
|
//changing weapons (not client-friendly).
|
|
if (gameLocal.isClient && entityNumber == gameLocal.localClientNum) {
|
|
if (weapon.IsValid() && weapon->GetDict() && idStr::Icmp(weapon->GetDict()->GetString("classname"), GetWeaponName( idealWeapon )) == 0) {
|
|
currentWeapon = idealWeapon;
|
|
weaponGone = false;
|
|
animPrefix = GetWeaponName( currentWeapon );
|
|
animPrefix.Strip( "weaponobj_" ); //HUMANHEAD pdm: changed from weapon_ to weaponobj_ per our naming convention
|
|
weapon.GetEntity()->Raise();
|
|
}
|
|
}
|
|
//HUMANHEAD END
|
|
else {
|
|
currentWeapon = idealWeapon;
|
|
weaponGone = false;
|
|
|
|
//HUMANHEAD: aob
|
|
animPrefix = GetWeaponName( currentWeapon );
|
|
if (!gameLocal.isClient) {
|
|
SAFE_REMOVE( weapon );
|
|
weapon = SpawnWeapon( animPrefix.c_str() );
|
|
}
|
|
//HUMANHEAD END
|
|
|
|
animPrefix.Strip( "weaponobj_" ); //HUMANHEAD pdm: changed from weapon_ to weaponobj_ per our naming convention
|
|
|
|
//HUMANHEAD PCF rww 05/03/06 - safety check, make sure weapon is valid particularly for the client
|
|
if (weapon.IsValid()) {
|
|
weapon.GetEntity()->Raise();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if (!bDeathWalk && !bReallyDead) {
|
|
weaponGone = false; // if you drop and re-get weap, you may miss the = false above
|
|
if ( weapon.IsValid() && weapon.GetEntity()->IsHolstered() ) {
|
|
if ( !weapon.GetEntity()->AmmoAvailable() ) {
|
|
// weapons can switch automatically if they have no more ammo
|
|
NextBestWeapon();
|
|
} else if( !gameLocal.isMultiplayer || inventory.lastShot[idealWeapon] < gameLocal.time ) { //HUMANHEAD bjk PATCH 9-11-06
|
|
weapon.GetEntity()->Raise();
|
|
state = GetScriptFunction( "RaiseWeapon" );
|
|
if ( state ) {
|
|
SetState( state );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( weapon.IsValid() ) {
|
|
weapon->PrecomputeTraceInfo();
|
|
}
|
|
|
|
// check for attack
|
|
AI_WEAPON_FIRED = false;
|
|
if ( ( usercmd.buttons & BUTTON_ATTACK ) && !weaponGone ) {
|
|
FireWeapon();
|
|
} else if ( oldButtons & BUTTON_ATTACK ) {
|
|
AI_ATTACK_HELD = false;
|
|
if( weapon.IsValid() ) {
|
|
weapon.GetEntity()->EndAttack();
|
|
}
|
|
}
|
|
|
|
// HUMANHEAD
|
|
if ( ( usercmd.buttons & BUTTON_ATTACK_ALT ) && !weaponGone ) {
|
|
FireWeaponAlt();
|
|
} else if ( oldButtons & BUTTON_ATTACK_ALT ) {
|
|
AI_ATTACK_HELD = false;
|
|
if( weapon.IsValid() ) {
|
|
weapon.GetEntity()->EndAltAttack();
|
|
}
|
|
}
|
|
// HUMANHEAD END
|
|
|
|
// update our ammo clip in our inventory
|
|
if ( weapon.IsValid() && ( currentWeapon >= 0 ) && ( currentWeapon < MAX_WEAPONS ) ) {
|
|
inventory.clip[ currentWeapon ] = weapon->AmmoInClip();
|
|
inventory.altMode[ currentWeapon ] = weapon->GetAltMode();
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::SpawnWeapon
|
|
===============
|
|
*/
|
|
hhWeapon* hhPlayer::SpawnWeapon( const char* name ) {
|
|
hhWeapon* weaponPtr = NULL;
|
|
idDict args;
|
|
|
|
args.SetVector( "origin", GetEyePosition() );
|
|
args.SetMatrix( "rotation", viewAngles.ToMat3() );
|
|
weaponPtr = static_cast<hhWeapon*>( gameLocal.SpawnObject(name, &args) );
|
|
weaponPtr->SetOwner( this );
|
|
weaponPtr->Bind( this, true );
|
|
|
|
//HUMANHEAD: aob - removed last two params
|
|
weaponPtr->GetWeaponDef( name );
|
|
|
|
return weaponPtr;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
hhPlayer::GetWeaponNum
|
|
HUMANHEAD
|
|
=====================
|
|
*/
|
|
int hhPlayer::GetWeaponNum( const char* weaponName ) const {
|
|
for( int i = 1; i < MAX_WEAPONS; ++i ) {
|
|
if( !idStr::Icmp(GetWeaponName(i), weaponName) ) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
hhPlayer::GetWeaponName
|
|
HUMANHEAD
|
|
=====================
|
|
*/
|
|
const char* hhPlayer::GetWeaponName( int num ) const {
|
|
if( num < 1 || num >= MAX_WEAPONS ) {
|
|
return NULL;
|
|
}
|
|
|
|
return spawnArgs.GetString( va("def_weapon%d", num) );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::Weapon_GUI
|
|
PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge
|
|
===============
|
|
*/
|
|
void hhPlayer::Weapon_GUI( void ) {
|
|
|
|
StopFiring();
|
|
weapon.GetEntity()->LowerWeapon();
|
|
|
|
// NLANOTE - Same here
|
|
// disable click prediction for the GUIs. handy to check the state sync does the right thing
|
|
if ( gameLocal.isClient && !net_clientPredictGUI.GetBool() ) {
|
|
return;
|
|
}
|
|
|
|
if ( health <= 0 || bDeathWalk ) { //HUMANHEAD bjk PCF (5-5-06) - no hand stuff when dead
|
|
return;
|
|
}
|
|
|
|
// HUMANHEAD nla
|
|
if ( !bDeathWalk ) { // mdl - Don't do hand stuff while deathwalking
|
|
if ( hand.IsValid() && hand->IsType( hhGuiHand::Type ) ) {
|
|
//if ( !hand->IsReady() && !hand->IsRaising() ) {
|
|
if ( !hand->IsReady() && !hand->IsRaising() && !hand->IsLowering() ) {
|
|
hand->Reraise();
|
|
}
|
|
}
|
|
else if ( (!hand.IsValid() && weapon.IsValid() && !weapon->IsAside() ) ||
|
|
(hand.IsValid() && !hand->IsType( hhGuiHand::Type ) && !hand->IsLowering() ) ||
|
|
(!hand.IsValid() && idealWeapon == 0 ) ) {
|
|
if (!gameLocal.isClient) { //rww
|
|
hhHand::AddHand( this, GetGuiHandInfo() );
|
|
}
|
|
}
|
|
}
|
|
// HUMANHEAD END
|
|
|
|
if ( hand.IsValid() && ( oldButtons ^ usercmd.buttons ) & BUTTON_ATTACK ) { //HUMANHEAD bjk: no waiting for ready
|
|
sysEvent_t ev;
|
|
const char *command = NULL;
|
|
bool updateVisuals = false;
|
|
|
|
idUserInterface *ui = ActiveGui();
|
|
if ( ui ) {
|
|
ev = sys->GenerateMouseButtonEvent( 1, ( usercmd.buttons & BUTTON_ATTACK ) != 0 );
|
|
command = ui->HandleEvent( &ev, gameLocal.time, &updateVisuals );
|
|
if ( updateVisuals && focusGUIent && ui == focusUI ) {
|
|
focusGUIent->UpdateVisuals();
|
|
}
|
|
}
|
|
if ( gameLocal.isClient ) {
|
|
// we predict enough, but don't want to execute commands
|
|
if (focusGUIent) {
|
|
if ( hand.IsValid() && hand->IsType( hhGuiHand::Type ) && ev.evValue2) { //rww - still predict hand
|
|
hand->SetAction( focusGUIent->spawnArgs.GetString( "pressAnim", "press" ) ); //HUMANHEAD bjk
|
|
hand->Action();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
if ( focusGUIent ) {
|
|
HandleGuiCommands( focusGUIent, command );
|
|
// HUMANHEAD nla - Added to handle the hand stuff. = )
|
|
if ( hand.IsValid() && hand->IsType( hhGuiHand::Type ) && ev.evValue2) {
|
|
hand->SetAction( focusGUIent->spawnArgs.GetString( "pressAnim", "press" ) ); //HUMANHEAD bjk
|
|
hand->Action();
|
|
}
|
|
// HUMANHEAD END
|
|
} else {
|
|
HandleGuiCommands( this, command );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
hhPlayer::UpdateCrosshairs
|
|
HUMANHEAD PDM
|
|
================
|
|
*/
|
|
void hhPlayer::UpdateCrosshairs() {
|
|
bool combatCrosshair = false;
|
|
bool targeting = false;
|
|
int crosshair = 0;
|
|
|
|
if ( !privateCameraView && !IsLocked(idealWeapon) && (!hand.IsValid() || hand->IsLowered()) && !InCinematic() && weapon.IsValid() && g_crosshair.GetInteger() ) {
|
|
weapon->UpdateCrosshairs(combatCrosshair, targeting);
|
|
crosshair = g_crosshair.GetInteger();
|
|
}
|
|
|
|
cursor->SetStateBool( "combatcursor", targeting ? false : combatCrosshair );
|
|
cursor->SetStateBool( "activecombatcursor", targeting );
|
|
cursor->SetStateInt( "crosshair", crosshair );
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
hhPlayer::UpdateFocus
|
|
|
|
Searches nearby entities for interactive guis, possibly making one of them
|
|
the focus and sending it a mouse move event
|
|
PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge
|
|
================
|
|
*/
|
|
void hhPlayer::UpdateFocus( void ) {
|
|
idClipModel *clipModelList[ MAX_GENTITIES ];
|
|
idClipModel *clip;
|
|
int listedClipModels;
|
|
idEntity *oldFocus;
|
|
idEntity *ent;
|
|
idUserInterface *oldUI;
|
|
int i, j;
|
|
idVec3 start, end;
|
|
const char *command;
|
|
trace_t trace;
|
|
guiPoint_t pt;
|
|
const idKeyValue *kv;
|
|
sysEvent_t ev;
|
|
idUserInterface *ui;
|
|
|
|
if ( gameLocal.inCinematic ) {
|
|
return;
|
|
}
|
|
|
|
oldFocus = focusGUIent;
|
|
oldUI = focusUI;
|
|
|
|
if ( focusTime <= gameLocal.time ) {
|
|
ClearFocus();
|
|
}
|
|
|
|
// don't let spectators interact with GUIs
|
|
if ( spectating ) {
|
|
return;
|
|
}
|
|
|
|
start = GetEyePosition();
|
|
|
|
//HUMANHEAD rww - the actual viewAngles do not seem to be properly transformed to at this point,
|
|
//so just get a transformed direction now.
|
|
idVec3 viewDir = (untransformedViewAngles.ToMat3()*GetEyeAxis())[0];
|
|
|
|
end = start + viewDir * 70.0f; // was 50, doom=80
|
|
|
|
// HUMANHEAD pdm: Changed aim logic a bit
|
|
// player identification -> names to the hud
|
|
if ( gameLocal.isMultiplayer && entityNumber == gameLocal.localClientNum ) {
|
|
idVec3 end = start + viewDir * 768.0f;
|
|
gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_BOUNDINGBOX, this );
|
|
if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum < MAX_CLIENTS ) ) {
|
|
MPAim = trace.c.entityNum;
|
|
}
|
|
else {
|
|
MPAim = -1;
|
|
|
|
//HUMANHEAD rww - if we're hitting a shuttle with a pilot, set the pilot to the MPAim ent
|
|
if (trace.c.entityNum >= MAX_CLIENTS && trace.c.entityNum < MAX_GENTITIES) {
|
|
idEntity *traceEnt = gameLocal.entities[trace.c.entityNum];
|
|
if (traceEnt && traceEnt->IsType(hhVehicle::Type)) { //is it a vehicle?
|
|
hhVehicle *traceVeh = static_cast<hhVehicle *>(traceEnt);
|
|
if (traceVeh->GetPilot() && traceVeh->GetPilot()->IsType(hhPlayer::Type)) { //if it's a vehicle, does it have a player pilot?
|
|
MPAim = traceVeh->GetPilot()->entityNumber;
|
|
}
|
|
}
|
|
}
|
|
//HUMANHEAD END
|
|
}
|
|
}
|
|
|
|
idBounds bounds( start );
|
|
bounds.AddPoint( end );
|
|
|
|
listedClipModels = gameLocal.clip.ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES );
|
|
|
|
// no pretense at sorting here, just assume that there will only be one active
|
|
// gui within range along the trace
|
|
for ( i = 0; i < listedClipModels; i++ ) {
|
|
clip = clipModelList[ i ];
|
|
ent = clip->GetEntity();
|
|
|
|
if ( ent->IsHidden() ) {
|
|
continue;
|
|
}
|
|
|
|
// HUMANHEAD pdm: added support here for all guis, not just gui1
|
|
int interactiveMask = 0;
|
|
renderEntity_t *renderEnt = ent->GetRenderEntity();
|
|
if ( renderEnt ) {
|
|
for (int ix=0; ix<MAX_RENDERENTITY_GUI; ix++) {
|
|
if (renderEnt->gui[ix] && renderEnt->gui[ix]->IsInteractive()) {
|
|
interactiveMask |= (1<<ix);
|
|
}
|
|
}
|
|
}
|
|
if (!interactiveMask) {
|
|
continue;
|
|
}
|
|
|
|
pt = gameRenderWorld->GuiTrace( ent->GetModelDefHandle(), start, end/*, interactiveMask*/); // jmarshall implement interactiveMask
|
|
|
|
if ( ent->fl.accurateGuiTrace ) {
|
|
trace_t tr;
|
|
gameLocal.clip.TracePoint(tr, start, end, CONTENTS_SOLID, this);
|
|
if (tr.fraction < pt.frac) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( pt.x != -1 ) {
|
|
// we have a hit
|
|
renderEntity_t *focusGUIrenderEntity = ent->GetRenderEntity();
|
|
if ( !focusGUIrenderEntity ) {
|
|
continue;
|
|
}
|
|
|
|
if ( pt.guiId == 1 ) {
|
|
ui = focusGUIrenderEntity->gui[ 0 ];
|
|
} else if ( pt.guiId == 2 ) {
|
|
ui = focusGUIrenderEntity->gui[ 1 ];
|
|
} else {
|
|
ui = focusGUIrenderEntity->gui[ 2 ];
|
|
}
|
|
|
|
if ( ui == NULL ) {
|
|
continue;
|
|
}
|
|
|
|
ClearFocus();
|
|
focusGUIent = ent;
|
|
focusUI = ui;
|
|
|
|
if ( oldFocus != ent ) {
|
|
// new activation
|
|
// going to see if we have anything in inventory a gui might be interested in
|
|
// need to enumerate inventory items
|
|
focusUI->SetStateInt( "inv_count", inventory.items.Num() );
|
|
for ( j = 0; j < inventory.items.Num(); j++ ) {
|
|
idDict *item = inventory.items[ j ];
|
|
const char *iname = item->GetString( "inv_name" );
|
|
const char *iicon = item->GetString( "inv_icon" );
|
|
const char *itext = item->GetString( "inv_text" );
|
|
|
|
focusUI->SetStateString( va( "inv_name_%i", j), iname );
|
|
focusUI->SetStateString( va( "inv_icon_%i", j), iicon );
|
|
focusUI->SetStateString( va( "inv_text_%i", j), itext );
|
|
kv = item->MatchPrefix("inv_id", NULL);
|
|
if ( kv ) {
|
|
focusUI->SetStateString( va( "inv_id_%i", j ), kv->GetValue() );
|
|
}
|
|
// HUMANHEAD nla - Changed to pass all "passtogui_" keys to the gui
|
|
kv = item->MatchPrefix("passtogui_", NULL);
|
|
if ( kv ) {
|
|
focusUI->SetStateString( kv->GetKey(), kv->GetValue() );
|
|
kv = item->MatchPrefix( "passtogui_", kv );
|
|
}
|
|
// HUMANHEAD END
|
|
focusUI->SetStateInt( iname, 1 );
|
|
}
|
|
|
|
focusUI->SetStateString( "player_health", va("%i", health ) );
|
|
focusUI->SetStateBool( "player_spiritwalking", IsSpiritWalking() ); // for hunterhand gui
|
|
|
|
kv = focusGUIent->spawnArgs.MatchPrefix( "gui_parm", NULL );
|
|
while ( kv ) {
|
|
focusUI->SetStateString( kv->GetKey(), kv->GetValue() );
|
|
kv = focusGUIent->spawnArgs.MatchPrefix( "gui_parm", kv );
|
|
}
|
|
}
|
|
|
|
// clamp the mouse to the corner
|
|
ev = sys->GenerateMouseMoveEvent( -2000, -2000 );
|
|
command = focusUI->HandleEvent( &ev, gameLocal.time );
|
|
HandleGuiCommands( focusGUIent, command );
|
|
|
|
// move to an absolute position
|
|
ev = sys->GenerateMouseMoveEvent( pt.x * SCREEN_WIDTH, pt.y * SCREEN_HEIGHT );
|
|
command = focusUI->HandleEvent( &ev, gameLocal.time );
|
|
HandleGuiCommands( focusGUIent, command );
|
|
focusTime = gameLocal.time + FOCUS_GUI_TIME;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( focusGUIent && focusUI ) {
|
|
if ( !oldFocus || oldFocus != focusGUIent ) {
|
|
command = focusUI->Activate( true, gameLocal.time );
|
|
HandleGuiCommands( focusGUIent, command );
|
|
//StartSound( "snd_guienter", SND_CHANNEL_ANY, 0, false, NULL );
|
|
}
|
|
} else if ( oldFocus && oldUI ) {
|
|
command = oldUI->Activate( false, gameLocal.time );
|
|
HandleGuiCommands( oldFocus, command );
|
|
//StartSound( "snd_guiexit", SND_CHANNEL_ANY, 0, false, NULL );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::ApplyLandDeflect
|
|
|
|
HUMANHEAD: aob
|
|
===============
|
|
*/
|
|
idVec3 hhPlayer::ApplyLandDeflect( const idVec3& pos, float scale ) {
|
|
idVec3 localPos( pos );
|
|
int delta = 0;
|
|
float fraction = 0.0f;
|
|
|
|
// add fall height
|
|
delta = gameLocal.time - landTime;
|
|
if ( delta < LAND_DEFLECT_TIME ) {
|
|
fraction = (float)delta / LAND_DEFLECT_TIME;
|
|
//HUMANHEAD: aob
|
|
fraction *= scale;
|
|
//HUMANHEAD END
|
|
localPos += cameraInterpolator.GetCurrentUpVector() * landChange * fraction;
|
|
} else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) {
|
|
fraction = (float)(LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME;
|
|
//HUMANHEAD: aob
|
|
fraction *= scale;
|
|
//HUMANHEAD END
|
|
localPos += cameraInterpolator.GetCurrentUpVector() * landChange * fraction;
|
|
}
|
|
|
|
return localPos;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
hhPlayer::CrashLand
|
|
|
|
Check for hard landings that generate sound events
|
|
PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge
|
|
=================
|
|
*/
|
|
void hhPlayer::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) {
|
|
//HUMANHEAD: aob - removed unused vars
|
|
idVec3 origin;
|
|
idVec3 gravityNormal;
|
|
float delta;
|
|
waterLevel_t waterLevel;
|
|
bool noDamage;
|
|
//HUMANHEAD END
|
|
|
|
//HUMANHEAD: aob
|
|
const trace_t& trace = physicsObj.GetGroundTrace();
|
|
//HUMANHEAD END
|
|
|
|
AI_SOFTLANDING = false;
|
|
AI_HARDLANDING = false;
|
|
|
|
//HUMANHEAD: aob - added IsBound and trace check
|
|
// if the player is not on the ground
|
|
if ( (!physicsObj.HasGroundContacts() || trace.fraction == 1.0f) && !IsBound() ) {
|
|
return;
|
|
}
|
|
|
|
//HUMANHEAD: aob - only check when we land on the ground
|
|
//If we get here we can assume we currently have ground contacts
|
|
if( physicsObj.HadGroundContacts() ) {
|
|
return;
|
|
}
|
|
//HUMANHEAD END
|
|
|
|
gravityNormal = physicsObj.GetGravityNormal();
|
|
|
|
// if the player wasn't going down
|
|
if ( ( oldVelocity * -gravityNormal ) >= 0.0f ) {
|
|
return;
|
|
}
|
|
|
|
waterLevel = physicsObj.GetWaterLevel();
|
|
|
|
// never take falling damage if completely underwater
|
|
if ( waterLevel == WATERLEVEL_HEAD ) {
|
|
return;
|
|
}
|
|
|
|
// no falling damage if touching a nodamage surface
|
|
noDamage = false;
|
|
for ( int i = 0; i < physicsObj.GetNumContacts(); i++ ) {
|
|
const contactInfo_t &contact = physicsObj.GetContact( i );
|
|
if ( contact.material->GetSurfaceFlags() & SURF_NODAMAGE ) {
|
|
noDamage = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//HUMANHEAD: aob - removed velocity calculations
|
|
|
|
//HUMANHEAD: aob
|
|
idVec3 deltaVelocity = DetermineDeltaCollisionVelocity( oldVelocity, trace );
|
|
delta = (IsBound()) ? deltaVelocity.Length() : deltaVelocity * gravityNormal;
|
|
//HUMANHEAD END
|
|
|
|
// reduce falling damage if there is standing water
|
|
if ( waterLevel == WATERLEVEL_WAIST ) {
|
|
delta *= 0.25f;
|
|
}
|
|
if ( waterLevel == WATERLEVEL_FEET ) {
|
|
delta *= 0.5f;
|
|
}
|
|
|
|
if ( delta < crashlandSpeed_jump || IsSpiritOrDeathwalking() ) {
|
|
return; // Early out
|
|
}
|
|
|
|
// Calculate landing sound volume
|
|
float soundScale = hhUtils::CalculateScale( delta, crashlandSpeed_jump, crashlandSpeed_fatal );
|
|
landChange = -32 * soundScale;
|
|
landTime = gameLocal.time;
|
|
float min = hhMath::dB2Scale( spawnArgs.GetInt("minCrashLandVolumedB", "-20") );
|
|
float max = hhMath::dB2Scale( spawnArgs.GetInt("maxCrashLandVolumedB", "-4") );
|
|
soundScale = min + (max - min) * soundScale;
|
|
|
|
idVec3 fallDir = oldVelocity;
|
|
idVec3 reverseContactNormal = -physicsObj.GetGroundContactNormal();
|
|
idEntity* entity = NULL;
|
|
float damageScale = hhUtils::CalculateScale( delta, crashlandSpeed_soft, crashlandSpeed_fatal );
|
|
|
|
fallDir.Normalize();
|
|
|
|
if( trace.fraction == 1.0f ) {
|
|
return;
|
|
}
|
|
|
|
if( !IsBound() ) {
|
|
PlayCrashLandSound( trace, soundScale );
|
|
gameLocal.AlertAI( this, spawnArgs.GetFloat( "land_alert_radius", "400" ) );
|
|
}
|
|
|
|
// Determine damage to what you're landing on
|
|
entity = gameLocal.GetTraceEntity( trace );
|
|
if( entity && trace.c.entityNum != ENTITYNUM_WORLD ) {
|
|
entity->ApplyImpulse( this, 0, trace.c.point, (oldVelocity * reverseContactNormal) * reverseContactNormal );//Not sure if this impulse is large enough
|
|
|
|
const char* entityDamageName = spawnArgs.GetString( "def_damageFellOnto" );
|
|
if( *entityDamageName && damageScale > 0.0f) {
|
|
entity->Damage( this, this, fallDir, entityDamageName, damageScale, INVALID_JOINT );
|
|
}
|
|
}
|
|
|
|
// Calculate damage to self
|
|
const char* selfDamageName = NULL;
|
|
if ( delta < crashlandSpeed_soft ) { // Soft Fall
|
|
AI_SOFTLANDING = true;
|
|
selfDamageName = spawnArgs.GetString( "def_damageSoftFall" );
|
|
}
|
|
else if ( delta < crashlandSpeed_fatal ) { // Hard Fall
|
|
AI_HARDLANDING = true;
|
|
selfDamageName = spawnArgs.GetString( "def_damageHardFall" );
|
|
}
|
|
else { // Fatal Fall
|
|
AI_HARDLANDING = true;
|
|
selfDamageName = spawnArgs.GetString( "def_damageFatalFall" );
|
|
}
|
|
|
|
if( *selfDamageName && damageScale > 0.0f && !noDamage ) {
|
|
pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim
|
|
Damage( NULL, NULL, fallDir, selfDamageName, damageScale, INVALID_JOINT );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::ApplyBobCycle
|
|
|
|
HUMANHEAD: aob
|
|
===============
|
|
*/
|
|
#define BOB_TO_NOBOB_RETURN_TIME 3000.0f
|
|
idVec3 hhPlayer::ApplyBobCycle( const idVec3& pos, const idVec3& velocity ) {
|
|
float delta = 0.0f;
|
|
idVec3 localViewBob( pos );
|
|
const float maxBob = 6.0f;
|
|
|
|
if( bob > 0.0f && !usercmd.forwardmove && !usercmd.rightmove ) {//smoothly goto bob of zero if not already there
|
|
delta = gameLocal.time - lastAppliedBobCycle;
|
|
bob *= ( 1.0f - (delta / BOB_TO_NOBOB_RETURN_TIME) );
|
|
if( bob < 0.0f ) {
|
|
bob = 0.0f;
|
|
}
|
|
} else {
|
|
// add bob height after any movement smoothing
|
|
lastAppliedBobCycle = gameLocal.time;
|
|
bob = bobfracsin * xyspeed * pm_bobup.GetFloat();
|
|
if( bob > maxBob ) {
|
|
bob = maxBob;
|
|
}
|
|
}
|
|
|
|
localViewBob -= cameraInterpolator.GetCurrentUpVector() * bob;
|
|
|
|
return localViewBob;
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::BobCycle
|
|
PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge
|
|
===============
|
|
*/
|
|
void hhPlayer::BobCycle( const idVec3 &pushVelocity ) {
|
|
float bobmove;
|
|
int old; //, deltaTime;
|
|
idVec3 vel, gravityDir, velocity;
|
|
idMat3 viewaxis;
|
|
// float bob;
|
|
float delta;
|
|
float speed;
|
|
// float f;
|
|
|
|
//
|
|
// calculate speed and cycle to be used for
|
|
// all cyclic walking effects
|
|
//
|
|
velocity = physicsObj.GetLinearVelocity() - pushVelocity;
|
|
|
|
//HUMANHEAD: aob - changed GetGravityNormal to GetCurrentUpVector to smooth out wallwalk
|
|
gravityDir = -cameraInterpolator.GetCurrentUpVector();
|
|
//HUMANHEAD END
|
|
vel = velocity - ( velocity * gravityDir ) * gravityDir;
|
|
xyspeed = vel.LengthFast();
|
|
|
|
// do not evaluate the bob for other clients
|
|
// when doing a spectate follow, don't do any weapon bobbing
|
|
if ( gameLocal.isClient && entityNumber != gameLocal.localClientNum ) {
|
|
//HUMANHEAD rww - allow bob when following players in spectator mode
|
|
bool canBob = false;
|
|
if (gameLocal.localClientNum != -1 && gameLocal.entities[gameLocal.localClientNum] && gameLocal.entities[gameLocal.localClientNum]->IsType(hhPlayer::Type)) {
|
|
hhPlayer *pl = static_cast<hhPlayer *>(gameLocal.entities[gameLocal.localClientNum]);
|
|
if (pl->spectating && pl->spectator == entityNumber) {
|
|
canBob = true;
|
|
}
|
|
}
|
|
|
|
if (!canBob) {
|
|
//HUMANHEAD END
|
|
viewBobAngles.Zero();
|
|
viewBob.Zero();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( !physicsObj.HasGroundContacts() || influenceActive == INFLUENCE_LEVEL2 || ( gameLocal.isMultiplayer && spectating ) ) {
|
|
// airborne
|
|
bobCycle = 0;
|
|
bobFoot = 0;
|
|
bobfracsin = 0;
|
|
} else if ( ( !usercmd.forwardmove && !usercmd.rightmove ) || ( xyspeed <= MIN_BOB_SPEED ) ) {
|
|
// start at beginning of cycle again
|
|
bobCycle = 0;
|
|
bobFoot = 0;
|
|
bobfracsin = 0;
|
|
} else {
|
|
if ( physicsObj.IsCrouching() ) {
|
|
bobmove = pm_crouchbob.GetFloat();
|
|
// ducked characters never play footsteps
|
|
} else {
|
|
// vary the bobbing based on the speed of the player
|
|
bobmove = pm_walkbob.GetFloat() * ( 1.0f - bobFrac ) + pm_runbob.GetFloat() * bobFrac;
|
|
}
|
|
|
|
// check for footstep / splash sounds
|
|
old = bobCycle;
|
|
bobCycle = (int)( old + bobmove * gameLocal.msec ) & 255;
|
|
bobFoot = ( bobCycle & 128 ) >> 7;
|
|
bobfracsin = idMath::Fabs( sin( ( bobCycle & 127 ) / 127.0 * idMath::PI ) );
|
|
}
|
|
|
|
// calculate angles for view bobbing
|
|
viewBobAngles.Zero();
|
|
|
|
viewaxis = viewAngles.ToMat3();
|
|
|
|
// add angles based on velocity
|
|
delta = velocity * viewaxis[0];
|
|
viewBobAngles.pitch += delta * pm_runpitch.GetFloat();
|
|
|
|
delta = velocity * viewaxis[1];
|
|
viewBobAngles.roll -= delta * pm_runroll.GetFloat();
|
|
|
|
// add angles based on bob
|
|
// make sure the bob is visible even at low speeds
|
|
speed = xyspeed > 200 ? xyspeed : 200;
|
|
|
|
delta = bobfracsin * pm_bobpitch.GetFloat() * speed;
|
|
if ( physicsObj.IsCrouching() ) {
|
|
delta *= 3; // crouching
|
|
}
|
|
viewBobAngles.pitch += delta;
|
|
delta = bobfracsin * pm_bobroll.GetFloat() * speed;
|
|
if ( physicsObj.IsCrouching() ) {
|
|
delta *= 3; // crouching accentuates roll
|
|
}
|
|
if ( bobFoot & 1 ) {
|
|
delta = -delta;
|
|
}
|
|
viewBobAngles.roll += delta;
|
|
|
|
//HUMANHEAD: aob
|
|
#if HUMANHEAD
|
|
viewBob = ApplyBobCycle( ApplyLandDeflect(vec3_zero, 1.0f), velocity );
|
|
#else
|
|
// calculate position for view bobbing
|
|
viewBob.Zero();
|
|
|
|
if ( physicsObj.HasSteppedUp() ) {
|
|
|
|
// check for stepping up before a previous step is completed
|
|
deltaTime = gameLocal.time - stepUpTime;
|
|
if ( deltaTime < STEPUP_TIME ) {
|
|
stepUpDelta = stepUpDelta * ( STEPUP_TIME - deltaTime ) / STEPUP_TIME + physicsObj.GetStepUp();
|
|
} else {
|
|
stepUpDelta = physicsObj.GetStepUp();
|
|
}
|
|
if ( stepUpDelta > 2.0f * pm_stepsize.GetFloat() ) {
|
|
stepUpDelta = 2.0f * pm_stepsize.GetFloat();
|
|
}
|
|
stepUpTime = gameLocal.time;
|
|
}
|
|
|
|
idVec3 gravity = physicsObj.GetGravityNormal();
|
|
|
|
// if the player stepped up recently
|
|
deltaTime = gameLocal.time - stepUpTime;
|
|
if ( deltaTime < STEPUP_TIME ) {
|
|
viewBob += gravity * ( stepUpDelta * ( STEPUP_TIME - deltaTime ) / STEPUP_TIME );
|
|
}
|
|
|
|
// add bob height after any movement smoothing
|
|
bob = bobfracsin * xyspeed * pm_bobup.GetFloat();
|
|
if ( bob > 6 ) {
|
|
bob = 6;
|
|
}
|
|
viewBob[2] += bob;
|
|
|
|
// add fall height
|
|
delta = gameLocal.time - landTime;
|
|
if ( delta < LAND_DEFLECT_TIME ) {
|
|
f = delta / LAND_DEFLECT_TIME;
|
|
viewBob -= gravity * ( landChange * f );
|
|
} else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) {
|
|
delta -= LAND_DEFLECT_TIME;
|
|
f = 1.0 - ( delta / LAND_RETURN_TIME );
|
|
viewBob -= gravity * ( landChange * f );
|
|
}
|
|
#endif //HUMANHEAD END
|
|
}
|
|
|
|
/*
|
|
================
|
|
hhPlayer::UpdateDeltaViewAngles
|
|
================
|
|
*/
|
|
void hhPlayer::UpdateDeltaViewAngles( const idAngles &angles ) {
|
|
// set the delta angle
|
|
idAngles delta;
|
|
for( int i = 0; i < 3; i++ ) {
|
|
#if 1 //rww - revert to id's method
|
|
//delta[i] = ((angles[i] - GetUntransformedViewAngles()[i]) / GetViewAnglesSensitivity()) + GetUntransformedViewAngles()[i] - SHORT2ANGLE(usercmd.angles[i]);
|
|
delta[ i ] = angles[ i ] - SHORT2ANGLE( usercmd.angles[ i ] );
|
|
#else // HUMANHEAD mdl: If you enable this, comment out the SetOrientation() call for vehicles in hhPlayer::Restore()
|
|
delta[i] = (angles[i] - idMath::AngleNormalize180( SHORT2ANGLE(usercmd.angles[ i ]) - oldCmdAngles[i]) * GetViewAnglesSensitivity());
|
|
#endif
|
|
}
|
|
SetDeltaViewAngles( delta );
|
|
}
|
|
|
|
/*
|
|
================
|
|
hhPlayer::DetermineViewAngles
|
|
================
|
|
*/
|
|
idAngles hhPlayer::DetermineViewAngles( const usercmd_t& cmd, idAngles& cmdAngles ) {
|
|
int i;
|
|
idAngles localViewAngles( GetUntransformedViewAngles() );
|
|
idAngles deltaCmdAngles;
|
|
|
|
if ( !noclip && ( gameLocal.inCinematic || privateCameraView || gameLocal.GetCamera() || influenceActive == INFLUENCE_LEVEL2 ) ) {
|
|
// no view changes at all, but we still want to update the deltas or else when
|
|
// we get out of this mode, our view will snap to a kind of random angle
|
|
return GetUntransformedViewAngles();
|
|
}
|
|
|
|
//HUMANHEAD rww - testing
|
|
/*
|
|
if (gameLoacl.isClient && gameLocal.localClientNum != entityNumber) {
|
|
idQuat a = viewAngles.ToQuat();
|
|
idQuat b = GetUntransformedViewAngles().ToQuat();
|
|
idQuat c;
|
|
c.Slerp(a, b, 0.3f);
|
|
return c.ToAngles();
|
|
}
|
|
*/
|
|
//HUMANHEAD END
|
|
|
|
// if dead
|
|
if ( IsDead() ) {
|
|
#if 0
|
|
if ( DoThirdPersonDeath() ) {
|
|
localViewAngles.roll = 0.0f;
|
|
localViewAngles.pitch = 30.0f;
|
|
}
|
|
else {
|
|
localViewAngles.roll = 40.0f;
|
|
localViewAngles.pitch = -15.0f;
|
|
}
|
|
return localViewAngles;
|
|
#else //HUMANHEAD PCF rww 04/26/06 - look freely while dead in MP
|
|
if ( !DoThirdPersonDeath() ) {
|
|
localViewAngles.roll = 40.0f;
|
|
localViewAngles.pitch = -15.0f;
|
|
return localViewAngles;
|
|
}
|
|
#endif //HUMANHEAD END
|
|
}
|
|
|
|
//JSHTODO this messes up multiplayer input. remerge sensitivity code
|
|
// circularly clamp the angles with deltas
|
|
if ( usercmd.buttons & BUTTON_MLOOK ) {
|
|
for ( i = 0; i < 3; i++ ) {
|
|
cmdAngles[i] = SHORT2ANGLE( usercmd.angles[i] );
|
|
#if 1 //rww - revert to id's method
|
|
//localViewAngles[i] = idMath::AngleNormalize180( cmdAngles[i] + deltaViewAngles[i] - GetUntransformedViewAngles()[i] ) * GetViewAnglesSensitivity() + GetUntransformedViewAngles()[i];
|
|
localViewAngles[i] = idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[i]) + deltaViewAngles[i] );
|
|
#else
|
|
deltaCmdAngles[i] = idMath::AngleNormalize180( cmdAngles[i] - oldCmdAngles[i] ) * GetViewAnglesSensitivity();
|
|
oldCmdAngles[i] = cmdAngles[i];
|
|
localViewAngles[i] = deltaCmdAngles[i] + deltaViewAngles[i];
|
|
#endif
|
|
}
|
|
} else {
|
|
#if 1 //rww - revert to id's method
|
|
//localViewAngles.yaw = idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[YAW] ) + deltaViewAngles[YAW] - GetUntransformedViewAngles()[YAW] ) * GetViewAnglesSensitivity() + GetUntransformedViewAngles()[YAW];
|
|
localViewAngles.yaw = idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[YAW]) + deltaViewAngles[YAW] );
|
|
#if GAMEPAD_SUPPORT // VENOM BEGIN
|
|
oldCmdAngles[YAW] = SHORT2ANGLE( usercmd.angles[YAW] );
|
|
#endif // VENOM END
|
|
#else
|
|
deltaCmdAngles[YAW] = idMath::AngleNormalize180( SHORT2ANGLE(usercmd.angles[YAW]) - oldCmdAngles[YAW] ) * GetViewAnglesSensitivity();
|
|
oldCmdAngles[YAW] = SHORT2ANGLE( usercmd.angles[YAW] );
|
|
localViewAngles.yaw = deltaCmdAngles[YAW] + deltaViewAngles[YAW];
|
|
#endif
|
|
|
|
if (!centerView.IsDone(gameLocal.time)) {
|
|
localViewAngles.pitch = centerView.GetCurrentValue(gameLocal.time);
|
|
}
|
|
}
|
|
|
|
#if GAMEPAD_SUPPORT // VENOM BEGIN
|
|
float fpitch = idMath::Fabs(localViewAngles.pitch);
|
|
if( idMath::Abs(usercmd.rightmove) < 10 &&
|
|
idMath::Abs(usercmd.forwardmove) > 80 &&
|
|
fpitch > 1.f &&
|
|
idMath::Fabs(oldCmdAngles[YAW] - cmdAngles[YAW]) < 1.0f &&
|
|
idMath::Fabs(oldCmdAngles[PITCH] - cmdAngles[PITCH]) < 1.0f
|
|
)
|
|
{
|
|
// dont start the leveling until we have .5 sec of valid movement
|
|
if((gameLocal.time - lastAutoLevelTime) > 500 ) {
|
|
float fadd = 0.6f;
|
|
if(fpitch < 10.f) {
|
|
fadd *= (fpitch/10.f );
|
|
fadd+= 0.05f;
|
|
}
|
|
|
|
if(localViewAngles.pitch < 0 ) {
|
|
localViewAngles.pitch +=fadd;
|
|
}
|
|
else {
|
|
localViewAngles.pitch -=fadd;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
lastAutoLevelTime = gameLocal.time;
|
|
}
|
|
|
|
oldCmdAngles[YAW] = cmdAngles[YAW];
|
|
oldCmdAngles[PITCH] = cmdAngles[PITCH];
|
|
#endif // VENOM END
|
|
|
|
// clamp the pitch
|
|
if ( noclip ) {
|
|
localViewAngles.pitch = hhMath::ClampFloat( -89.0f, 89.0f, localViewAngles.pitch );
|
|
} else {
|
|
//HUMANHEAD PCF rww 04/26/06 - look freely while dead in MP
|
|
if (IsDead() && DoThirdPersonDeath()) {
|
|
localViewAngles.pitch = hhMath::ClampFloat( -45.0f, 45.0f, localViewAngles.pitch );
|
|
}
|
|
else {
|
|
//HUMANHEAD END
|
|
localViewAngles.pitch = hhMath::ClampFloat( pm_minviewpitch.GetFloat(), pm_maxviewpitch.GetFloat(), localViewAngles.pitch );
|
|
}
|
|
}
|
|
|
|
// HUMANHEAD pdm: clamp the yaw (only used for bindControllers)
|
|
if ( !noclip && bClampYaw ) {
|
|
float idealYaw=0.0f;
|
|
idVec3 masterOrigin;
|
|
idMat3 masterAxis;
|
|
|
|
if (GetMasterPosition(masterOrigin, masterAxis)) {
|
|
idealYaw = masterAxis[0].ToYaw();
|
|
idealYaw = idMath::AngleNormalize180( idealYaw );
|
|
}
|
|
|
|
// Transpose the everything to "zero" space before clamping, so idealYaw is at zero in a (-180..180) normalized space
|
|
float zeroSpaceYaw = idMath::AngleNormalize180( localViewAngles.yaw - idealYaw );
|
|
zeroSpaceYaw = idMath::ClampFloat(-maxRelativeYaw, maxRelativeYaw, zeroSpaceYaw);
|
|
localViewAngles.yaw = idMath::AngleNormalize180( zeroSpaceYaw + idealYaw );
|
|
}
|
|
// HUMANHEAD END
|
|
|
|
UpdateDeltaViewAngles( localViewAngles );
|
|
|
|
// save in the log for analyzing weapon angle offsets
|
|
loggedViewAngles[ gameLocal.framenum & (NUM_LOGGED_VIEW_ANGLES-1) ] = localViewAngles;
|
|
|
|
return localViewAngles;
|
|
}
|
|
|
|
/*
|
|
================
|
|
hhPlayer::SetViewAnglesSensitivity
|
|
================
|
|
*/
|
|
void hhPlayer::SetViewAnglesSensitivity( float factor ) {
|
|
viewAnglesSensitivity = factor;
|
|
if (gameLocal.localClientNum == entityNumber) {
|
|
//common->SetGameSensitivityFactor(factor); // jmarshall
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
hhPlayer::GetViewAnglesSensitivity
|
|
================
|
|
*/
|
|
float hhPlayer::GetViewAnglesSensitivity() const {
|
|
return viewAnglesSensitivity;
|
|
}
|
|
|
|
/*
|
|
================
|
|
hhPlayer::UpdateViewAngles
|
|
================
|
|
*/
|
|
void hhPlayer::UpdateViewAngles( void ) {
|
|
if( InVehicle() ) {
|
|
return;
|
|
}
|
|
|
|
if (!noclip && (InCinematic() && lockView)) {
|
|
// no view changes at all, but we still want to update the deltas or else when
|
|
// we get out of this mode, our view will snap to a kind of random angle
|
|
UpdateDeltaViewAngles( viewAngles );
|
|
return;
|
|
}
|
|
|
|
//HUMANHEAD: aob - moved logic into helper function so we can call this somewhere else while in a vehicle
|
|
viewAngles = DetermineViewAngles( usercmd, cmdAngles );
|
|
|
|
//HUMANHEAD rww - moved angle smoothing code here
|
|
// update the smoothed view angles
|
|
if (gameLocal.isClient) {
|
|
if ( gameLocal.framenum >= smoothedFrame && entityNumber != gameLocal.localClientNum ) {
|
|
idAngles anglesDiff = viewAngles - smoothedAngles;
|
|
anglesDiff.Normalize180();
|
|
if ( idMath::Fabs( anglesDiff.yaw ) < 90.0f && idMath::Fabs( anglesDiff.pitch ) < 90.0f ) {
|
|
// smoothen by pushing back to the previous angles
|
|
viewAngles -= gameLocal.clientSmoothing * anglesDiff;
|
|
viewAngles.Normalize180();
|
|
}
|
|
smoothedAngles = viewAngles;
|
|
}
|
|
smoothedOriginUpdated = false;
|
|
}
|
|
|
|
// orient the model towards the direction we're looking
|
|
UpdateOrientation( viewAngles );
|
|
//HUMANHEAD END
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
hhPlayer::UpdateOrientation
|
|
|
|
HUMANHEAD: aob
|
|
=====================
|
|
*/
|
|
void hhPlayer::UpdateOrientation( const idAngles& newUntransformedViewAngles ) {
|
|
idAngles angles;
|
|
//This is where the camera interpolator transforms the viewAngles to work with wallwalk.
|
|
//We also store the original untransformed view angles for use in some other code.
|
|
//idPlayer called SetAxis, but only used the yaw but because we are transforming our view angles
|
|
//we do something similier by taking the untransformed yaw and rotating by the camera interpolator's axis.
|
|
//This becomes our new axis, the camera interpolator axis plus the untransformed view angles yaw
|
|
SetUntransformedViewAngles( newUntransformedViewAngles );
|
|
|
|
angles = GetUntransformedViewAngles();
|
|
if( IsWallWalking() ) {
|
|
angles.pitch = 0.0f;
|
|
}
|
|
SetUntransformedViewAxis( idAngles( 0.0f, angles.yaw, 0.0f ).ToMat3() );
|
|
|
|
viewAngles = cameraInterpolator.UpdateViewAngles( angles );
|
|
SetAxis( TransformToPlayerSpace(GetUntransformedViewAxis()) );
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::CheckFOV
|
|
//
|
|
// Similar to idActor::CheckFOV, but isn't infinite along the vertical plane
|
|
//
|
|
// CJR
|
|
//=============================================================================
|
|
|
|
bool hhPlayer::CheckFOV( const idVec3 &pos ) {
|
|
if ( fovDot == 1.0f ) {
|
|
return true;
|
|
}
|
|
|
|
float dot;
|
|
idVec3 delta;
|
|
|
|
delta = pos - GetEyePosition();
|
|
delta.Normalize();
|
|
|
|
dot = delta * viewAngles.ToForward();
|
|
|
|
return ( dot >= fovDot );
|
|
}
|
|
|
|
//=============================================================================
|
|
// hhPlayer::CheckFOV
|
|
// jsh - Similar to CheckFOV, but only checks yaw
|
|
//=============================================================================
|
|
bool hhPlayer::CheckYawFOV( const idVec3 &pos ) {
|
|
if ( fovDot == 1.0f ) {
|
|
return true;
|
|
}
|
|
|
|
float dot;
|
|
idVec3 delta;
|
|
idVec3 viewAng;
|
|
|
|
delta = pos - GetEyePosition();
|
|
delta.z = 0;
|
|
delta.Normalize();
|
|
viewAng = viewAngles.ToForward();
|
|
viewAng.z = 0;
|
|
dot = delta * viewAng;
|
|
|
|
return ( dot >= fovDot );
|
|
}
|
|
|
|
|
|
/*
|
|
=====================
|
|
hhPlayer::SetOrientation
|
|
|
|
HUMANHEAD: aob
|
|
=====================
|
|
*/
|
|
void hhPlayer::SetOrientation( const idVec3& origin, const idMat3& bboxAxis, const idVec3& lookDir, const idAngles& newUntransformedViewAngles ) {
|
|
//never let the untransformed view angles have a roll value
|
|
idAngles modifiedUntransViewAngles = newUntransformedViewAngles;
|
|
modifiedUntransViewAngles.roll = 0.0f;
|
|
|
|
physicsObj.SetOrigin( GetLocalCoordinates(origin) );
|
|
physicsObj.SetAxis( bboxAxis );
|
|
physicsObj.CheckWallWalk( true );
|
|
|
|
SetViewAngles( modifiedUntransViewAngles );
|
|
BufferLoggedViewAngles( modifiedUntransViewAngles );
|
|
SetUntransformedViewAngles( modifiedUntransViewAngles );
|
|
|
|
SetAxis( lookDir.ToMat3() );
|
|
SetUntransformedViewAxis( idAngles(0.0f, modifiedUntransViewAngles.yaw, 0.0f).ToMat3() );
|
|
|
|
cameraInterpolator.Reset( origin, bboxAxis[2], EyeHeightIdeal() );
|
|
|
|
UpdateViewAngles();
|
|
UpdateVisuals();
|
|
}
|
|
/*
|
|
=====================
|
|
hhPlayer::RestoreOrientation
|
|
|
|
HUMANHEAD: mdl
|
|
Same as SetOrientation, except doesn't check wallwalk, since the materials may not be set before the first frame.
|
|
=====================
|
|
*/
|
|
void hhPlayer::RestoreOrientation( const idVec3& origin, const idMat3& bboxAxis, const idVec3& lookDir, const idAngles& newUntransformedViewAngles ) {
|
|
physicsObj.SetOrigin( GetLocalCoordinates(origin) );
|
|
physicsObj.SetAxis( bboxAxis );
|
|
|
|
SetViewAngles( newUntransformedViewAngles );
|
|
BufferLoggedViewAngles( newUntransformedViewAngles );
|
|
SetUntransformedViewAngles( newUntransformedViewAngles );
|
|
|
|
SetAxis( lookDir.ToMat3() );
|
|
SetUntransformedViewAxis( idAngles(0.0f, newUntransformedViewAngles.yaw, 0.0f).ToMat3() );
|
|
cameraInterpolator.Reset( origin, bboxAxis[2], EyeHeightIdeal() );
|
|
|
|
UpdateViewAngles();
|
|
UpdateVisuals();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
hhPlayer::BufferLoggedViewAngles
|
|
|
|
HUMANHEAD: aob - used when teleporting and using portals
|
|
=====================
|
|
*/
|
|
void hhPlayer::BufferLoggedViewAngles( const idAngles& newUntransformedViewAngles ) {
|
|
idAngles deltaAngles = newUntransformedViewAngles - GetUntransformedViewAngles();
|
|
|
|
for( int ix = 0; ix < NUM_LOGGED_VIEW_ANGLES; ++ix ) {
|
|
loggedViewAngles[ix] += deltaAngles;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
hhPlayer::UpdateFromPhysics
|
|
|
|
AOBMERGE - PERSISTANT MERGE
|
|
================
|
|
*/
|
|
void hhPlayer::UpdateFromPhysics( bool moveBack ) {
|
|
|
|
// set master delta angles for actors
|
|
if ( GetBindMaster() ) {
|
|
if( !InVehicle() ) {
|
|
idAngles delta = GetDeltaViewAngles();
|
|
if ( moveBack ) {
|
|
delta.yaw -= physicsObj.GetMasterDeltaYaw();
|
|
} else {
|
|
delta.yaw += physicsObj.GetMasterDeltaYaw();
|
|
}
|
|
|
|
SetDeltaViewAngles( delta );
|
|
} else {
|
|
SetUntransformedViewAxis( mat3_identity );
|
|
SetUntransformedViewAngles( GetUntransformedViewAxis().ToAngles() );
|
|
viewAngles = GetUntransformedViewAngles();
|
|
SetAxis( GetBindMaster()->GetAxis() );
|
|
|
|
//AOB - now that we have an updated viewAxis we need to update the
|
|
//camera. Feels like a hack!
|
|
cameraInterpolator.SetTargetAxis( GetAxis(), INTERPOLATE_NONE );
|
|
}
|
|
}
|
|
|
|
UpdateVisuals();
|
|
}
|
|
|
|
/*
|
|
==============
|
|
hhPlayer::PerformImpulse
|
|
==============
|
|
*/
|
|
|
|
void hhPlayer::PerformImpulse( int impulse ) {
|
|
if( InVehicle() && GetVehicleInterface()->InvalidVehicleImpulse(impulse) ) {
|
|
return;
|
|
}
|
|
|
|
idPlayer::PerformImpulse( impulse );
|
|
|
|
switch( impulse ) {
|
|
case IMPULSE_14: {
|
|
if ( weapon.IsValid() && weapon->IsType( hhWeaponZoomable::Type ) ) {
|
|
hhWeaponZoomable *weap = static_cast<hhWeaponZoomable*>(weapon.GetEntity());
|
|
if ( weap && weap->GetAltMode() ) {
|
|
weap->ZoomInStep();
|
|
} else {
|
|
NextWeapon();
|
|
}
|
|
} else {
|
|
NextWeapon();
|
|
}
|
|
break;
|
|
}
|
|
case IMPULSE_15: {
|
|
if ( weapon.IsValid() && weapon->IsType( hhWeaponZoomable::Type ) ) {
|
|
hhWeaponZoomable *weap = static_cast<hhWeaponZoomable*>(weapon.GetEntity());
|
|
if ( weap && weap->GetAltMode() ) {
|
|
weap->ZoomOutStep();
|
|
} else {
|
|
PrevWeapon();
|
|
}
|
|
} else {
|
|
PrevWeapon();
|
|
}
|
|
break;
|
|
}
|
|
case IMPULSE_16:
|
|
// Toggle Lighter
|
|
if (inventory.requirements.bCanUseLighter && weaponFlags != 0) { // mdl: Disable lighter if all weapons are disabled
|
|
if (!gameLocal.isClient) {
|
|
ToggleLighter();
|
|
}
|
|
}
|
|
break;
|
|
case IMPULSE_25:
|
|
// Throw grenade
|
|
if ( weaponFlags != 0 && !ActiveGui()) { // mdl: Disable if all weapons are disabled
|
|
if (!gameLocal.isClient) {
|
|
ThrowGrenade();
|
|
}
|
|
}
|
|
break;
|
|
case IMPULSE_54:
|
|
// Spirit Power key
|
|
if ( IsDeathWalking() ) { // CJR: Developer-only ability to quickly return from DeathWalk by hitting the spirit key
|
|
if ( developer.GetBool() && ( gameLocal.time - deathWalkTime > spawnArgs.GetInt( "deathWalkMinTime", "4000" ) ) ) { // Force the player to stay in deathwalk for a short period of time
|
|
deathWalkFlash = 0.0f;
|
|
PostEventMS( &EV_PrepareToResurrect, 0 );
|
|
}
|
|
}
|
|
else if (inventory.requirements.bCanSpiritWalk) {
|
|
ToggleSpiritWalk();
|
|
}
|
|
break;
|
|
|
|
case IMPULSE_18: {
|
|
#if GAMEPAD_SUPPORT // VENOM BEGIN
|
|
idAngles localViewAngles( GetUntransformedViewAngles() );
|
|
centerView.Init(gameLocal.time, 360, localViewAngles.pitch, 0);
|
|
#else
|
|
centerView.Init(gameLocal.time, 200, viewAngles.pitch, 0);
|
|
#endif // VENOM END
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void hhPlayer::Present() {
|
|
idPlayer::Present();
|
|
|
|
if ( lighterHandle != -1 ) {
|
|
// Update oscillation position
|
|
idVec3 oscillation;
|
|
oscillation.x = 0.0f;
|
|
oscillation.y = idMath::Cos( MS2SEC(gameLocal.time) * spawnArgs.GetFloat("lighter_yspeed") ) * spawnArgs.GetFloat("lighter_ysize");
|
|
oscillation.z = idMath::Sin( MS2SEC(gameLocal.time) * spawnArgs.GetFloat("lighter_zspeed") ) * spawnArgs.GetFloat("lighter_zsize");
|
|
|
|
idVec3 offset;
|
|
offset = spawnArgs.GetVector("offset_lighter");
|
|
offset.z = EyeHeight();
|
|
lighter.origin = GetOrigin() + GetAxis() * (offset + oscillation);
|
|
lighter.axis = mat3_identity;
|
|
|
|
lighter.shaderParms[ SHADERPARM_DIVERSITY ] = renderEntity.shaderParms[ SHADERPARM_DIVERSITY ];
|
|
|
|
gameRenderWorld->UpdateLightDef( lighterHandle, &lighter );
|
|
}
|
|
}
|
|
|
|
void hhPlayer::LighterOn() {
|
|
if (IsSpiritOrDeathwalking() || InVehicle() || spectating || bReallyDead) { // No lighter in spirit mode, deathwalk, or when in vehicles (or when spectating or really dead -rww)
|
|
return;
|
|
}
|
|
|
|
if (lighterHandle == -1) {
|
|
// Add the dynamic light
|
|
memset( &lighter, 0, sizeof( lighter ) );
|
|
lighter.lightId = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber;
|
|
lighter.pointLight = true; // false; ?
|
|
lighter.shader = declManager->FindMaterial( spawnArgs.GetString( "mtr_lighter" ) );
|
|
lighter.shaderParms[ SHADERPARM_RED ] = spawnArgs.GetFloat( "lighterColorR" );
|
|
lighter.shaderParms[ SHADERPARM_GREEN ] = spawnArgs.GetFloat( "lighterColorG" );
|
|
lighter.shaderParms[ SHADERPARM_BLUE ] = spawnArgs.GetFloat( "lighterColorB" );
|
|
lighter.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
|
|
lighter.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
|
|
lighter.lightRadius = spawnArgs.GetVector( "lighter_radius" );
|
|
|
|
lighter.origin = GetOrigin() + GetAxis() * spawnArgs.GetVector("offset_lighter");
|
|
lighter.axis = mat3_identity;
|
|
|
|
lighterHandle = gameRenderWorld->AddLightDef( &lighter );
|
|
|
|
StartSound("snd_lighter_on", SND_CHANNEL_ITEM);
|
|
}
|
|
}
|
|
|
|
void hhPlayer::LighterOff() {
|
|
if (lighterHandle != -1) {
|
|
gameRenderWorld->FreeLightDef( lighterHandle );
|
|
lighterHandle = -1;
|
|
StartSound("snd_lighter_off", SND_CHANNEL_ITEM);
|
|
}
|
|
}
|
|
|
|
bool hhPlayer::IsLighterOn() const { //HUMANHEAD PCF mdl 05/04/06 - Made const
|
|
return (lighterHandle != -1);
|
|
}
|
|
|
|
void hhPlayer::ToggleLighter() {
|
|
if (IsLighterOn()) {
|
|
LighterOff();
|
|
}
|
|
else {
|
|
LighterOn();
|
|
}
|
|
}
|
|
|
|
void hhPlayer::UpdateLighter() {
|
|
if (IsLighterOn()) {
|
|
// Increase the lighter's temperature until it overheats
|
|
if (!gameLocal.isMultiplayer) { //rww - don't bother overheating the lighter in mp
|
|
lighterTemperature += spawnArgs.GetFloat( "lighterHeatRate", "0.025" ) * MS2SEC( USERCMD_MSEC );
|
|
if (lighterTemperature >= 1.0f) {
|
|
// Too hot, turn the lighter off
|
|
StartSound("snd_lighter_toohot", SND_CHANNEL_ANY);
|
|
lighterTemperature = 1.0f;
|
|
|
|
if (!(godmode || noclip)) {
|
|
LighterOff();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// Lighter is off, so decrease the lighter's temperature
|
|
if (lighterTemperature > 0) {
|
|
lighterTemperature -= spawnArgs.GetFloat( "lighterCoolRate", "0.05") * MS2SEC( USERCMD_MSEC );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
hhPlayer::PlayFootstepSound
|
|
==================
|
|
*/
|
|
void hhPlayer::PlayFootstepSound() {
|
|
if ( IsSpiritOrDeathwalking() ) {
|
|
return; // No footstep sounds in spiritwalk mode
|
|
}
|
|
|
|
if ( IsWallWalking() ) {
|
|
// Special case wallwalk since contacts includes some non-wallwalk types
|
|
const char* soundKey = gameLocal.MatterTypeToMatterKey( "snd_footstep", SURFTYPE_WALLWALK );
|
|
StartSound( soundKey, SND_CHANNEL_BODY3, 0, false, NULL );
|
|
return;
|
|
}
|
|
|
|
idPlayer::PlayFootstepSound();
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
hhPlayer::PlayPainSound
|
|
|
|
HUMANHEAD: aob
|
|
=====================
|
|
*/
|
|
void hhPlayer::PlayPainSound() {
|
|
if( IsSpiritOrDeathwalking() ) {
|
|
return;
|
|
}
|
|
|
|
//HUMANHEAD PCF rww 09/15/06 - female mp sounds
|
|
if (IsFemale()) {
|
|
StartSound( "snd_pain_small_female", SND_CHANNEL_VOICE );
|
|
return;
|
|
}
|
|
//HUMANHEAD END
|
|
idPlayer::PlayPainSound();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::Give
|
|
===============
|
|
*/
|
|
bool hhPlayer::Give( const char *statname, const char *value ) {
|
|
if( IsDead() ) {
|
|
return false;
|
|
}
|
|
|
|
return inventory.Give( this, spawnArgs, statname, value, &idealWeapon, true );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::ReportAttack
|
|
===============
|
|
*/
|
|
void hhPlayer::ReportAttack(idEntity *attacker) {
|
|
if (gameLocal.isServer && attacker) { //rww - broadcast event for this on the server
|
|
idBitMsg msg;
|
|
byte msgBuf[MAX_EVENT_PARAM_SIZE];
|
|
|
|
msg.Init(msgBuf, sizeof(msgBuf));
|
|
msg.WriteBits(gameLocal.GetSpawnId(attacker), 32);
|
|
//unreliable since it's not super-important, and only send to this player.
|
|
ServerSendEvent(EVENT_REPORTATTACK, &msg, false, -1, entityNumber, true);
|
|
}
|
|
|
|
int ix;
|
|
|
|
for (ix=0; ix<MAX_TRACKED_ATTACKERS; ix++) {
|
|
|
|
// If the attacker already exists, stomp it
|
|
if (lastAttackers[ix].attacker == attacker) {
|
|
lastAttackers[ix].time = gameLocal.time;
|
|
lastAttackers[ix].displayed = false;
|
|
return;
|
|
}
|
|
|
|
// If any are expired, free their slot
|
|
if (gameLocal.time > lastAttackers[ix].time + DAMAGE_INDICATOR_TIME) {
|
|
lastAttackers[ix].attacker = NULL;
|
|
}
|
|
}
|
|
|
|
// Add this attacker to first free slot, if any
|
|
for (ix=0; ix<MAX_TRACKED_ATTACKERS; ix++) {
|
|
if (!lastAttackers[ix].attacker.IsValid()) {
|
|
lastAttackers[ix].attacker = attacker;
|
|
lastAttackers[ix].time = gameLocal.time;
|
|
lastAttackers[ix].displayed = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// All slots full, overwrite the oldest slot
|
|
int oldSlot = -1;
|
|
int oldTime = gameLocal.time;
|
|
for (ix=0; ix<MAX_TRACKED_ATTACKERS; ix++) {
|
|
if (lastAttackers[ix].time < oldTime) {
|
|
oldTime = lastAttackers[ix].time;
|
|
oldSlot = ix;
|
|
}
|
|
}
|
|
if (oldSlot > 0) {
|
|
lastAttackers[oldSlot].attacker = attacker;
|
|
lastAttackers[oldSlot].time = gameLocal.time;
|
|
lastAttackers[oldSlot].displayed = false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
hhPlayer::Damage
|
|
==================
|
|
*/
|
|
void hhPlayer::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir,
|
|
const char *damageDefName, const float damageScale, const int location ) {
|
|
|
|
if( IsSpiritOrDeathwalking() ) { //Player is spirit-walking, so check for special immunities
|
|
const idKeyValue *kv = spawnArgs.MatchPrefix("immunityspirit");
|
|
while( kv && kv->GetValue().Length() ) {
|
|
if ( !kv->GetValue().Icmp(damageDefName) ) {
|
|
return;
|
|
}
|
|
kv = spawnArgs.MatchPrefix("immunityspirit", kv);
|
|
}
|
|
|
|
// If the player is spiritwalking, then any damage takes away spirit power instead of health
|
|
ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName( "ammo_spiritpower" );
|
|
if ( IsSpiritWalking() ) {
|
|
const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName );
|
|
if ( damageDef ) {
|
|
idPlayer *player = (attacker && attacker->IsType( idPlayer::Type )) ? static_cast<idPlayer*>(attacker) : NULL;
|
|
if ( !(gameLocal.gameType == GAME_TDM
|
|
&& !gameLocal.serverInfo.GetBool( "si_teamDamage" )
|
|
&& !damageDef->GetBool( "noTeam" )
|
|
&& player
|
|
&& player != this // you get self damage no matter what
|
|
&& player->team == team) ) {
|
|
//rww - don't damage teammates' spirit when ff off
|
|
int damageWhenSpirit = damageDef->GetInt("damageWhenSpirit", "0"); //rww - special damage for knocking players back to body in mp when shot by other things (namely spirit arrows)
|
|
int oldSpirit = inventory.ammo[ ammo_spiritpower ];
|
|
|
|
int spiritDamage = (damageDef->GetInt( "damage", "1" ) * spawnArgs.GetFloat( "damageScaleInSpirit" ) * damageScale )+damageWhenSpirit;
|
|
if ( spiritDamage <= 0 && damageScale > 0 ) {
|
|
spiritDamage = 1;
|
|
}
|
|
|
|
if ( !UseAmmo( ammo_spiritpower, spiritDamage ) ) {
|
|
inventory.ammo[ ammo_spiritpower ] = 0; // Clear spiritpower amount when returning from excessive damage
|
|
}
|
|
}
|
|
}
|
|
|
|
lastDamagedTime = gameLocal.time; // Save the damage time for the health recharge code
|
|
|
|
// Track last attacker for use in displaying HUD hit indicator
|
|
if (!gameLocal.isClient) {
|
|
ReportAttack(attacker);
|
|
}
|
|
|
|
//HUMANHEAD rww - damage feedback for hitting spirit players in mp
|
|
if (gameLocal.isMultiplayer && attacker && !gameLocal.isClient) {
|
|
hhPlayer *killer = NULL;
|
|
if (attacker->IsType(idPlayer::Type)) {
|
|
killer = static_cast<hhPlayer*>(attacker);
|
|
}
|
|
else if (attacker->IsType(hhVehicle::Type)) {
|
|
hhVehicle *veh = static_cast<hhVehicle*>(attacker);
|
|
if (veh->GetPilot() && veh->GetPilot()->IsType(idPlayer::Type)) {
|
|
killer = static_cast<hhPlayer*>(veh->GetPilot());
|
|
}
|
|
}
|
|
|
|
if (killer && killer->entityNumber != entityNumber && killer->mpHitFeedbackTime <= gameLocal.time) {
|
|
if (killer == gameLocal.GetLocalPlayer()) {
|
|
assert(IsSpiritOrDeathwalking());
|
|
if (gameLocal.gameType == GAME_TDM && team == killer->team) {
|
|
killer->StartSound( "snd_hitTeamFeedback", SND_CHANNEL_ITEM, 0, false, NULL );
|
|
}
|
|
else {
|
|
killer->StartSound( "snd_hitSpiritFeedback", SND_CHANNEL_ITEM, 0, false, NULL );
|
|
|
|
//hardcoded health ranges for the various flash colors
|
|
float h;
|
|
if (health > 100) {
|
|
h = 0.0f;
|
|
}
|
|
else if (health > 75) {
|
|
h = 0.25f;
|
|
}
|
|
else if (health > 25) {
|
|
h = 0.50f;
|
|
}
|
|
else {
|
|
h = 0.75f;
|
|
}
|
|
SetShaderParm(3, h);
|
|
SetShaderParm(5, -MS2SEC(gameLocal.time)*2);
|
|
}
|
|
}
|
|
else {
|
|
idBitMsg msg;
|
|
byte msgBuf[MAX_EVENT_PARAM_SIZE];
|
|
|
|
msg.Init(msgBuf, sizeof(msgBuf));
|
|
msg.WriteBits(gameLocal.GetSpawnId(this), 32);
|
|
killer->ServerSendEvent(EVENT_HITNOTIFICATION, &msg, false, -1, killer->entityNumber);
|
|
}
|
|
}
|
|
}
|
|
//HUMANHEAD END
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
lastDamagedTime = gameLocal.time; // Save the damage time for the health recharge code
|
|
|
|
// Track last attacker for use in displaying HUD hit indicator
|
|
if (!gameLocal.isClient) {
|
|
ReportAttack(attacker);
|
|
}
|
|
|
|
// Check if the damage should "really kill" the player (when in deathwalk mode)
|
|
if ( IsDeathWalking() ) {
|
|
const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName );
|
|
if ( damageDef ) {
|
|
if ( damageDef->GetBool( "reallyKill", "0" ) ) { // Truly kill the player
|
|
ReallyKilled( inflictor, attacker, damageDef->GetInt( "damage", "9999" ), dir, location );
|
|
return;
|
|
} else if ( damageDef->GetBool( "spiritDamage", "0" ) ) { // Drain spirit power
|
|
ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName( "ammo_spiritpower" );
|
|
UseAmmo( ammo_spiritpower, damageDef->GetInt( "damage", "1" ) * damageScale );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//HUMANHEAD rww - keep track of last person to attack me, debounce time will vary based on ground contact
|
|
if (attacker && attacker->IsType(idPlayer::Type)) {
|
|
airAttacker = attacker;
|
|
if (!AI_ONGROUND) {
|
|
airAttackerTime = gameLocal.time + 300;
|
|
}
|
|
else { //if they are on the ground do a 100ms debounce in case they fall off a ledge or something as a result of leftover attack velocity
|
|
airAttackerTime = gameLocal.time + 100;
|
|
}
|
|
}
|
|
//HUMANHEAD END
|
|
|
|
// CJR: DDA: Only in single player
|
|
if ( !gameLocal.isMultiplayer ) {
|
|
float newDamageScale = gameLocal.GetDDAValue() * 2.0f; // Scale damage from easy to hard
|
|
newDamageScale *= damageScale;
|
|
|
|
const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName );
|
|
if ( damageDef ) {
|
|
bool noGod = damageDef->GetBool("noGod", "0");
|
|
if ( noGod ) {
|
|
// Make sure fatal damage bypasses post-deathwalk invulnerability
|
|
fl.takedamage = true;
|
|
}
|
|
}
|
|
|
|
int oldHealth = health;
|
|
idPlayer::Damage( inflictor, attacker, dir, damageDefName, (const float)newDamageScale, location ); // CJR DDA TEST
|
|
|
|
if ( attacker && attacker->IsType( hhMonsterAI::Type ) ) { // CJR DDA: Damaged by a monster, add the damage to the monster count
|
|
float delta = oldHealth - health;
|
|
|
|
hhMonsterAI *monster = static_cast<hhMonsterAI *>( attacker );
|
|
|
|
if ( monster ) {
|
|
monster->DamagedPlayer( delta );
|
|
}
|
|
}
|
|
} else {
|
|
idPlayer::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
|
|
}
|
|
|
|
//HUMANHEAD rww - hit feedback
|
|
if (gameLocal.isMultiplayer && attacker && !gameLocal.isClient) { //let's broadcast from the server only, so hit feedback is always reliable
|
|
hhPlayer *killer = NULL;
|
|
if (attacker->IsType(idPlayer::Type)) {
|
|
killer = static_cast<hhPlayer*>(attacker);
|
|
}
|
|
else if (attacker->IsType(hhVehicle::Type)) {
|
|
hhVehicle *veh = static_cast<hhVehicle*>(attacker);
|
|
if (veh->GetPilot() && veh->GetPilot()->IsType(idPlayer::Type)) {
|
|
killer = static_cast<hhPlayer*>(veh->GetPilot());
|
|
}
|
|
}
|
|
|
|
if (killer && killer->entityNumber != entityNumber && killer->mpHitFeedbackTime <= gameLocal.time) {
|
|
if (killer == gameLocal.GetLocalPlayer()) {
|
|
//don't provide visual indicator when shooting a teammate
|
|
if (gameLocal.gameType == GAME_TDM && team == killer->team) {
|
|
killer->StartSound( "snd_hitTeamFeedback", SND_CHANNEL_ITEM, 0, false, NULL );
|
|
}
|
|
else {
|
|
if (IsSpiritOrDeathwalking()) {
|
|
killer->StartSound( "snd_hitSpiritFeedback", SND_CHANNEL_ITEM, 0, false, NULL );
|
|
}
|
|
else {
|
|
killer->StartSound( "snd_hitFeedback", SND_CHANNEL_ITEM, 0, false, NULL );
|
|
}
|
|
|
|
//hardcoded health ranges for the various flash colors
|
|
float h;
|
|
if (health > 100) {
|
|
h = 0.0f;
|
|
}
|
|
else if (health > 75) {
|
|
h = 0.25f;
|
|
}
|
|
else if (health > 25) {
|
|
h = 0.50f;
|
|
}
|
|
else {
|
|
h = 0.75f;
|
|
}
|
|
SetShaderParm(3, h);
|
|
SetShaderParm(5, -MS2SEC(gameLocal.time)*2);
|
|
}
|
|
}
|
|
else {
|
|
idBitMsg msg;
|
|
byte msgBuf[MAX_EVENT_PARAM_SIZE];
|
|
|
|
msg.Init(msgBuf, sizeof(msgBuf));
|
|
msg.WriteBits(gameLocal.GetSpawnId(this), 32);
|
|
killer->ServerSendEvent(EVENT_HITNOTIFICATION, &msg, false, -1, killer->entityNumber);
|
|
}
|
|
|
|
//see how this feels (and more importantly how destructive it is toward bandwidth)
|
|
//killer->mpHitFeedbackTime = gameLocal.time + USERCMD_MSEC;
|
|
}
|
|
}
|
|
//HUMANHEAD END
|
|
|
|
if ( bFrozen ) {
|
|
const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName );
|
|
if ( damageDef && damageDef->GetInt( "free_cocoon", "0" ) ) {
|
|
Event_Unfreeze();
|
|
}
|
|
}
|
|
}
|
|
|
|
void hhPlayer::DoDeathDrop() {
|
|
// General dropping (for monsters, souls, etc.)
|
|
const idKeyValue *kv = NULL;
|
|
kv = spawnArgs.MatchPrefix( "def_drops", NULL );
|
|
while ( kv ) {
|
|
|
|
idStr drops = kv->GetValue();
|
|
idDict args;
|
|
|
|
idStr last5 = kv->GetKey().Right(5);
|
|
if ( drops.Length() && idStr::Icmp( last5, "Joint" ) != 0) {
|
|
|
|
args.Set( "classname", drops );
|
|
|
|
// HUMANHEAD pdm: specify monster so souls can call back to remove body when picked up
|
|
args.Set("monsterSpawnedBy", name.c_str());
|
|
|
|
idVec3 origin;
|
|
idMat3 axis;
|
|
idStr jointKey = kv->GetKey() + idStr("Joint");
|
|
idStr jointName = spawnArgs.GetString( jointKey );
|
|
idStr joint2JointKey = kv->GetKey() + idStr("Joint2Joint");
|
|
idStr j2jName = spawnArgs.GetString( joint2JointKey );
|
|
|
|
idEntity *newEnt = NULL;
|
|
gameLocal.SpawnEntityDef( args, &newEnt );
|
|
HH_ASSERT(newEnt != NULL);
|
|
|
|
if(jointName.Length()) {
|
|
jointHandle_t joint = GetAnimator()->GetJointHandle( jointName );
|
|
if (!GetAnimator()->GetJointTransform( joint, gameLocal.time, origin, axis ) ) {
|
|
gameLocal.Printf( "%s refers to invalid joint '%s' on entity '%s'\n", (const char*)jointKey.c_str(), (const char*)jointName, (const char*)name );
|
|
origin = renderEntity.origin;
|
|
axis = renderEntity.axis;
|
|
}
|
|
axis *= renderEntity.axis;
|
|
origin = renderEntity.origin + origin * renderEntity.axis;
|
|
newEnt->SetAxis(axis);
|
|
newEnt->SetOrigin(origin);
|
|
}
|
|
else {
|
|
newEnt->SetAxis(viewAxis);
|
|
newEnt->SetOrigin(GetOrigin());
|
|
}
|
|
}
|
|
|
|
kv = spawnArgs.MatchPrefix( "def_drops", kv );
|
|
}
|
|
}
|
|
|
|
//HUMANHEAD rww
|
|
/*
|
|
==================
|
|
hhPlayer::Event_RespawnCleanup
|
|
performs any necessary operations on death to prepare for a clean spawn.
|
|
only cosmetic things should be placed here, this function will not be called
|
|
for sure on death, at least for the client.
|
|
==================
|
|
*/
|
|
void hhPlayer::Event_RespawnCleanup(void) {
|
|
//remove any fx entities which are bound to the player
|
|
idEntity *ent;
|
|
idEntity *next;
|
|
|
|
for( ent = teamChain; ent != NULL; ent = next ) {
|
|
next = ent->GetTeamChain();
|
|
if ( ent->GetBindMaster() == this && ent->IsType(hhEntityFx::Type) ) {
|
|
ent->Unbind();
|
|
if( !ent->fl.noRemoveWhenUnbound ) {
|
|
ent->PostEventMS( &EV_Remove, 0 );
|
|
if (gameLocal.isClient) {
|
|
ent->Hide();
|
|
}
|
|
}
|
|
next = teamChain;
|
|
}
|
|
}
|
|
}
|
|
//HUMANHEAD END
|
|
|
|
/*
|
|
==================
|
|
hhPlayer::Killed
|
|
==================
|
|
*/
|
|
void hhPlayer::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
|
|
//you only die once
|
|
if( AI_DEAD ) {
|
|
return;
|
|
}
|
|
|
|
CancelEvents(&EV_DamagePlayer); //rww - don't do any more posted damage events once dead
|
|
|
|
if (gameLocal.isMultiplayer) { //rww - this was not handled at all, i guess because there are different circumstances for "death" in sp
|
|
wallwalkSoundController.StopSound( SND_CHANNEL_WALLWALK, SND_CHANNEL_WALLWALK2, false );
|
|
spiritwalkSoundController.StopSound( SND_CHANNEL_BODY, SND_CHANNEL_BODY2 );
|
|
|
|
LighterOff();
|
|
|
|
if (!gameLocal.isClient && weapon.IsValid() && weapon->IsType(hhWeapon::Type) && weapon->CanDrop()) { //when dying in mp, toss my weapon out.
|
|
idVec3 forward, up;
|
|
|
|
viewAngles.ToVectors( &forward, NULL, &up );
|
|
//rww - hackishness to keep the type of ammo in the leechgun that it was dropped with
|
|
idEntity *dropped = weapon->DropItem( 50.0f * forward + 50.0f * up, 0, 60000, true );
|
|
if (dropped && weapon->IsType(hhWeaponSoulStripper::Type)) {
|
|
dropped->spawnArgs.Set("def_droppedEnergyType", inventory.energyType);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!gameLocal.isClient) {
|
|
DoDeathDrop();
|
|
}
|
|
|
|
if ( gameLocal.isMultiplayer && !gameLocal.IsCooperative() ) {
|
|
bDeathWalk = false;
|
|
bSpiritWalk = false;
|
|
bReallyDead = true;
|
|
}
|
|
else if ( !inventory.requirements.bCanDeathWalk || !gameLocal.DeathwalkMapLoaded() ) {
|
|
bDeathWalk = false;
|
|
bSpiritWalk = false;
|
|
bReallyDead = true;
|
|
}
|
|
|
|
//First thing we do is get out of vehicle
|
|
if (InVehicle()) {
|
|
GetVehicleInterface()->GetVehicle()->EjectPilot();
|
|
}
|
|
|
|
// HUMANHEAD cjr: Update DDA
|
|
if ( !AI_DEAD && !gameLocal.isMultiplayer ) {
|
|
gameLocal.GetDDA()->DDA_AddDeath( this, attacker );
|
|
}
|
|
// HUMANHEAD END
|
|
|
|
if ( attacker && attacker->spawnArgs.GetBool("death_look_at", "0")) {
|
|
attacker->spawnArgs.GetString("death_look_at_bone", "", deathLookAtBone);
|
|
attacker->spawnArgs.GetString("death_camera_bone", "", deathCameraBone );
|
|
deathLookAtEntity = attacker;
|
|
}
|
|
|
|
if ( weapon.IsValid() ) {
|
|
//HUMANHEAD bjk 04/26/06 - no sniper scope when dead
|
|
if ( weapon->IsType( hhWeaponRifle::Type ) ) {
|
|
static_cast<hhWeaponRifle*>( weapon.GetEntity() )->ZoomOut();
|
|
}
|
|
weapon->PutAway();
|
|
}
|
|
|
|
// HUMANHEAD nla
|
|
if ( hand.IsValid() ) {
|
|
hand->PutAway();
|
|
}
|
|
// HUMANHEAD END
|
|
|
|
if ( !bReallyDead ) { // Only go into deathwalk mode if the player is in the transitional death state
|
|
idVec3 origin;
|
|
idMat3 axis;
|
|
idVec3 viewDir;
|
|
idAngles angles;
|
|
idMat3 eyeAxis;
|
|
GetResurrectionPoint( origin, axis, viewDir, angles, eyeAxis, GetPhysics()->GetAbsBounds(), GetOrigin(), GetPhysics()->GetAxis(), GetAxis()[0], viewAngles );
|
|
DeathWalk( origin, axis, viewDir.ToMat3(), angles, eyeAxis );
|
|
} else {
|
|
if (gameLocal.isMultiplayer) { //HUMANHEAD rww
|
|
PostEventMS(&EV_RespawnCleanup, 32);
|
|
if (!gameLocal.isClient) {
|
|
StartRagdoll(); //start to rag on the player so that the proxy copies off proper af status
|
|
|
|
GetPhysics()->SetContents( 0 ); //make non-solid
|
|
|
|
hhMPDeathProxy *prox = (hhMPDeathProxy *)hhSpiritProxy::CreateProxy( spawnArgs.GetString("def_deathProxy_mp"), this, GetOrigin(), GetPhysics()->GetAxis(), viewAxis, viewAngles, GetEyeAxis() );
|
|
assert(((hhSpiritProxy *)prox)->IsType(hhMPDeathProxy::Type));
|
|
if (prox) {
|
|
idVec3 flingForce;
|
|
float capDmg = (float)damage;
|
|
if (capDmg > 200.0f) {
|
|
capDmg = 200.0f;
|
|
}
|
|
|
|
flingForce = dir;
|
|
flingForce.Normalize();
|
|
flingForce *= capDmg;
|
|
|
|
prox->GetPhysics()->AddForce(0, prox->GetPhysics()->GetOrigin(0), flingForce*256.0f*256.0f);
|
|
prox->SetFling(prox->GetPhysics()->GetOrigin(0), flingForce);
|
|
}
|
|
|
|
StopRagdoll(); //stop again, as we don't need to be ragging on the actual player
|
|
}
|
|
|
|
Hide(); //hide player
|
|
|
|
minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME;
|
|
maxRespawnTime = minRespawnTime + 10000;
|
|
} else {
|
|
GetPhysics()->SetContents(0);
|
|
Hide();
|
|
|
|
minRespawnTime = gameLocal.time + 2000;
|
|
maxRespawnTime = minRespawnTime + 5000;
|
|
} //HUMANHEAD END
|
|
|
|
physicsObj.SetMovementType( PM_DEAD );
|
|
SAFE_REMOVE( weapon );
|
|
}
|
|
|
|
AI_DEAD = true;
|
|
|
|
//HUMANHEAD rww
|
|
if (gameLocal.isMultiplayer) {
|
|
SetAnimState( ANIMCHANNEL_LEGS, "Legs_Death", 4 );
|
|
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Death", 4 );
|
|
SetWaitState( "" );
|
|
}
|
|
//HUMANHEAD END
|
|
|
|
//HUMANHEAD PCF rww 09/15/06 - female mp sounds
|
|
if (IsFemale()) {
|
|
StartSound( "snd_death_female", SND_CHANNEL_VOICE );
|
|
}
|
|
else {
|
|
//HUMANHEAD END
|
|
StartSound( "snd_death", SND_CHANNEL_VOICE );
|
|
}
|
|
|
|
if ( gameLocal.isMultiplayer && !gameLocal.isCoop ) {
|
|
idPlayer *killer = NULL;
|
|
|
|
if ( attacker->IsType( idPlayer::Type ) ) {
|
|
killer = static_cast<idPlayer*>(attacker);
|
|
}
|
|
else { //rww - otherwise try to credit airAttacker
|
|
if (airAttacker.IsValid() &&
|
|
airAttacker.GetEntity() &&
|
|
airAttackerTime > gameLocal.time) {
|
|
|
|
if (airAttacker->IsType(idPlayer::Type)) {
|
|
killer = static_cast<idPlayer*>(airAttacker.GetEntity());
|
|
}
|
|
else if (airAttacker->IsType(hhVehicle::Type)) {
|
|
hhVehicle *veh = static_cast<hhVehicle*>(airAttacker.GetEntity());
|
|
if (veh->GetPilot() && veh->GetPilot()->IsType(idPlayer::Type)) {
|
|
killer = static_cast<idPlayer*>(veh->GetPilot());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
gameLocal.mpGame.PlayerDeath( this, killer, inflictor, false ); //HUMANHEAD rww - pass inflictor
|
|
}
|
|
|
|
UpdateVisuals();
|
|
|
|
airAttackerTime = 0; //HUMANHEAD rww - reset air attacker time once dead
|
|
}
|
|
|
|
/*
|
|
==============
|
|
hhPlayer::Kill
|
|
==============
|
|
*/
|
|
void hhPlayer::Kill( bool delayRespawn, bool nodamage ) {
|
|
if (noclip) { // HUMANHEAD pdm: Because of deathwalk, this shouldn't be allowed
|
|
return;
|
|
}
|
|
|
|
if ( health > 0 ) {
|
|
//HUMANHEAD rww
|
|
if (IsSpiritOrDeathwalking()) {
|
|
if (!IsSpiritWalking()) { //don't "kill" when dead.
|
|
return;
|
|
}
|
|
StopSpiritWalk(true);
|
|
}
|
|
//HUMANHEAD END
|
|
|
|
godmode = false;
|
|
health = 0;
|
|
//HUMANHEAD rww - if in a vehicle, eject now
|
|
if (InVehicle()) {
|
|
if (vehicleInterfaceLocal.GetVehicle()) {
|
|
vehicleInterfaceLocal.GetVehicle()->EjectPilot();
|
|
}
|
|
}
|
|
//HUMANHEAD END
|
|
Damage( NULL, NULL, vec3_origin, "damage_suicide", 1.0f, INVALID_JOINT );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::DetermineOwnerPosition
|
|
|
|
HUMANHEAD: aob
|
|
===============
|
|
*/
|
|
void hhPlayer::DetermineOwnerPosition( idVec3 &ownerOrigin, idMat3 &ownerAxis ) {
|
|
ownerAxis = TransformToPlayerSpace( GetUntransformedViewAxis() );
|
|
ownerOrigin = cameraInterpolator.GetCurrentPosition() + idVec3(g_gun_x.GetFloat(), g_gun_y.GetFloat(), g_gun_z.GetFloat()) * GetAxis();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::GetViewPos
|
|
===============
|
|
*/
|
|
//HUMANHEAD bjk
|
|
void hhPlayer::GetViewPos( idVec3 &origin, idMat3 &axis ) {
|
|
idAngles angles;
|
|
|
|
// if dead, fix the angle and don't add any kick
|
|
// HUMANHEAD cjr: Replaced health <= 0 with IsDead() call for deathwalk override
|
|
if ( IsDead() && !gameLocal.isMultiplayer ) { //rww - don't want this in mp.
|
|
// HUMANHEAD END
|
|
angles.yaw = viewAngles.yaw;
|
|
angles.roll = 40;
|
|
angles.pitch = -15;
|
|
axis = angles.ToMat3();
|
|
origin = GetEyePosition();
|
|
} else {
|
|
assert(kickSpring < 500.0f && kickSpring > 0.0f);
|
|
assert(kickDamping < 500.0f && kickDamping > 0.0f);
|
|
origin = viewBob + TransformToPlayerSpace( idVec3(g_viewNodalX.GetFloat(), g_viewNodalZ.GetFloat(), g_viewNodalZ.GetFloat() + EyeHeight()) );
|
|
axis = TransformToPlayerSpace( (GetUntransformedViewAngles() + viewBobAngles + playerView.AngleOffset(kickSpring, kickDamping)).ToMat3() );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::CalculateRenderView
|
|
===============
|
|
*/
|
|
void hhPlayer::CalculateRenderView( void ) {
|
|
idPlayer::CalculateRenderView();
|
|
|
|
// HUMANHEAD cjr
|
|
if ( IsSpiritOrDeathwalking() ) { // If spiritwalking, then allow the player to see special objects
|
|
renderView->viewSpiritEntities = true;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::OffsetThirdPersonView
|
|
PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge
|
|
===============
|
|
*/
|
|
void hhPlayer::OffsetThirdPersonView( float angle, float range, float height, bool clip ) {
|
|
idVec3 view;
|
|
trace_t trace;
|
|
idMat3 lookAxis;
|
|
idVec3 origin;
|
|
idAngles angles( GetUntransformedViewAngles() );
|
|
|
|
if ( angle ) {
|
|
angles.pitch = 0.0f;
|
|
}
|
|
|
|
if ( angles.pitch > 45.0f && !InVehicle() ) {
|
|
angles.pitch = 45.0f; // don't go too far overhead
|
|
}
|
|
|
|
//HUMANHEAD: aob
|
|
lookAxis = TransformToPlayerSpace( angles.ToMat3() * idAngles(0.0f, angle, 0.0f).ToMat3() );
|
|
if( InVehicle() ) {
|
|
origin = GetOrigin() + lookAxis[2] * (height + EyeHeight());
|
|
} else {
|
|
origin = GetEyePosition() + GetEyeAxis()[2] * height;
|
|
}
|
|
view = origin + lookAxis[0] * -range;
|
|
// trace a ray from the origin to the viewpoint to make sure the view isn't
|
|
// in a solid block. Use an 16 by 16 block to prevent the view from near clipping anything
|
|
if( !noclip ) {
|
|
int mask = MASK_SHOT_BOUNDINGBOX;
|
|
|
|
if (gameLocal.isMultiplayer) { //rww - don't want this hitting corpses. also, shouldn't we pay attention to "clip"?
|
|
mask = MASK_PLAYERSOLID;
|
|
}
|
|
|
|
thirdPersonCameraClipBounds.SetOwner( this );
|
|
idEntity* ignore = (InVehicle()) ? GetVehicleInterface()->GetVehicle() : (idEntity*)this;
|
|
gameLocal.clip.Translation( trace, origin, view, &thirdPersonCameraClipBounds, lookAxis, mask, ignore );
|
|
range *= trace.fraction;
|
|
}
|
|
|
|
renderView->vieworg = origin + lookAxis[0] * -range;
|
|
renderView->viewaxis = lookAxis;
|
|
//HUMANHEAD END
|
|
renderView->viewID = 0;
|
|
}
|
|
|
|
|
|
/*
|
|
==============================
|
|
hhPlayer::GetGuiHandInfo
|
|
Retrns the proper gui hand info for a given gui
|
|
==============================
|
|
*/
|
|
const char *hhPlayer::GetGuiHandInfo() {
|
|
const char *attrib;
|
|
idDict *item = NULL;
|
|
const char *handString;
|
|
|
|
// Set the gui to:
|
|
// "required_attribute" "Hunter Hand"
|
|
// If item exists in player inventory, player uses guihand specified by item
|
|
if (!IsSpiritWalking() && focusGUIent && focusGUIent->spawnArgs.GetString("required_attribute", NULL, &attrib)) {
|
|
item = FindInventoryItem( attrib );
|
|
if ( item ) {
|
|
if (item->GetString("def_guihand", NULL, &handString)) {
|
|
return handString;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If no required attribute/player doesn't have the item, check for a def_guihand string
|
|
if ( focusGUIent && focusGUIent->spawnArgs.GetString( "def_guihand", NULL, &handString ) ) {
|
|
return( handString );
|
|
}
|
|
|
|
// Otherwise, use the default player hand
|
|
return spawnArgs.GetString("def_guihand");
|
|
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
//
|
|
// Spirit walk functions
|
|
//
|
|
//=============================================================================
|
|
|
|
void hhPlayer::StartSpiritWalk( const bool bThrust, bool force ) {
|
|
hhFxInfo fxInfo;
|
|
idVec3 origin;
|
|
idMat3 axis;
|
|
|
|
//rww - don't do anything on the client
|
|
if (gameLocal.isClient) {
|
|
return;
|
|
}
|
|
|
|
if ( bReallyDead ) { // Don't allow spiritwalking if truly dead
|
|
if (force) {
|
|
gameLocal.Error("Attempted to force spirit walk when dead.\n");
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Make sure spirit walk isn't disabled
|
|
if ( !bAllowSpirit ) {
|
|
StartSound("snd_spiritWalkDenied", SND_CHANNEL_ANY);
|
|
return;
|
|
}
|
|
|
|
spiritWalkToggleTime = gameLocal.time;
|
|
|
|
if ( gameLocal.isMultiplayer ) { // CJR: Don't allow spiritwalking in MP unless the player has spirit power
|
|
if ( GetSpiritPower() <= 0 ) {
|
|
StartSound("snd_spiritWalkDenied", SND_CHANNEL_ANY, 0, true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( !force && nextSpiritTime > gameLocal.time ) { // mdl: Make sure they didn't just get knocked back into their body by a wraith
|
|
StartSound("snd_spiritWalkDenied", SND_CHANNEL_ANY);
|
|
return;
|
|
}
|
|
|
|
if (!IsSpiritOrDeathwalking()) {
|
|
if ( !gameLocal.IsLOTA() ) { // In LOTA, spirit power never drains
|
|
PostEventMS( &EV_DrainSpiritPower, spiritDrainHeartbeatMS );
|
|
}
|
|
|
|
if ( !bThrust ) { // Normal spiritwalking
|
|
EnableEthereal( spawnArgs.GetString( "def_spiritProxy" ), GetOrigin(), GetPhysics()->GetAxis(), viewAxis, viewAngles, GetEyeAxis() );
|
|
} else { // Knocked out by a wraith
|
|
EnableEthereal( spawnArgs.GetString( "def_possessedProxy" ), GetOrigin(), GetPhysics()->GetAxis(), viewAxis, viewAngles, GetEyeAxis() );
|
|
}
|
|
|
|
SelectEtherealWeapon();
|
|
|
|
spiritwalkSoundController.StartSound( SND_CHANNEL_BODY, SND_CHANNEL_BODY2 );
|
|
|
|
// Update HUD
|
|
if (hud) {
|
|
hud->HandleNamedEvent("SwitchToEthereal");
|
|
}
|
|
|
|
// Set the player's skin to a glowy effect
|
|
SetSkinByName( spawnArgs.GetString("skin_Spiritwalk") );
|
|
SetShaderParm( SHADERPARM_TIMEOFFSET, 1.0f ); // TEMP: cjr - Required by the forcefield material. Can remove when a proper spiritwalk texture is made
|
|
|
|
// Spawn in a flash
|
|
GetViewPos( origin, axis );
|
|
fxInfo.SetEntity( this );
|
|
fxInfo.RemoveWhenDone( true );
|
|
fxInfo.SetBindBone( "origin" );
|
|
BroadcastFxInfoPrefixed( "fx_spiritWalkFlash", origin, axis, &fxInfo );
|
|
|
|
// Thrust the player backwards out of the body
|
|
if ( bThrust ) {
|
|
idVec3 vec = GetPhysics()->GetAxis()[0] * -200.0f + GetPhysics()->GetAxis()[2] * 50.0f;
|
|
|
|
physicsObj.SetLinearVelocity( physicsObj.GetLinearVelocity() + vec );
|
|
// set the timer so that the player can't cancel out the movement immediately
|
|
physicsObj.SetKnockBack( 100 );
|
|
}
|
|
|
|
// bg - trigger map entity, provides a simple hook for map/scripts
|
|
idEntity *swTrig = gameLocal.FindEntity( "sw_spiritWalkEntered" );
|
|
if( swTrig ) {
|
|
swTrig->PostEventMS( &EV_Activate, 0, this );
|
|
}
|
|
}
|
|
}
|
|
|
|
void hhPlayer::StopSpiritWalk(bool forceAllowance) {
|
|
hhFxInfo fxInfo;
|
|
idVec3 origin;
|
|
idMat3 axis;
|
|
|
|
//rww - don't do anything on the client
|
|
if (gameLocal.isClient) {
|
|
return;
|
|
}
|
|
|
|
if ( IsPossessed() ) { // If possessed and the player stops spiritwalking, they die
|
|
PossessKilled();
|
|
return;
|
|
}
|
|
|
|
spiritWalkToggleTime = gameLocal.time;
|
|
|
|
if ( bReallyDead ) { // Don't allow spiritwalking if truly dead
|
|
return;
|
|
}
|
|
|
|
CancelEvents(&EV_SetOverlayMaterial); //HUMANHEAD rww - if being forced back out of spirit mode quickly enough, don't set overlay afterward
|
|
|
|
if (IsSpiritOrDeathwalking()) {
|
|
CancelEvents(&EV_DrainSpiritPower);
|
|
if( weapon.IsValid() && weapon->IsType(hhWeaponSpiritBow::Type) ) {
|
|
hhWeaponSpiritBow *bow = static_cast<hhWeaponSpiritBow *>( weapon.GetEntity() );
|
|
if( bow->BowVisionIsEnabled() ) {
|
|
bow->StopBowVision();
|
|
}
|
|
}
|
|
DisableEthereal();
|
|
spiritwalkSoundController.StopSound( SND_CHANNEL_BODY, SND_CHANNEL_BODY2 );
|
|
SetSkin( NULL );
|
|
|
|
// bg - trigger map entity, provides a simple hook for map/scripts
|
|
idEntity *swTrig = gameLocal.FindEntity( "sw_spiritWalkExited" );
|
|
if( swTrig ) {
|
|
swTrig->PostEventMS( &EV_Activate, 0, this );
|
|
}
|
|
|
|
// Update HUD
|
|
if (hud) {
|
|
hud->HandleNamedEvent("SwitchFromEthereal");
|
|
}
|
|
buttonMask |= BUTTON_ATTACK; //HUMANHEAD bjk
|
|
|
|
// Spawn in a flash
|
|
GetViewPos( origin, axis );
|
|
fxInfo.SetEntity( this );
|
|
fxInfo.RemoveWhenDone( true );
|
|
fxInfo.SetBindBone( "origin" );
|
|
BroadcastFxInfoPrefixed( "fx_spiritWalkFlash", origin, axis, &fxInfo );
|
|
}
|
|
}
|
|
|
|
void hhPlayer::ToggleSpiritWalk( void ) {
|
|
if (spectating) { //rww - do not allow spectators to spirit walk.
|
|
return;
|
|
}
|
|
|
|
if ( bPossessed ) { // Cannot toggle away from spiritwalk when possessed
|
|
StartSound("snd_spiritWalkDenied", SND_CHANNEL_ANY);
|
|
return;
|
|
}
|
|
|
|
// spiritwalk time check -- force a delay between spiritwalking and not
|
|
if (gameLocal.time < spiritWalkToggleTime + 250) {
|
|
return;
|
|
}
|
|
|
|
if( IsSpiritOrDeathwalking() ) {
|
|
StopSpiritWalk();
|
|
} else {
|
|
StartSpiritWalk( false );
|
|
}
|
|
}
|
|
|
|
//HUMANHEAD rww
|
|
void hhPlayer::RestorePlayerLocationFromDeathwalk( const idVec3& origin, const idMat3& bboxAxis, const idVec3& viewDir, const idAngles& angles ) {
|
|
idVec3 newOrigin;
|
|
idMat3 newAxis;
|
|
idVec3 newViewDir;
|
|
idAngles newAngles;
|
|
|
|
if( deathwalkLastCrouching ) {
|
|
ForceCrouching();
|
|
}
|
|
|
|
SetEyeAxis(deathwalkLastEyeAxis);
|
|
GetResurrectionPoint( newOrigin, newAxis, newViewDir, newAngles, deathwalkLastEyeAxis, GetPhysics()->GetAbsBounds(), deathwalkLastOrigin, deathwalkLastBBoxAxis, viewDir, angles );
|
|
Teleport( newOrigin, newAxis, newViewDir, (newAngles.ToMat3() * deathwalkLastEyeAxis.Transpose()).ToAngles(), NULL );
|
|
}
|
|
//HUMANHEAD END
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::EnableEthereal
|
|
//
|
|
// Sets the player into an ethereal state (used for both spiritwalk and deathwalk)
|
|
// This function does the following:
|
|
// - Fade volume down on all class 0 sounds
|
|
// - Sets bSpiritWalk (since this is true for both spirit and death walks)
|
|
// - Spawns a proxy
|
|
// - Sets the new collision
|
|
// - Disables the weapons (the bow is enabled seperately for spiritwalk)
|
|
// - Disables the lighter
|
|
// - Unpossesses the player (if possessed)
|
|
// - Updates bindings
|
|
//=============================================================================
|
|
|
|
void hhPlayer::EnableEthereal( const char *proxyName, const idVec3& origin, const idMat3& bboxAxis, const idMat3& newViewAxis, const idAngles& newViewAngles, const idMat3& newEyeAxis ) {
|
|
//rww - don't do anything on the client - DO NOT PUT ANYTHING ABOVE HERE
|
|
if (gameLocal.isClient) {
|
|
return;
|
|
}
|
|
|
|
// Turn on low-pass effect
|
|
if (gameLocal.localClientNum == entityNumber) { //rww - don't do this for anyone but the local client on listen servers
|
|
gameLocal.SpiritWalkSoundMode( true );
|
|
}
|
|
|
|
// Spawn proxy Tommy actor
|
|
spiritProxy = hhSpiritProxy::CreateProxy( proxyName, this, origin, bboxAxis, newViewAxis, newViewAngles, newEyeAxis );
|
|
|
|
// Change player's collision type (to allow walking through forcefields, etc)
|
|
GetPhysics()->SetClipMask( MASK_SPIRITPLAYER );
|
|
fl.acceptsWounds = false;
|
|
spawnArgs.Set("produces_splats", "0");
|
|
|
|
// HUMANHEAD mdl: Let our enemies know about the spirit proxy
|
|
for ( int i = 0; i < hhMonsterAI::allSimpleMonsters.Num(); i++ ) {
|
|
if ( hhMonsterAI::allSimpleMonsters[i]->GetEnemy() == this ) {
|
|
hhMonsterAI::allSimpleMonsters[i]->ProcessEvent( &MA_EnemyIsSpirit, this, spiritProxy.GetEntity() );
|
|
}
|
|
}
|
|
|
|
// If bound to another object (like on a rail ride), swap bindings with the proxy
|
|
if ( GetBindMaster() != NULL ) {
|
|
if (GetBindMaster()->IsType(hhBindController::Type)) {
|
|
hhBindController *binder = static_cast<hhBindController *>(GetBindMaster());
|
|
bool loose = binder->IsLoose();
|
|
binder->Detach();
|
|
binder->Attach( spiritProxy.GetEntity(), loose );
|
|
} else {
|
|
gameLocal.Warning("Handle this: spiritwalking while bound to non-rail");
|
|
SwapBindInfo( spiritProxy.GetEntity() ); // untested
|
|
}
|
|
}
|
|
|
|
SnapDownCurrentWeapon();
|
|
|
|
LighterOff();
|
|
|
|
// Set the player into spiritwalk mode
|
|
bSpiritWalk = true;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::DisableEthereal
|
|
//
|
|
// Returns the player from an ethereal state (used for both spiritwalk and deathwalk)
|
|
// This function does the following:
|
|
// - Clears bSpiritWalk (since this is true for both spirit and death walks)
|
|
// - Removes the proxy
|
|
// - Sets the new collision
|
|
// - Re-enables the weapons
|
|
// - Updates bindings
|
|
// - Fade volume back up on all class 0 sounds
|
|
//=============================================================================
|
|
|
|
void hhPlayer::DisableEthereal( void ) {
|
|
hhFxInfo fxInfo;
|
|
idVec3 boneOffset;
|
|
idMat3 boneAxis;
|
|
|
|
//rww - don't do anything on the client - DO NOT PUT ANYTHING ABOVE HERE
|
|
if (gameLocal.isClient) {
|
|
return;
|
|
}
|
|
|
|
bSpiritWalk = false;
|
|
|
|
// Spawn in an effect when the spirit is snapped back
|
|
GetJointWorldTransform( "waist", boneOffset, boneAxis );
|
|
fxInfo.RemoveWhenDone( true );
|
|
fxInfo.SetNormal( boneAxis[1] );
|
|
BroadcastFxInfo( spawnArgs.GetString( "fx_spiritReturn" ), boneOffset, GetAxis(), &fxInfo );
|
|
|
|
if( spiritProxy.IsValid() ) {
|
|
// Handle case of returning to a body on a rail ride
|
|
idEntity *spiritMaster = spiritProxy->GetBindMaster();
|
|
if (spiritMaster != NULL) {
|
|
if (spiritMaster->IsType(hhBindController::Type)) {
|
|
hhBindController *binder = static_cast<hhBindController *>(spiritMaster);
|
|
bool loose = binder->IsLoose();
|
|
binder->Detach();
|
|
spiritProxy->DeactivateProxy();
|
|
binder->Attach(this, loose);
|
|
} else if (spiritMaster->IsType(hhMonsterAI::Type)) {
|
|
if (reinterpret_cast<hhMonsterAI *> (spiritMaster)->GetEnemy() != static_cast<idActor *> (spiritProxy.GetEntity())) {
|
|
gameLocal.Warning("Monster has spirit proxy bound to it but spirit proxy is not it's enemy!\n");
|
|
}
|
|
spiritProxy->DeactivateProxy();
|
|
} else {
|
|
gameLocal.Warning("Handle this: spiritwalking while bound to non-rail");
|
|
idEntity *temp = spiritProxy.GetEntity(); // this removed by DeactivateProxy
|
|
spiritProxy->DeactivateProxy();
|
|
SwapBindInfo(temp); // untested
|
|
}
|
|
}
|
|
else { // Normal case
|
|
spiritProxy->DeactivateProxy();
|
|
}
|
|
}
|
|
|
|
PutawayEtherealWeapon();
|
|
|
|
GetPhysics()->SetClipMask( MASK_PLAYERSOLID );
|
|
fl.acceptsWounds = true;
|
|
spawnArgs.Set("produces_splats", "1");
|
|
|
|
// Turn off low-pass effect
|
|
if (gameLocal.localClientNum == entityNumber) { //rww - don't do this for anyone but the local client on listen servers
|
|
gameLocal.SpiritWalkSoundMode( false );
|
|
}
|
|
|
|
// HUMANHEAD mdl: Let our enemies know the spirit proxy is going away
|
|
for ( int i = 0; i < hhMonsterAI::allSimpleMonsters.Num(); i++ ) {
|
|
if ( hhMonsterAI::allSimpleMonsters[i]->GetEnemy() == spiritProxy.GetEntity() ) {
|
|
hhMonsterAI::allSimpleMonsters[i]->ProcessEvent( &MA_EnemyIsPhysical, this, spiritProxy.GetEntity() );
|
|
} else if ( hhMonsterAI::allSimpleMonsters[i]->GetEnemy() == this ) { // Targetting spirit that is going away
|
|
hhMonsterAI::allSimpleMonsters[i]->ProcessEvent( &MA_EnemyIsPhysical, this, NULL );
|
|
}
|
|
}
|
|
|
|
//HUMANHEAD rww - clear the focus now that we've changed positions by switching out of spirit mode
|
|
focusTime = 0;
|
|
UpdateFocus();
|
|
//HUMANHEAD END
|
|
|
|
spiritProxy = NULL;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::GetResurrectionPoint
|
|
//
|
|
// Get the position/orientation for resurrection after deathwalk
|
|
//=============================================================================
|
|
void hhPlayer::GetResurrectionPoint( idVec3& origin, idMat3& axis, idVec3& viewDir, idAngles& angles, idMat3& eyeAxis, const idBounds& absBounds, const idVec3& defaultOrigin, const idMat3& defaultAxis, const idVec3& defaultViewDir, const idAngles& defaultAngles ) {
|
|
idClipModel *clipModelList[ MAX_GENTITIES ];
|
|
idClipModel* clipModel = NULL;
|
|
idEntity* entity = NULL;
|
|
hhSafeResurrectionVolume* volume = NULL;
|
|
|
|
int num = gameLocal.clip.ClipModelsTouchingBounds( absBounds, CONTENTS_DEATHVOLUME, clipModelList, MAX_GENTITIES );
|
|
for( int ix = 0; ix < num; ++ix ) {
|
|
clipModel = clipModelList[ix];
|
|
|
|
if( !clipModel ) {
|
|
continue;
|
|
}
|
|
|
|
entity = clipModel->GetEntity();
|
|
if( !entity || !entity->IsType(hhSafeResurrectionVolume::Type) ) {
|
|
continue;
|
|
}
|
|
|
|
volume = static_cast<hhSafeResurrectionVolume*>( entity );
|
|
volume->PickRandomPoint( origin, axis );
|
|
viewDir = axis[0];
|
|
angles = axis.ToAngles();
|
|
eyeAxis = mat3_identity;
|
|
return;
|
|
}
|
|
|
|
eyeAxis = GetEyeAxis();
|
|
origin = defaultOrigin;
|
|
axis = defaultAxis;
|
|
viewDir = defaultViewDir;
|
|
angles = defaultAngles;
|
|
|
|
// gameLocal.Printf("Deathwalk @:\n");
|
|
// gameLocal.Printf(" origin: %s\n", origin.ToString(0));
|
|
// gameLocal.Printf(" axis: %s\n", axis.ToString(2));
|
|
// gameLocal.Printf(" viewDir: %s\n", viewDir.ToString(2));
|
|
// gameLocal.Printf(" angles: %s\n", angles.ToString(0));
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::SquishedByDoor
|
|
//
|
|
//=============================================================================
|
|
void hhPlayer::SquishedByDoor(idEntity *door) {
|
|
if ( door == this ) { // Don't allow the squished code to send a player back it the squisher is the player itself
|
|
return;
|
|
}
|
|
|
|
// If there is a spirit player in the door, make him go back to physical so he doesn't become stuck
|
|
if (IsSpiritWalking()) {
|
|
StopSpiritWalk();
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::UpdatePossession
|
|
//
|
|
// Updates the possession effects (if the player is possessed)
|
|
//
|
|
// HUMANHEAD cjr
|
|
//=============================================================================
|
|
|
|
void hhPlayer::UpdatePossession( void ) {
|
|
|
|
if ( !IsPossessed() ) { // Player is not possessed
|
|
return;
|
|
}
|
|
|
|
/* todo:
|
|
possessionTimer -= MS2SEC( USERCMD_MSEC );
|
|
|
|
// Update a shaderparm for the view screen to update based upon possessionTimer
|
|
possessionMax = spawnArgs.GetFloat( "possessionTime", "8" );
|
|
possessionScale = possessionTimer / possessionMax;
|
|
playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, possessionScale ) );
|
|
|
|
// Mess with the player's FOV while they are being possessed
|
|
possessionFOV = g_fov.GetFloat() + (1.0f - possessionScale) * 60 + 1.5f * sin( MS2SEC(gameLocal.time) * 3 );
|
|
|
|
if ( possessionTimer <= 0 ) { // The player is now fully possessed, kill them
|
|
// TODO: Extra code to show the possessed player running around and such?
|
|
Unpossess();
|
|
Damage( NULL, NULL, vec3_origin, "damage_crush", 1.0f, INVALID_JOINT );
|
|
}
|
|
*/
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::RechargeHealth
|
|
//
|
|
// Recharges the player's health to 25%
|
|
//
|
|
// This is done to avoid gameplay issues where the player has only a few points
|
|
// of health. The game will still be tense if the player is at 25%, but
|
|
// will play more fairly.
|
|
//
|
|
// HUMANHEAD cjr
|
|
//=============================================================================
|
|
|
|
void hhPlayer::Event_RechargeHealth( void ) {
|
|
|
|
if ( health > 0 && health < spawnArgs.GetInt( "healthRecharge", "25" ) && !IsSpiritOrDeathwalking() ) { // Only recharge if the player is alive and in the critical zone
|
|
if ( gameLocal.time - lastDamagedTime >= spawnArgs.GetInt( "healthRechargeDelay", "1500" ) ) { // Delay before recharging
|
|
health++;
|
|
}
|
|
}
|
|
|
|
PostEventSec( &EV_RechargeHealth, spawnArgs.GetFloat( "healthRechargeRate", "0.5" ) );
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::RechargeRifleAmmo
|
|
//
|
|
// Recharges rifle ammo if the player is very low. This is done to guarantee
|
|
// that the player has at least some ammo to solve puzzles that require shooting
|
|
// something such as a gravity switch.
|
|
//
|
|
// HUMANHEAD cjr
|
|
//=============================================================================
|
|
|
|
void hhPlayer::Event_RechargeRifleAmmo( void ) {
|
|
|
|
int ammoIndex = inventory.AmmoIndexForAmmoClass( "ammo_rifle" );
|
|
int ammoCount = inventory.ammo[ ammoIndex ];
|
|
|
|
int maxAmmo = spawnArgs.GetInt( "rifleAmmoRechargeMax", "20" );
|
|
if ( ammoCount < maxAmmo && !AI_ATTACK_HELD ) { // CJR PCF 04/26/06
|
|
inventory.ammo[ ammoIndex ] += 2;
|
|
if ( inventory.ammo[ ammoIndex ] > maxAmmo ) {
|
|
inventory.ammo[ ammoIndex ] = maxAmmo;
|
|
}
|
|
}
|
|
|
|
PostEventSec( &EV_RechargeRifleAmmo, spawnArgs.GetFloat( "rifleAmmoRechargeRate", "2" ) );
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::Event_DrainSpiritPower
|
|
//
|
|
// HUMANHEAD pdm: 10 Hz timer used to drain spirit power at differing rates
|
|
//=============================================================================
|
|
void hhPlayer::Event_DrainSpiritPower() {
|
|
|
|
// JRM: God mode, don't drain!
|
|
if(godmode) {
|
|
return;
|
|
}
|
|
|
|
ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName( "ammo_spiritpower" );
|
|
|
|
if ( gameLocal.isMultiplayer ) { // CJR: Drain spirit power in MP
|
|
if ( !UseAmmo( ammo_spiritpower, 1 ) ) {
|
|
StopSpiritWalk();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// spirit bow alt mode drain
|
|
if( weapon.IsValid() && weapon->IsType(hhWeaponSpiritBow::Type) ) {
|
|
hhWeaponSpiritBow *bow = static_cast<hhWeaponSpiritBow *>( weapon.GetEntity() );
|
|
if( bow->BowVisionIsEnabled() ) {
|
|
if( !UseAmmo(ammo_spiritpower, 1) ) { // Bow vision drains spirit power
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
PostEventMS( &EV_DrainSpiritPower, spiritDrainHeartbeatMS );
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::DeathWalk
|
|
//
|
|
//=============================================================================
|
|
|
|
void hhPlayer::DeathWalk( const idVec3& resurrectOrigin, const idMat3& resurrectBBoxAxis, const idMat3& resurrectViewAxis, const idAngles& resurrectViewAngles, const idMat3& resurrectEyeAxis ) {
|
|
if ( IsSpiritWalking() ) { // Ensure that two proxies cannot be active at the same time
|
|
StopSpiritWalk();
|
|
}
|
|
|
|
bDeathWalk = true;
|
|
deathWalkTime = gameLocal.time; // Get the time of death
|
|
|
|
health = 0; // Force health to zero
|
|
|
|
deathWalkPower = spawnArgs.GetInt( "deathWalkPowerStart" ); // Power in deathwalk. When full, the player will return
|
|
fl.takedamage = false; // can no longer be damaged in deathwalk mode
|
|
|
|
if (hud) {
|
|
hud->HandleNamedEvent("SwitchToEthereal");
|
|
}
|
|
|
|
// Disable clip until we get to deathwalk
|
|
bInDeathwalkTransition = true;
|
|
|
|
//rww - for deathwalk, let's store these values on the player instead of relying on the death proxy
|
|
deathwalkLastOrigin = resurrectOrigin;
|
|
deathwalkLastBBoxAxis = resurrectBBoxAxis;
|
|
deathwalkLastViewAxis = resurrectViewAxis;
|
|
deathwalkLastViewAngles = resurrectViewAngles;
|
|
deathwalkLastEyeAxis = resurrectEyeAxis;
|
|
deathwalkLastCrouching = IsCrouching();
|
|
|
|
EnableEthereal( spawnArgs.GetString("def_deathProxy"), resurrectOrigin, resurrectBBoxAxis, resurrectViewAxis, resurrectViewAngles, resurrectEyeAxis );
|
|
// Set the overlay material for DeathWalk
|
|
playerView.SetViewOverlayColor( colorWhite ); // Guarantee that the color is reset
|
|
PostEventMS( &EV_SetOverlayMaterial, 1, spawnArgs.GetString("mtr_deathWalk"), false );
|
|
|
|
//Don't allow weapon use for a bit
|
|
if ( weapon.GetEntity() ) {
|
|
weapon.GetEntity()->PutAway();
|
|
}
|
|
|
|
// Alert the scripts that death has occured so they can manage music
|
|
idEntity *dwJustDied = gameLocal.FindEntity("dw_justdied");
|
|
if (dwJustDied) {
|
|
dwJustDied->PostEventMS(&EV_Activate, 0, this);
|
|
}
|
|
|
|
deathwalkSoundController.StartSound( SND_CHANNEL_BODY, SND_CHANNEL_BODY2 );
|
|
|
|
// Flash the screen before entering death world
|
|
playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, 0.0f ) );
|
|
deathWalkFlash = 0.0f;
|
|
possessionFOV = 90.0f;
|
|
|
|
CancelEvents( &EV_RechargeHealth );
|
|
CancelEvents( &EV_RechargeRifleAmmo );
|
|
|
|
// If this is our first time, tell the script
|
|
if (!gameLocal.IsCompetitive() && !inventory.bHasDeathwalked) {
|
|
const function_t *firstFunc = gameLocal.program.FindFunction( "map_deathwalk::FirstDeathwalk" );
|
|
if ( firstFunc ) {
|
|
idThread *thread = new idThread();
|
|
thread->CallFunction( firstFunc, false );
|
|
thread->DelayedStart( 0 );
|
|
}
|
|
}
|
|
|
|
//rww - if there is a deathwalk portal, let's create a target for it and activate it
|
|
if (!gameLocal.FindEntity("dw_deathLocation")) { //check to see if we've made ourselves a target yet
|
|
idEntity *dwPortal = gameLocal.FindEntity( "dw_deathPortal" );
|
|
if (dwPortal) {
|
|
trace_t tr;
|
|
idVec3 camPos, camDir;
|
|
idAngles camAngles;
|
|
idDict args;
|
|
|
|
//FIXME more complex logic that allows angles to change based on collision circumstances
|
|
//do wider trace that takes the actual view frustum of camera into account
|
|
//check for collisions with the actual view "plane"
|
|
camPos = resurrectOrigin;
|
|
camPos += resurrectBBoxAxis[2] * 64.0f;
|
|
gameLocal.clip.TracePoint(tr, resurrectOrigin, camPos, CONTENTS_SOLID, this);
|
|
camDir = resurrectOrigin-camPos;
|
|
camAngles = camDir.ToAngles();
|
|
|
|
args.Clear();
|
|
args.SetVector("origin", tr.endpos);
|
|
args.SetAngles("angles", camAngles);
|
|
args.SetMatrix("rotation", camAngles.ToMat3());
|
|
args.Set("name", "dw_deathLocation");
|
|
gameLocal.SpawnObject("info_null", &args);
|
|
|
|
dwPortal->ProcessEvent(&EV_UpdateCameraTarget); //update for the newly placed cameraTarget
|
|
dwPortal->PostEventMS(&EV_Activate, 0, this);
|
|
if (dwPortal->cameraTarget) { //if camera target was legitimate, force an update to the remoteRenderView now
|
|
dwPortal->GetRenderEntity()->remoteRenderView = dwPortal->cameraTarget->GetRenderView();
|
|
}
|
|
}
|
|
}
|
|
|
|
Hide(); // Don't show the player when they are in the limbo state between alive and dead
|
|
PostEventSec( &EV_PrepareForDeathWorld, spawnArgs.GetFloat( "prepareForDeathWorldDelay", "1.5" ) );
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::Event_PrepareForDeathWorld
|
|
//
|
|
// Flashes the screen / fades the FOV
|
|
//=============================================================================
|
|
|
|
void hhPlayer::Event_PrepareForDeathWorld() {
|
|
deathWalkFlash += spawnArgs.GetFloat( "deathWalkFlashChange", "0.05" );
|
|
if ( deathWalkFlash >= 1.0f ) {
|
|
deathWalkFlash = 1.0f;
|
|
PostEventSec( &EV_EnterDeathWorld, 0.0f ); // Actually send player into the deathworld
|
|
} else {
|
|
PostEventSec( &EV_PrepareForDeathWorld, 0.02f );
|
|
}
|
|
|
|
possessionFOV += spawnArgs.GetFloat( "deathWalkFOVChange", "3.0" );
|
|
playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, deathWalkFlash ) );
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::Event_EnterDeathWorld
|
|
//
|
|
// - Teleports the player to the death world
|
|
// - Spawns the death wraiths
|
|
// - Gives the player a weapon
|
|
//=============================================================================
|
|
|
|
void hhPlayer::Event_EnterDeathWorld() {
|
|
// Reset player view information
|
|
playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, 0.0f ) ); // Turn off the flash
|
|
possessionFOV = 0.0f;
|
|
bDeathWalkStage2 = false;
|
|
|
|
// Spawn in DeathWraiths after a short period of time
|
|
if ( !inventory.bHasDeathwalked ) { // Delay the wraiths the first time the player deathwalks
|
|
int firstDelay = spawnArgs.GetInt( "deathWalk_firstTimeDelay", "15000" );
|
|
PostEventMS( &EV_SpawnDeathWraith, firstDelay ); // CJR: delay the deathwraiths for several seconds while the Grandfather DW speech is going on
|
|
PostEventMS( &EV_AdjustSpiritPowerDeathWalk, firstDelay + 2000 );
|
|
inventory.bHasDeathwalked = true;
|
|
} else {
|
|
PostEventMS( &EV_SpawnDeathWraith, 0 ); // CJR: Non-first time, spawn the wraiths instantly
|
|
PostEventMS( &EV_AdjustSpiritPowerDeathWalk, 2000 );
|
|
}
|
|
|
|
// Give the player the spirit bow
|
|
SelectEtherealWeapon();
|
|
|
|
// get the entity which tells us where we want to place our dw proxy
|
|
idEntity *dwProxyPosEnt = gameLocal.FindEntity( "dw_floatingBodyMarker" );
|
|
if (dwProxyPosEnt) {
|
|
//if we found that, then this is the new deathwalk, so create the deathwalk proxy.
|
|
hhDeathWalkProxy *dwProxy = (hhDeathWalkProxy *)gameLocal.SpawnObject( spawnArgs.GetString("def_deathWalkProxy"), NULL );
|
|
if (!dwProxy) {
|
|
gameLocal.Error("hhPlayer::Event_EnterDeathWorld: Could not create hhDeathWalkProxy");
|
|
return;
|
|
}
|
|
idAngles dwProxyAngles;
|
|
dwProxyPosEnt->spawnArgs.GetAngles("angles", "0 0 0", dwProxyAngles);
|
|
dwProxy->ActivateProxy(this, dwProxyPosEnt->GetOrigin(), dwProxyAngles.ToMat3(), dwProxyAngles.ToMat3(), dwProxyAngles, GetEyeAxis());
|
|
}
|
|
|
|
idEntity* deathWalkPlayerStartManager = gameLocal.FindEntity( "dw_deathWalkPlayerStartManager" );
|
|
if( !deathWalkPlayerStartManager ) {
|
|
return;
|
|
}
|
|
|
|
idEntity* deathWalkStart = deathWalkPlayerStartManager->PickRandomTarget();
|
|
if( deathWalkStart ) {
|
|
deathWalkStart->ProcessEvent( &EV_Activate, this );
|
|
}
|
|
|
|
// Trigger the 'DeathWalkEntered' entity
|
|
// This allows the scripters to do something upon entering deathwalk
|
|
idEntity *dwe = gameLocal.FindEntity("dw_DeathWalkEntered");
|
|
if (dwe) {
|
|
dwe->PostEventMS(&EV_Activate, 0, this);
|
|
}
|
|
|
|
// Allow collisions again now that we're in the death world
|
|
bInDeathwalkTransition = false;
|
|
Show(); // The player is visible in deathmode
|
|
|
|
// Reset the player's health and spirit power
|
|
ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName( "ammo_spiritpower" );
|
|
inventory.ammo[ ammo_spiritpower ] = 0;
|
|
SetHealth( hhMath::hhMax( health, spawnArgs.GetInt("minResurrectHealth", "50") ) );
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::Event_AdjustSpiritPowerDeathWalk
|
|
//
|
|
//=============================================================================
|
|
void hhPlayer::Event_AdjustSpiritPowerDeathWalk() {
|
|
if( !IsDeathWalking() ) {
|
|
return;
|
|
}
|
|
|
|
// Increase deathwalk power
|
|
int power = GetDeathWalkPower();
|
|
power += spawnArgs.GetInt( "deathWraithPowerAmount" );
|
|
SetDeathWalkPower( power );
|
|
|
|
// Check if deathWalkPower has exceeded the maximum
|
|
if ( deathWalkPower >= spawnArgs.GetInt( "deathWalkPowerMax", "1000") ) {
|
|
DeathWalkSuccess();
|
|
return;
|
|
}
|
|
|
|
CancelEvents( &EV_AdjustSpiritPowerDeathWalk );
|
|
PostEventSec( &EV_AdjustSpiritPowerDeathWalk, spawnArgs.GetFloat("deathWalkDeathPowerIncreaseRate") );
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::DeathWalkSuccess
|
|
//
|
|
// Called when deathwalk timer runs out or we run out of spirit power
|
|
//=============================================================================
|
|
void hhPlayer::DeathWalkSuccess() {
|
|
CancelEvents( &EV_AdjustSpiritPowerDeathWalk );
|
|
bDeathWalkStage2 = true;
|
|
|
|
idEntity *dw_deathWalkResult = gameLocal.FindEntity("dw_deathWalkSuccess");
|
|
if (dw_deathWalkResult) { //if we have a result, activate it.
|
|
dw_deathWalkResult->ProcessEvent(&EV_Activate, this);
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::ReallyKilled
|
|
//
|
|
// Called when the player should be truly killed
|
|
// Funnels through Killed(), but disabled the deathwalk functionality
|
|
//=============================================================================
|
|
|
|
void hhPlayer::ReallyKilled( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
|
|
if ( !IsSpiritOrDeathwalking() ) {
|
|
return;
|
|
}
|
|
|
|
bDeathWalk = false;
|
|
bSpiritWalk = false;
|
|
|
|
bReallyDead = true;
|
|
|
|
// Turn off low-pass effect
|
|
if (!gameLocal.isClient) { //rww
|
|
if (gameLocal.localClientNum == entityNumber) { //rww - don't do this for anyone but the local client on listen servers
|
|
gameLocal.SpiritWalkSoundMode( false );
|
|
gameLocal.DialogSoundMode( false );
|
|
}
|
|
}
|
|
|
|
if (hud) {
|
|
hud->HandleNamedEvent("SwitchFromEthereal");
|
|
}
|
|
|
|
Killed( inflictor, attacker, damage, dir, location );
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::Event_SpawnDeathWraith
|
|
//
|
|
// Spawns in a special type of wraith that is only visible in deathwalk
|
|
//=============================================================================
|
|
|
|
void hhPlayer::Event_SpawnDeathWraith() {
|
|
idDict args;
|
|
idEntity *ent;
|
|
int maxDeathWalkWraiths = spawnArgs.GetInt( "deathWalkMaxWraiths" );
|
|
|
|
if ( !IsDeathWalking() ) { // Don't spawn in a wraith if the player has resurrected
|
|
return;
|
|
}
|
|
|
|
float angle = hhMath::PI * gameLocal.random.RandomFloat();
|
|
idVec3 startPoint = idVec3( hhMath::Sin(angle) * 600.0f, hhMath::Cos(angle) * 600.0f, 0.0f ) - GetAxis()[2] * 100.0f;
|
|
|
|
args.Clear();
|
|
args.SetVector( "origin", GetEyePosition() + startPoint );
|
|
args.SetMatrix( "rotation", idAngles( 0.0f, (-viewAxis[0]).ToYaw(), 0.0f ).ToMat3() );
|
|
ent = gameLocal.SpawnObject( spawnArgs.GetString("def_deathWraith"), &args );
|
|
if ( ent ) {
|
|
hhDeathWraith *wraith = static_cast< hhDeathWraith * > ( ent );
|
|
wraith->SetEnemy( this );
|
|
}
|
|
|
|
// Check the number of deathwraiths in the world
|
|
int count = 0;
|
|
for( ent = gameLocal.activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) {
|
|
if ( ent && ent->IsType( hhDeathWraith::Type ) ) {
|
|
count++;
|
|
}
|
|
}
|
|
|
|
if ( count < maxDeathWalkWraiths ) {
|
|
PostEventMS( &EV_SpawnDeathWraith, 0 );
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::GetDeathwalkEnergyDestination
|
|
//
|
|
//=============================================================================
|
|
idEntity *hhPlayer::GetDeathwalkEnergyDestination() {
|
|
return gameLocal.FindEntityOfType(hhDeathWalkProxy::Type, NULL);
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::DeathWalkDamagedByWraith
|
|
//
|
|
//=============================================================================
|
|
void hhPlayer::DeathWalkDamagedByWraith(idEntity *attacker, const char *damageType) {
|
|
Damage( attacker, attacker, vec3_origin, damageType, spawnArgs.GetFloat( "deathWalkWraithDamage", "10" ), INVALID_JOINT );
|
|
|
|
CancelEvents( &EV_SpawnDeathWraith );
|
|
PostEventMS( &EV_SpawnDeathWraith, 0 );
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::KilledDeathWraith
|
|
//
|
|
//=============================================================================
|
|
|
|
void hhPlayer::KilledDeathWraith( void ) {
|
|
CancelEvents( &EV_SpawnDeathWraith );
|
|
PostEventMS( &EV_SpawnDeathWraith, 0 );
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::DeathWraithEnergyArived
|
|
//
|
|
//=============================================================================
|
|
|
|
void hhPlayer::DeathWraithEnergyArived(bool energyHealth) {
|
|
if (energyHealth) {
|
|
const char *healthAmount = spawnArgs.GetString("deathWraithHealthAmount");
|
|
Give( "health", healthAmount );
|
|
}
|
|
else {
|
|
Give( "ammo_spiritpower", spawnArgs.GetString( "deathWraithSpiritAmount" ) );
|
|
}
|
|
|
|
// Bump the deathwalk power up a bit so the player lowers faster
|
|
int power = GetDeathWalkPower();
|
|
power += spawnArgs.GetInt( "deathWalkPowerEnergyBoost", "100" );
|
|
SetDeathWalkPower( power );
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::Resurrect
|
|
//
|
|
//=============================================================================
|
|
|
|
void hhPlayer::Resurrect( void ) {
|
|
|
|
bDeathWalk = false;
|
|
|
|
possessionFOV = 0.0f;
|
|
|
|
PostEventSec( &EV_AllowDamage, 3 ); // Re-enable damage in 3 seconds
|
|
fl.noknockback = false; // Restore knockback ability
|
|
|
|
// Cancel the impending translation to deathworld (only happens if the player resurrect cheats early)
|
|
CancelEvents( &EV_PrepareForDeathWorld );
|
|
CancelEvents( &EV_EnterDeathWorld );
|
|
CancelEvents( &EV_SpawnDeathWraith );
|
|
|
|
DisableEthereal();
|
|
|
|
// Reset the view overlay
|
|
playerView.SetViewOverlayTime( 0, true );
|
|
playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, 0.0f ) );
|
|
|
|
//rww - deathwalk position is restored from the player now.
|
|
RestorePlayerLocationFromDeathwalk(deathwalkLastOrigin, deathwalkLastBBoxAxis, deathwalkLastViewAxis[0], deathwalkLastViewAngles);
|
|
|
|
deathwalkSoundController.StopSound( SND_CHANNEL_BODY, SND_CHANNEL_BODY2 );
|
|
|
|
ProcessEvent( &EV_RechargeHealth );
|
|
ProcessEvent( &EV_RechargeRifleAmmo );
|
|
|
|
if (hud) {
|
|
hud->HandleNamedEvent("SwitchFromEthereal");
|
|
}
|
|
|
|
// TODO: Build in an autoimmune time?
|
|
lastResurrectTime = gameLocal.GetTime();
|
|
|
|
// Radius blast (doesn't damage enemies / objects, only shoves them back)
|
|
gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, this, this, this, spawnArgs.GetString("def_resurrect_damage") );
|
|
|
|
Show(); // Ensure that the player is visible when they resurrect
|
|
|
|
// trigger the 'DeathWalkExited' entity
|
|
idEntity *dwe = gameLocal.FindEntity("dw_DeathWalkExited");
|
|
if (dwe) {
|
|
dwe->PostEventMS(&EV_Activate, 0, this);
|
|
}
|
|
|
|
// bg: Visual effect for returning from DeathWalk uses landing camera movement.
|
|
landChange = -10;
|
|
landTime = gameLocal.time;
|
|
|
|
//rww - deactivate dw portal and remove cam target
|
|
idEntity *dwPortal = gameLocal.FindEntity( "dw_deathPortal" );
|
|
if (dwPortal && dwPortal->IsActive()) {
|
|
dwPortal->cameraTarget = NULL; //reset the camera target
|
|
dwPortal->PostEventMS(&EV_Activate, 0, dwPortal);
|
|
}
|
|
idEntity *dwCamTarget = gameLocal.FindEntity( "dw_deathLocation" );
|
|
if (dwCamTarget) {
|
|
dwCamTarget->PostEventMS(&EV_Remove, 50); //make sure this is removed after the portal has stopped.
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
hhPlayer::IsWallWalking
|
|
============
|
|
*/
|
|
bool hhPlayer::IsWallWalking( void ) const {
|
|
return( physicsObj.IsWallWalking() );
|
|
}
|
|
|
|
/*
|
|
============
|
|
hhPlayer::WasWallWalking
|
|
============
|
|
*/
|
|
bool hhPlayer::WasWallWalking( void ) const {
|
|
return( physicsObj.WasWallWalking() );
|
|
}
|
|
|
|
/*
|
|
============
|
|
hhPlayer::MangleControls
|
|
============
|
|
*/
|
|
void hhPlayer::MangleControls( usercmd_t *cmd ) {
|
|
// When frozen in a cinematic, we want to restrict any movement and disallow many features
|
|
if (guiWantsControls.IsValid()) {
|
|
if (cmd->buttons & BUTTON_ATTACK_ALT) {
|
|
guiWantsControls->LockedGuiReleased(this);
|
|
guiWantsControls = NULL; // release
|
|
}
|
|
else {
|
|
guiWantsControls->PlayerControls(cmd);
|
|
}
|
|
cmd->forwardmove = 0;
|
|
cmd->rightmove = 0;
|
|
cmd->upmove = 0;
|
|
cmd->buttons &= ~(BUTTON_ATTACK|BUTTON_ZOOM|BUTTON_ATTACK_ALT);
|
|
cmd->impulse = 0;
|
|
}
|
|
else if (InCinematic()) {
|
|
cmd->forwardmove = 0;
|
|
cmd->rightmove = 0;
|
|
cmd->upmove = 0;
|
|
cmd->buttons &= ~(BUTTON_ATTACK|BUTTON_ZOOM|BUTTON_ATTACK_ALT);
|
|
cmd->impulse = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
hhPlayer::GetPilotInput
|
|
|
|
Called from PilotVehicleInterface
|
|
============
|
|
*/
|
|
void hhPlayer::GetPilotInput( usercmd_t& pilotCmds, idAngles& pilotViewAngles ) {
|
|
pilotCmds = gameLocal.usercmds[ entityNumber ];
|
|
pilotViewAngles = DetermineViewAngles( pilotCmds, cmdAngles );
|
|
}
|
|
|
|
/*
|
|
==============
|
|
hhPlayer::Think
|
|
PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge
|
|
==============
|
|
*/
|
|
void hhPlayer::Think( void ) {
|
|
renderEntity_t *headRenderEnt;
|
|
|
|
UpdatePossession();
|
|
|
|
UpdatePlayerIcons();
|
|
|
|
// latch button actions
|
|
oldButtons = usercmd.buttons;
|
|
|
|
// grab out usercmd
|
|
usercmd_t oldCmd = usercmd;
|
|
usercmd = gameLocal.usercmds[ entityNumber ];
|
|
buttonMask &= usercmd.buttons;
|
|
usercmd.buttons &= ~buttonMask;
|
|
|
|
if ( gameLocal.inCinematic && gameLocal.skipCinematic ) {
|
|
return;
|
|
}
|
|
|
|
//HUMANHEAD rww - keep air attacker timer refreshed
|
|
if (!AI_ONGROUND && airAttackerTime > gameLocal.time) {
|
|
airAttackerTime = gameLocal.time + 300;
|
|
}
|
|
|
|
// HUMANHEAD pdm: Depending on our mode, might want to modify the input
|
|
MangleControls( &usercmd );
|
|
// HUMANHEAD END
|
|
|
|
// clear the ik before we do anything else so the skeleton doesn't get updated twice
|
|
walkIK.ClearJointMods();
|
|
|
|
// if this is the very first frame of the map, set the delta view angles
|
|
// based on the usercmd angles
|
|
if ( !spawnAnglesSet && ( gameLocal.GameState() != GAMESTATE_STARTUP ) ) {
|
|
spawnAnglesSet = true;
|
|
SetViewAngles( spawnAngles );
|
|
oldFlags = usercmd.flags;
|
|
}
|
|
|
|
if ( gameLocal.inCinematic || influenceActive ) {
|
|
usercmd.forwardmove = 0;
|
|
usercmd.rightmove = 0;
|
|
usercmd.upmove = 0;
|
|
}
|
|
|
|
// log movement changes for weapon bobbing effects
|
|
if ( usercmd.forwardmove != oldCmd.forwardmove ) {
|
|
loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)];
|
|
currentLoggedAccel++;
|
|
acc->time = gameLocal.time;
|
|
acc->dir[0] = usercmd.forwardmove - oldCmd.forwardmove;
|
|
acc->dir[1] = acc->dir[2] = 0;
|
|
}
|
|
|
|
if ( usercmd.rightmove != oldCmd.rightmove ) {
|
|
loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)];
|
|
currentLoggedAccel++;
|
|
acc->time = gameLocal.time;
|
|
acc->dir[1] = usercmd.rightmove - oldCmd.rightmove;
|
|
acc->dir[0] = acc->dir[2] = 0;
|
|
}
|
|
|
|
// freelook centering
|
|
if ( ( usercmd.buttons ^ oldCmd.buttons ) & BUTTON_MLOOK ) {
|
|
centerView.Init( gameLocal.time, 200, viewAngles.pitch, 0 );
|
|
}
|
|
|
|
// zooming
|
|
if ( ( usercmd.buttons ^ oldCmd.buttons ) & BUTTON_ZOOM ) {
|
|
if ( ( usercmd.buttons & BUTTON_ZOOM ) && weapon.GetEntity() ) {
|
|
zoomFov.Init( gameLocal.time, 200.0f, CalcFov( false ), weapon.GetEntity()->GetZoomFov() );
|
|
} else {
|
|
zoomFov.Init( gameLocal.time, 200.0f, zoomFov.GetCurrentValue( gameLocal.time ), DefaultFov() );
|
|
}
|
|
}
|
|
|
|
if ( g_fov.IsModified() ) {
|
|
idEntity *weaponEnt = weapon.GetEntity();
|
|
if ( ! ( weaponEnt &&
|
|
weaponEnt->IsType( hhWeaponZoomable::Type ) &&
|
|
reinterpret_cast<hhWeaponZoomable *> (weaponEnt)->IsZoomed() ) )
|
|
{
|
|
GetZoomFov().Init( gameLocal.GetTime(), 0.0f, CalcFov(true), g_fov.GetInteger() );
|
|
g_fov.ClearModified();
|
|
}
|
|
}
|
|
|
|
// if we have an active gui, we will unrotate the view angles as
|
|
// we turn the mouse movements into gui events
|
|
idUserInterface *gui = ActiveGui();
|
|
if ( gui && gui != focusUI ) {
|
|
RouteGuiMouse( gui );
|
|
}
|
|
|
|
// set the push velocity on the weapon before running the physics
|
|
if ( weapon.GetEntity() ) {
|
|
weapon.GetEntity()->SetPushVelocity( physicsObj.GetPushedLinearVelocity() );
|
|
}
|
|
|
|
EvaluateControls();
|
|
|
|
if ( !af.IsActive() ) {
|
|
AdjustBodyAngles();
|
|
CopyJointsFromBodyToHead();
|
|
}
|
|
|
|
//HUMANHEAD: aob - added vehicle check. Vehicle moves us
|
|
if( !InVehicle() ) {
|
|
Move();
|
|
}
|
|
//HUMANHEAD END
|
|
|
|
if ( !g_stopTime.GetBool() ) {
|
|
//HUMANHEAD: aob - changed heath check to IsDead check. Player needs to touch triggers when deathwalking.
|
|
if ( !noclip && !spectating && !IsDead() && !IsHidden() ) {
|
|
TouchTriggers();
|
|
}
|
|
|
|
UpdateLighter();
|
|
|
|
// update GUIs, Items, and character interactions
|
|
UpdateFocus();
|
|
|
|
UpdateLocation();
|
|
|
|
// update player script
|
|
UpdateScript();
|
|
|
|
// service animations
|
|
if ( !spectating && !af.IsActive() && !gameLocal.inCinematic ) {
|
|
UpdateConditions();
|
|
UpdateAnimState();
|
|
CheckBlink();
|
|
}
|
|
|
|
// clear out our pain flag so we can tell if we recieve any damage between now and the next time we think
|
|
AI_PAIN = false;
|
|
}
|
|
|
|
// calculate the exact bobbed view position, which is used to
|
|
// position the view weapon, among other things
|
|
CalculateFirstPersonView();
|
|
|
|
// this may use firstPersonView, or a thirdPeroson / camera view
|
|
CalculateRenderView();
|
|
|
|
if ( spectating ) {
|
|
if (!gameLocal.isClient) {
|
|
UpdateSpectating();
|
|
}
|
|
//HUMANHEAD: aob - changed heath check to IsDead check. Player needs to update weapon when deathwalking
|
|
} else if ( !IsDead() && !bFrozen ) { //HUMANHEAD bjk PCF (4-27-06) - no setting unnecessary weapon state
|
|
if ( !gameLocal.isClient || weapon.GetEntity()) {
|
|
UpdateWeapon();
|
|
}
|
|
}
|
|
|
|
if (InVehicle()) {
|
|
UpdateHud( GetVehicleInterfaceLocal()->GetHUD() );
|
|
}
|
|
else {
|
|
UpdateHud( hud );
|
|
}
|
|
|
|
UpdateDeathSkin( false );
|
|
|
|
if ( gameLocal.isMultiplayer ) {
|
|
DrawPlayerIcons();
|
|
}
|
|
|
|
if ( head.GetEntity() ) {
|
|
headRenderEnt = head.GetEntity()->GetRenderEntity();
|
|
} else {
|
|
headRenderEnt = NULL;
|
|
}
|
|
|
|
if ( gameLocal.isMultiplayer || g_showPlayerShadow.GetBool() ) {
|
|
renderEntity.suppressShadowInViewID = 0;
|
|
if ( headRenderEnt ) {
|
|
headRenderEnt->suppressShadowInViewID = 0;
|
|
}
|
|
} else {
|
|
renderEntity.suppressShadowInViewID = entityNumber+1;
|
|
if ( headRenderEnt ) {
|
|
headRenderEnt->suppressShadowInViewID = entityNumber+1;
|
|
}
|
|
}
|
|
// never cast shadows from our first-person muzzle flashes
|
|
renderEntity.suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber;
|
|
if ( headRenderEnt ) {
|
|
headRenderEnt->suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber;
|
|
}
|
|
|
|
if ( !g_stopTime.GetBool() ) {
|
|
UpdateAnimation();
|
|
Present();
|
|
UpdateDamageEffects();
|
|
if (gameLocal.isMultiplayer) { //rww
|
|
UpdateWounds();
|
|
}
|
|
LinkCombat();
|
|
playerView.CalculateShake();
|
|
}
|
|
|
|
if ( g_showEnemies.GetBool() ) {
|
|
idActor *ent;
|
|
int num = 0;
|
|
for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) {
|
|
gameLocal.Printf( "enemy (%d)'%s'\n", ent->entityNumber, ent->name.c_str() );
|
|
gameRenderWorld->DebugBounds( colorRed, ent->GetPhysics()->GetBounds().Expand( 2 ), ent->GetPhysics()->GetOrigin() );
|
|
num++;
|
|
}
|
|
gameLocal.Printf( "%d: enemies\n", num );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
hhPlayer::AdjustBodyAngles
|
|
==============
|
|
*/
|
|
void hhPlayer::AdjustBodyAngles( void ) {
|
|
if (InVehicle()) {
|
|
return;
|
|
}
|
|
|
|
if (bClampYaw) { //rww - for slabs
|
|
//first, clamp the viewangles while bound to a slab
|
|
if (maxRelativePitch >= 0.0f) {
|
|
if (untransformedViewAngles.pitch > maxRelativePitch) {
|
|
untransformedViewAngles.pitch = maxRelativePitch;
|
|
SetViewAngles(untransformedViewAngles);
|
|
}
|
|
else if (untransformedViewAngles.pitch < -maxRelativePitch) {
|
|
untransformedViewAngles.pitch = -maxRelativePitch;
|
|
SetViewAngles(untransformedViewAngles);
|
|
}
|
|
}
|
|
|
|
animator.SetJointAxis( hipJoint, JOINTMOD_WORLD, mat3_identity ); //no leg offset while slabbed
|
|
|
|
#if 0 //old headlook code
|
|
if (head.IsValid() && head.GetEntity()) { //has a head
|
|
idVec3 origin;
|
|
idMat3 axis;
|
|
|
|
head->GetPhysics()->Evaluate(gameLocal.time-gameLocal.previousTime, gameLocal.time); //make sure it is bolted up to date
|
|
|
|
if (GetMasterPosition(origin, axis)) {
|
|
idAngles masterAngles = axis.ToAngles(); //bound angles
|
|
idAngles ang;
|
|
idMat3 rot;
|
|
|
|
//determine our "look" angles
|
|
ang.yaw = viewAngles.yaw-masterAngles.yaw;
|
|
ang.pitch = viewAngles.pitch;
|
|
ang.roll = 0.0f;
|
|
|
|
ang = ang.Normalize180();
|
|
ang *= 0.5f; //scale the angles down so the head doesn't go completely sideways or anything
|
|
|
|
//set the yaw only
|
|
animator.SetJointAxis(headJoint, JOINTMOD_WORLD, idAngles(0.0f, ang.yaw, 0.0f).ToMat3());
|
|
ang.yaw = 0.0f;
|
|
|
|
hhMath::BuildRotationMatrix(DEG2RAD(-ang.pitch), 0, rot); //create a rotation matrix for the pitch
|
|
|
|
//get the local axis to multiply it by the pitch rotation matrix
|
|
animator.GetJointLocalTransform(headJoint, gameLocal.time, origin, axis);
|
|
|
|
//use the multiplied axis
|
|
animator.SetJointAxis(headJoint, JOINTMOD_LOCAL, rot*axis); //point head in proper direction
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
else {
|
|
#if 0 //old headlook code
|
|
//rww - reset head joint override when not on a slab
|
|
animator.SetJointAxis( headJoint, JOINTMOD_LOCAL, idAngles( 0.0f, 0.0f, 0.0f ).ToMat3() );
|
|
#endif
|
|
idPlayer::AdjustBodyAngles();
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
hhPlayer::Move
|
|
PDMMERGE PERSISTENTMERGE: Overridden, Done for 6-03-05 merge
|
|
==============
|
|
*/
|
|
void hhPlayer::Move( void ) {
|
|
float newEyeOffset;
|
|
idVec3 oldOrigin;
|
|
idVec3 oldVelocity;
|
|
idVec3 pushVelocity;
|
|
|
|
// save old origin and velocity for crashlanding
|
|
oldOrigin = physicsObj.GetOrigin();
|
|
oldVelocity = physicsObj.GetLinearVelocity();
|
|
pushVelocity = physicsObj.GetPushedLinearVelocity();
|
|
|
|
// set physics variables
|
|
physicsObj.SetMaxStepHeight( GetStepHeight() );//HUMANHEAD: aob
|
|
physicsObj.SetMaxJumpHeight( pm_jumpheight.GetFloat() );
|
|
|
|
if ( noclip ) {
|
|
physicsObj.SetContents( 0 );
|
|
physicsObj.SetMovementType( PM_NOCLIP );
|
|
} else if ( spectating ) {
|
|
physicsObj.SetContents( 0 );
|
|
physicsObj.SetMovementType( PM_SPECTATOR );
|
|
} else if ( bInDeathwalkTransition ) { // In between life and death, don't allow ragdoll to collide with player
|
|
physicsObj.SetContents( 0 );
|
|
physicsObj.SetMovementType( PM_NORMAL );
|
|
} else if ( IsDead() ) { // HUMANHEAD cjr: Replaced health <= 0 with IsDead() call for deathwalk override
|
|
if (gameLocal.isMultiplayer) { // HUMANHEAD rww - contents 0 because we use a dead body proxy in mp
|
|
physicsObj.SetContents(0);
|
|
}
|
|
else {
|
|
physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP );
|
|
} //HUMANHEAD END
|
|
physicsObj.SetMovementType( PM_DEAD );
|
|
} else if ( gameLocal.inCinematic || gameLocal.GetCamera() || privateCameraView ) {
|
|
physicsObj.SetContents( CONTENTS_BODY );
|
|
physicsObj.SetMovementType( PM_FREEZE );
|
|
} else if ( bFrozen && !IsSpiritOrDeathwalking() ) { // HUMANHEAD: freeze support
|
|
physicsObj.SetContents( CONTENTS_BODY );
|
|
physicsObj.SetMovementType( PM_FREEZE );
|
|
} else {
|
|
physicsObj.SetContents( CONTENTS_BODY );
|
|
physicsObj.SetMovementType( PM_NORMAL );
|
|
}
|
|
|
|
if ( spectating ) {
|
|
physicsObj.SetClipMask( MASK_SPECTATOR );
|
|
} else if ( IsDead() ) { // HUMANHEAD cjr: Replaced health <= 0 with IsDead() call for deathwalk override
|
|
physicsObj.SetClipMask( MASK_DEADSOLID );
|
|
// HUMANHEAD cjr: allow spirit walk
|
|
} else if ( IsSpiritOrDeathwalking() ) {
|
|
physicsObj.SetClipMask( MASK_SPIRITPLAYER );
|
|
// HUMANHEAD END
|
|
} else {
|
|
physicsObj.SetClipMask( MASK_PLAYERSOLID );
|
|
}
|
|
|
|
physicsObj.SetDebugLevel( g_debugMove.GetBool() );
|
|
physicsObj.SetPlayerInput( usercmd, viewAngles );
|
|
|
|
//HUMANHEAD: aob - moved down a few lines
|
|
// FIXME: physics gets disabled somehow
|
|
//BecomeActive( TH_PHYSICS );
|
|
//RunPhysics();
|
|
//HUMANHEAD END
|
|
|
|
// update our last valid AAS location for the AI
|
|
SetAASLocation();
|
|
|
|
if ( spectating ) {
|
|
newEyeOffset = 0.0f;
|
|
// HUMANHEAD cjr: Replaced health <= 0 with IsDead() call for deathwalk override
|
|
} else if ( IsDead() ) {
|
|
newEyeOffset = pm_deadviewheight.GetFloat();
|
|
} else if ( physicsObj.IsCrouching() ) {
|
|
newEyeOffset = pm_crouchviewheight.GetFloat();
|
|
} else if ( GetBindMaster() && GetBindMaster()->IsType( idAFEntity_Vehicle::Type ) ) {
|
|
newEyeOffset = 0.0f;
|
|
} else {
|
|
newEyeOffset = pm_normalviewheight.GetFloat();
|
|
}
|
|
|
|
if ( EyeHeight() != newEyeOffset ) {
|
|
//AOB: camera does smoothing
|
|
SetEyeHeight( newEyeOffset );
|
|
}
|
|
|
|
// HUMANHEAD aob
|
|
float camRotScale = p_camRotRateScale.GetFloat();
|
|
//rww - if on a wallwalk mover, increase the scale
|
|
if (GetPhysics() == &physicsObj && physicsObj.GetGroundTrace().fraction < 1.0f) {
|
|
const trace_t &grTr = physicsObj.GetGroundTrace();
|
|
if (grTr.c.entityNum >= 0 && grTr.c.entityNum < MAX_GENTITIES && gameLocal.entities[grTr.c.entityNum] && gameLocal.entities[grTr.c.entityNum]->IsType(hhMover::Type) && grTr.c.material && grTr.c.material->GetSurfaceType() == SURFTYPE_WALLWALK) {
|
|
camRotScale *= 5.0f;
|
|
}
|
|
}
|
|
cameraInterpolator.Setup( camRotScale, IT_VariableMidPointSinusoidal );//Also set in constructor
|
|
|
|
// moved here to allow weapons physics get correct eyeOffset
|
|
BecomeActive( TH_PHYSICS ); //rww - prevent the bug that id so lovingly left for us
|
|
RunPhysics();
|
|
// HUMANHEAD END
|
|
|
|
// HUMANHEAD pdm: Experimental: allow player to jump up onto wallwalk
|
|
#define WALLWALK_EXPERIMENT 0
|
|
#if WALLWALK_EXPERIMENT
|
|
if (!physicsObj.IsWallWalking() && physicsObj.HasJumped()) {
|
|
trace_t trace;
|
|
memset(&trace, 0, sizeof(trace));
|
|
idVec3 start, end;
|
|
start = physicsObj.GetOrigin();
|
|
end = start + idVec3(0,0,100); //fixme: jumpheight
|
|
if (gameLocal.clip.TracePoint( trace, start, end, MASK_SOLID, this )) {
|
|
if (gameLocal.GetMatterType(trace, NULL) == SURFTYPE_WALLWALK) {
|
|
gameLocal.Printf("Trying to flip over for wallwalk\n");
|
|
|
|
// Clear velocity, so won't miss wallwalk above
|
|
physicsObj.SetLinearVelocity(vec3_origin);
|
|
|
|
SetGravity(-trace.c.normal * DEFAULT_GRAVITY);
|
|
ProcessEvent( &EV_ShouldRemainAlignedToAxial, (int)false );
|
|
ProcessEvent( &EV_OrientToGravity, (int)true );
|
|
|
|
physicsObj.IsWallWalking( true );
|
|
|
|
/* idVec3 flippedOrigin = trace.endpos;
|
|
idMat3 flippedAxis;
|
|
flippedAxis[0] = GetAxis()[0];
|
|
flippedAxis[2] = trace.c.normal;
|
|
flippedAxis[1] = flippedAxis[0].Cross(flippedAxis[2]);
|
|
|
|
// cameraInterpolator.SetInterpolationType(flags);
|
|
// cameraInterpolator.SetTargetEyeOffset(idealEyeOffset, flags);
|
|
// cameraInterpolator.SetTargetPosition(idealPosition, flags);
|
|
|
|
// Flip the physics object
|
|
physicsObj.SetOrigin(flippedOrigin);
|
|
physicsObj.SetAxis(flippedAxis);
|
|
physicsObj.CheckWallWalk(true);*/
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
// HUMANHEAD END
|
|
|
|
if ( noclip || gameLocal.inCinematic ) {
|
|
AI_CROUCH = false;
|
|
AI_ONGROUND = false;
|
|
AI_ONLADDER = false;
|
|
AI_JUMP = false;
|
|
AI_REALLYFALL = true; //HUMANHEAD rww - well, it fits with ONGROUND false
|
|
} else {
|
|
AI_CROUCH = physicsObj.IsCrouching();
|
|
AI_ONGROUND = physicsObj.HasGroundContacts();
|
|
AI_ONLADDER = physicsObj.OnLadder();
|
|
AI_JUMP = physicsObj.HasJumped();
|
|
//HUMANHEAD rww
|
|
if (!physicsObj.IsInwardGravity()) {
|
|
AI_REALLYFALL = !AI_ONGROUND;
|
|
}
|
|
else if (!AI_ONGROUND) { //for inward gravity zones, do an extra ground check when in-air
|
|
AI_REALLYFALL = !physicsObj.ExtraGroundCheck();
|
|
}
|
|
//HUMANHEAD END
|
|
|
|
// check if we're standing on top of a monster and give a push if we are
|
|
idEntity *groundEnt = physicsObj.GetGroundEntity();
|
|
if ( groundEnt && groundEnt->IsType( idAI::Type ) ) {
|
|
idVec3 vel = physicsObj.GetLinearVelocity();
|
|
if ( vel.ToVec2().LengthSqr() < 0.1f ) {
|
|
vel.ToVec2() = physicsObj.GetOrigin().ToVec2() - groundEnt->GetPhysics()->GetAbsBounds().GetCenter().ToVec2();
|
|
vel.ToVec2().NormalizeFast();
|
|
vel.ToVec2() *= pm_walkspeed.GetFloat();
|
|
} else {
|
|
// give em a push in the direction they're going
|
|
vel *= 1.1f;
|
|
}
|
|
physicsObj.SetLinearVelocity( vel );
|
|
}
|
|
}
|
|
|
|
if ( AI_JUMP ) {
|
|
// bounce the view weapon
|
|
loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)];
|
|
currentLoggedAccel++;
|
|
acc->time = gameLocal.time;
|
|
acc->dir[2] = 200;
|
|
acc->dir[0] = acc->dir[1] = 0;
|
|
}
|
|
|
|
BobCycle( pushVelocity );
|
|
if ( !noclip ) { // HUMANHEAD: Only crashland if not spiritwalking or not deathwalking
|
|
CrashLand( oldOrigin, oldVelocity );
|
|
}
|
|
|
|
//HUMANHEAD: aob - put this into helper func
|
|
if (!gameLocal.isClient) {
|
|
if( IsWallWalking() && !WasWallWalking() ) {
|
|
wallwalkSoundController.StartSound( SND_CHANNEL_WALLWALK, SND_CHANNEL_WALLWALK2, SSF_LOOPING, false );
|
|
} else if( !IsWallWalking() && WasWallWalking() ) {
|
|
wallwalkSoundController.StopSound( SND_CHANNEL_WALLWALK, SND_CHANNEL_WALLWALK2, false );
|
|
}
|
|
}
|
|
physicsObj.WasWallWalking( IsWallWalking() ); //rww - moved here
|
|
//HUMANHEAD END
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::ShouldTouchTrigger
|
|
===============
|
|
*/
|
|
bool hhPlayer::ShouldTouchTrigger( idEntity* entity ) const {
|
|
if( !entity ) {
|
|
return false;
|
|
}
|
|
|
|
if ( entity->fl.onlySpiritWalkTouch ) { // cjr - Trigger can only be touched by spirit
|
|
return IsSpiritWalking();
|
|
}
|
|
|
|
if( IsSpiritOrDeathwalking() && !entity->fl.allowSpiritWalkTouch ) { // Trigger can be touched by either physical or spirit
|
|
return false; // jrm - reversed logic
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::HandleSingleGuiCommand
|
|
===============
|
|
*/
|
|
bool hhPlayer::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) {
|
|
|
|
idToken token;
|
|
|
|
if (!src->ReadToken(&token)) {
|
|
return false;
|
|
}
|
|
|
|
if (token == ";") {
|
|
return false;
|
|
}
|
|
|
|
if (token.Icmp("guilockplayer") == 0) {
|
|
//Now locked in place:
|
|
// manglecontrols() will route controls to current gui focus
|
|
if (entityGui->IsType(hhConsole::Type)) {
|
|
guiWantsControls = static_cast<hhConsole*>(entityGui);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
src->UnreadToken(&token);
|
|
return idPlayer::HandleSingleGuiCommand(entityGui, src);
|
|
}
|
|
|
|
void hhPlayer::SetOverlayGui(const char *guiName) {
|
|
if (guiName && *guiName && guiOverlay == NULL) {
|
|
guiOverlay = uiManager->FindGui(guiName, true);
|
|
if (guiOverlay) {
|
|
guiOverlay->Activate(true, gameLocal.time);
|
|
}
|
|
}
|
|
else {
|
|
guiOverlay = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
hhPlayer::ForceWeapon
|
|
nla: used to instantly force the spirit weapon, without lowering and raising
|
|
============
|
|
*/
|
|
void hhPlayer::ForceWeapon( int weaponNum ) {
|
|
const char * weaponDef;
|
|
hhWeapon * newWeapon;
|
|
|
|
if (spectating) { //rww - if spectating this is bad.
|
|
gameLocal.Error("hhPlayer::ForceWeapon called on spectator. (client %i)", entityNumber);
|
|
}
|
|
|
|
// HUMANHEAD mdl: Special case - coming out of spirit mode when no physical weapon is available
|
|
if ( weaponNum == -1 ) {
|
|
idealWeapon = 0;
|
|
currentWeapon = -1;
|
|
return;
|
|
}
|
|
|
|
if ( weaponNum < 1 ) {
|
|
gameLocal.Warning( "Error: Illegal weapon num passed: %d\n", weaponNum );
|
|
}
|
|
|
|
weaponDef = GetWeaponName( weaponNum );
|
|
|
|
newWeapon = SpawnWeapon( weaponDef );
|
|
|
|
weapon = newWeapon;
|
|
|
|
idealWeapon = currentWeapon = weaponNum;
|
|
|
|
animPrefix = weaponDef;
|
|
animPrefix.Strip( "weaponobj_" );
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
hhPlayer::ForceWeapon
|
|
============
|
|
*/
|
|
void hhPlayer::ForceWeapon( hhWeapon *newWeapon ) {
|
|
int weaponNum;
|
|
|
|
|
|
weapon = newWeapon;
|
|
|
|
weaponNum = GetWeaponNum( weapon->GetDict()->GetString( "classname" ) );
|
|
|
|
idealWeapon = currentWeapon = weaponNum;
|
|
|
|
}
|
|
|
|
/*
|
|
===========
|
|
hhPlayer::Teleport
|
|
|
|
HUMANHEAD cjr: Now calls TeleportNoKillBox, then applies a kill box
|
|
============
|
|
*/
|
|
void hhPlayer::Teleport( const idVec3& origin, const idAngles& angles, idEntity *destination ) {
|
|
//pdm - converted this to call our other version so it doesn't unalign the clip model
|
|
TeleportNoKillBox( origin, mat3_identity, angles.ToForward(), angles );
|
|
|
|
// mdl: Moved here from TeleportNoBox, so player can't avoid falling damage by toggling spirit mode quickly
|
|
physicsObj.SetLinearVelocity( vec3_origin );
|
|
physicsObj.SetKnockBack( 250 ); // Slow the player down after a teleport for a moment
|
|
|
|
// kill anything at the new position
|
|
gameLocal.KillBox( this, destination != NULL );
|
|
|
|
teleportEntity = destination;
|
|
}
|
|
|
|
/*
|
|
===========
|
|
hhPlayer::Teleport
|
|
|
|
HUMANHEAD aob: Needed for coming back from deathwalk
|
|
============
|
|
*/
|
|
void hhPlayer::Teleport( const idVec3& origin, const idMat3& bboxAxis, const idVec3& viewDir, const idAngles& newUntransformedViewAngles, idEntity *destination ) {
|
|
TeleportNoKillBox( origin, bboxAxis, viewDir, newUntransformedViewAngles );
|
|
|
|
// kill anything at the new position
|
|
gameLocal.KillBox( this, destination != NULL );
|
|
|
|
teleportEntity = destination;
|
|
}
|
|
|
|
/*
|
|
===========
|
|
hhPlayer::TeleportNoKillBox
|
|
|
|
HUMANHEAD cjr: Called from SpiritProxy
|
|
============
|
|
*/
|
|
void hhPlayer::TeleportNoKillBox( const idVec3& origin, const idMat3& bboxAxis, const idVec3& viewDir, const idAngles& newUntransformedViewAngles ) {
|
|
DisableIK();
|
|
|
|
SetOrientation( origin, bboxAxis, viewDir, newUntransformedViewAngles );
|
|
|
|
CancelEvents( &EV_ResetGravity );
|
|
ProcessEvent( &EV_ResetGravity ); // Guarantee that gravity is instantly reset after this teleport
|
|
|
|
EnableIK();
|
|
}
|
|
|
|
/*
|
|
===========
|
|
hhPlayer::TeleportNoKillBox
|
|
|
|
HUMANHEAD cjr: Teleport, but don't telefrag anything
|
|
============
|
|
*/
|
|
|
|
void hhPlayer::TeleportNoKillBox( const idVec3& origin, const idMat3& bboxAxis ) {
|
|
TeleportNoKillBox( origin, bboxAxis, GetAxis()[0], GetUntransformedViewAngles() );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
hhPlayer::Possess
|
|
|
|
//HUMANHEAD: cjr - this is the entry point for possession.
|
|
=====================
|
|
*/
|
|
void hhPlayer::Possess( idEntity* possessor ) {
|
|
bPossessed = true;
|
|
|
|
/* TODO:
|
|
- needs a new spirit proxy (a little hidden object just as storage that is linked to the possessed human)
|
|
- disallow the player from returning until the possessed tommy has been "killed"
|
|
|
|
|
|
possessed tommy:
|
|
- attacks whatever
|
|
- health ticking down -- when it reaches zero, ragdoll and instantly kill the spirit player
|
|
- if killed by the spirit bow, then the wraith is removed (seperate health?)
|
|
*/
|
|
|
|
// If we're spirit walking when we're possessed, snap back to our body for a moment, then get thrown back out.
|
|
//TODO this isn't ideal
|
|
if ( IsSpiritOrDeathwalking() ) {
|
|
DisableEthereal();
|
|
}
|
|
|
|
// Must allow spirit walking at this point
|
|
HH_ASSERT( bAllowSpirit );
|
|
|
|
// Force the player into spirit and thrust them backwards
|
|
StartSpiritWalk( true, true );
|
|
|
|
// test: spawn in a possessed version
|
|
// need: effects here...flash or whatever
|
|
possessedTommy = (hhPossessedTommy *)gameLocal.SpawnObject( "monster_possessed_tommy", NULL );
|
|
|
|
if ( possessedTommy.IsValid() ) { // Copy player stats to the possessed Tommy
|
|
/*if ( IsSpiritOrDeathwalking() ) { //TODO mdl: Remove this if we go with wraiths knocking players back to their body
|
|
// This should means we were called by
|
|
possessedTommy->SetOrigin( spiritProxy->GetOrigin() );
|
|
possessedTommy->SetAxis( spiritProxy->GetAxis() );
|
|
} else*/ {
|
|
possessedTommy->SetOrigin( GetOrigin() );
|
|
possessedTommy->SetAxis( GetAxis() );
|
|
}
|
|
possessedTommy->SetPossessedProxy( spiritProxy.GetEntity() );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
hhPlayer::Unpossess
|
|
|
|
//HUMANHEAD: cjr
|
|
=====================*/
|
|
|
|
void hhPlayer::Unpossess() {
|
|
bPossessed = false;
|
|
|
|
possessedTommy = NULL;
|
|
/*
|
|
idDict args;
|
|
idEntity *ent;
|
|
|
|
playerView.SetViewOverlayMaterial( NULL );
|
|
possessionFOV = 0; // Restore the original FOV
|
|
|
|
// Spawn a wraith to fly out of the player's origin
|
|
args.Clear();
|
|
args.SetVector( "origin", GetOrigin() + GetEyePosition() + GetAxis()[0] * 50 );
|
|
args.SetMatrix( "rotation", GetAxis() );
|
|
//TODO: Externalize this monster_wraith
|
|
args.Set( "classname", "monster_wraith" );
|
|
|
|
gameLocal.SpawnEntityDef( args, &ent );
|
|
((hhWraith *)ent)->SetEnemy( this );
|
|
|
|
// TODO: Unpossessed by portalling, so kill the wraith
|
|
*/
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::CanBePossessed
|
|
//
|
|
// Players can only be possessed if they are are not currently possessed
|
|
//=============================================================================
|
|
|
|
bool hhPlayer::CanBePossessed( void ) {
|
|
if ( godmode || noclip || !bAllowSpirit ) { // Don't possess if in god mode or noclipping
|
|
return false;
|
|
}
|
|
|
|
return !(IsPossessed() || InVehicle() || IsSpiritOrDeathwalking() );
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::PossessKilled
|
|
//
|
|
// Killed when possessed (spirit power ran out)
|
|
//
|
|
// - Ragdoll the possessed body
|
|
// - Return to the location of the possessed tommy and die
|
|
//=============================================================================
|
|
|
|
void hhPlayer::PossessKilled( void ) {
|
|
if ( possessedTommy.IsValid() ) {
|
|
possessedTommy->PostEventMS( &EV_Remove, 0 );
|
|
}
|
|
|
|
Unpossess();
|
|
StopSpiritWalk();
|
|
Kill( 0, 0 ); // FIXME: Some other way to kill self (instead of suicide?)
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::Portalled
|
|
//
|
|
// Player just portalled
|
|
//=============================================================================
|
|
|
|
void hhPlayer::Portalled( idEntity *portal ) {
|
|
if ( talon.IsValid() && talon->GetBindMaster() != this ) { // If Talon isn't bound to Tommy, portal him with the player
|
|
talon->Portalled( portal );
|
|
}
|
|
|
|
if (gameLocal.isClient) { //HUMANHEAD rww - compensate for angle jump
|
|
bBufferNextSnapAngles = true;
|
|
smoothedAngles = viewAngles;
|
|
}
|
|
smoothedFrame = 0; //HUMANHEAD rww - skip smoothing on the portalled frame
|
|
walkIK.InvalidateHeights(); //HUMANHEAD rww - don't try to interpolate ik positions between two equal planes on either side of a portal
|
|
if (gameLocal.isServer) { //HUMANHEAD rww - send an unreliable event in the coming snapshot
|
|
idBitMsg msg;
|
|
byte msgBuf[MAX_EVENT_PARAM_SIZE];
|
|
|
|
msg.Init(msgBuf, sizeof(msgBuf));
|
|
msg.WriteBits(gameLocal.GetSpawnId(portal), 32);
|
|
ServerSendEvent(EVENT_PORTALLED, &msg, false, -1, -1, true); //only send it to the client who portalled
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
hhPlayer::UpdateModelTransform
|
|
================
|
|
*/
|
|
void hhPlayer::UpdateModelTransform( void ) {
|
|
idVec3 origin;
|
|
idMat3 axis;
|
|
|
|
if( GetPhysicsToVisualTransform(origin, axis) ) {
|
|
//HUMANHEAD: aob
|
|
GetRenderEntity()->axis = axis;
|
|
idVec3 absOrigin = TransformToPlayerSpaceNotInterpolated( origin ); //rww - don't use interpolated origin
|
|
GetRenderEntity()->origin = absOrigin;
|
|
//HUMANHEAD END
|
|
} else {
|
|
//HUMANHEAD: aob
|
|
GetRenderEntity()->axis = GetAxis();
|
|
GetRenderEntity()->origin = GetOrigin();
|
|
//HUMANHEAD END
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
hhPlayer::CalcFov
|
|
|
|
Fixed fov at intermissions, otherwise account for fov variable and zooms.
|
|
Takes possession FOV into account
|
|
|
|
HUMANHEAD cjr
|
|
====================
|
|
*/
|
|
float hhPlayer::CalcFov( bool honorZoom ) {
|
|
float fov;
|
|
|
|
// HUMANHEAD mdl: Refactored this to work properly with possessionFOV. Being zoomed in will ignore possessionFOV, however.
|
|
idEntity *weaponEnt = weapon.GetEntity();
|
|
if ( possessionFOV > 0.0f &&
|
|
! ( weaponEnt &&
|
|
weaponEnt->IsType( hhWeaponZoomable::Type ) &&
|
|
reinterpret_cast<hhWeaponZoomable *> (weaponEnt)->IsZoomed() ) )
|
|
{
|
|
fov = possessionFOV;
|
|
} else if ( IsDeathWalking() ) {
|
|
fov = spawnArgs.GetFloat("deathwalkFOV", "90");
|
|
} else if ( pm_thirdPerson.GetBool() ) {
|
|
fov = g_fov.GetFloat();
|
|
} else if ( InCinematic() ) {
|
|
fov = cinematicFOV.GetCurrentValue(gameLocal.time);
|
|
} else {
|
|
fov = zoomFov.GetCurrentValue(gameLocal.time);
|
|
}
|
|
|
|
//HUMANHEAD: aob
|
|
fov = hhMath::ClampFloat( 1.0f, 179.0f, fov );
|
|
//HUMANHEAD END
|
|
|
|
return fov;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::EnterVehicle
|
|
===============
|
|
*/
|
|
void hhPlayer::EnterVehicle( hhVehicle* vehicle ) {
|
|
if (!gameLocal.isClient) { //HUMANHEAD PCF rww 05/04/06 - do not do the check on the client, just listen to what the snapshot says.
|
|
if (!vehicle->WillAcceptPilot(this)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Cancel any pending damage allowing events, since we'll be turning damage off
|
|
CancelEvents(&EV_AllowDamage);
|
|
|
|
// Set shuttle view
|
|
if (entityNumber == gameLocal.localClientNum) { //rww
|
|
renderSystem->SetShuttleView( true );
|
|
}
|
|
|
|
// Move eye to proper height
|
|
SetEyeHeight( vehicle->spawnArgs.GetFloat("pilot_eyeHeight") );
|
|
|
|
// Turn off any illegal behaviors
|
|
if( IsSpiritWalking() ) {
|
|
StopSpiritWalk();
|
|
}
|
|
|
|
LighterOff();
|
|
|
|
// nla - Remove any offset cause by jumping. (Fixes the jumping and getting in the shuttle bug)
|
|
SetViewBob( vec3_origin );
|
|
|
|
// CJR: Inform Talon that the player has entered a vehicle
|
|
if( talon.IsValid() ) {
|
|
talon->OwnerEnteredVehicle();
|
|
}
|
|
|
|
ShouldRemainAlignedToAxial( false );
|
|
|
|
cameraInterpolator.SetInterpolationType( IT_None );
|
|
SetOrientation( GetOrigin(), mat3_identity, vehicle->GetAxis()[0], vehicle->GetAxis()[0].ToAngles() );
|
|
//Need to clear untransformedViewAxis so cameraInterpolator doesn't do any unnessacary transforms
|
|
SetUntransformedViewAxis( mat3_identity );
|
|
|
|
//Hack
|
|
cameraInterpolator.Reset( GetOrigin(), mat3_identity[2], EyeHeightIdeal() );
|
|
|
|
idPlayer::EnterVehicle( vehicle );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::ExitVehicle
|
|
|
|
This should only be called from vehicle
|
|
===============
|
|
*/
|
|
void hhPlayer::ExitVehicle( hhVehicle* vehicle ) {
|
|
|
|
// Allow model in player's view again
|
|
if (vehicle) {
|
|
vehicle->GetRenderEntity()->suppressSurfaceInViewID = 0;
|
|
}
|
|
|
|
// Set shuttle view off
|
|
if (entityNumber == gameLocal.localClientNum) { //rww
|
|
renderSystem->SetShuttleView( false );
|
|
}
|
|
|
|
ShouldRemainAlignedToAxial( true );
|
|
|
|
// CJR: Inform Talon that the player has left a vehicle
|
|
if( talon.IsValid() ) {
|
|
talon->OwnerExitedVehicle();
|
|
}
|
|
|
|
idPlayer::ExitVehicle( vehicle );
|
|
|
|
buttonMask |= BUTTON_ATTACK_ALT; //HUMANHEAD bjk
|
|
|
|
// Reset the animPrefix
|
|
animPrefix = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) );
|
|
animPrefix.Strip( "weaponobj_" );
|
|
|
|
if (vehicle) {
|
|
SetOrientation( GetOrigin(), mat3_identity, vehicle->GetAxis()[0], vehicle->GetAxis()[0].ToAngles().Normalize180() );
|
|
}
|
|
cameraInterpolator.SetInterpolationType( IT_VariableMidPointSinusoidal );
|
|
}
|
|
|
|
//
|
|
// ResetClipModel()
|
|
//
|
|
// HUMANHEAD: aob
|
|
//
|
|
void hhPlayer::ResetClipModel() {
|
|
//Needed for touching triggers. Our bbox is its original size.
|
|
SetClipModel();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::BecameBound
|
|
===============
|
|
*/
|
|
void hhPlayer::BecameBound(hhBindController *b) {
|
|
if( !gameLocal.isMultiplayer && weapon.IsValid() ) { //rww - can use weapon while bound in mp
|
|
weapon->Hide();
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::BecameUnbound
|
|
===============
|
|
*/
|
|
void hhPlayer::BecameUnbound(hhBindController *b) {
|
|
if( !gameLocal.isMultiplayer && weapon.IsValid() ) { //rww - can use weapon while bound in mp
|
|
weapon->Show();
|
|
}
|
|
}
|
|
|
|
void hhPlayer::GetLocationText( idStr &locationString ) {
|
|
idLocationEntity* locationEntity = gameLocal.LocationForPoint( GetEyePosition() );
|
|
if( locationEntity ) {
|
|
locationString = locationEntity->GetLocation();
|
|
}
|
|
else {
|
|
locationString = common->GetLanguageDict()->GetString( "#str_02911" );
|
|
}
|
|
}
|
|
|
|
void hhPlayer::UpdateLocation( void ) {
|
|
if( hud ) {
|
|
hud->SetStateBool("showlocations", developer.GetBool());
|
|
if (developer.GetBool()) {
|
|
idStr locationString;
|
|
GetLocationText(locationString);
|
|
hud->SetStateString( "location", locationString.c_str() );
|
|
hud->SetStateInt( "areanum", gameRenderWorld->PointInArea( GetEyePosition() ));
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::FillDebugVars
|
|
===============
|
|
*/
|
|
void hhPlayer::FillDebugVars(idDict *args, int page) {
|
|
idStr text;
|
|
|
|
switch(page) {
|
|
case 1:
|
|
args->SetInt("deathpower", GetDeathWalkPower());
|
|
args->SetInt("spiritpower", GetSpiritPower());
|
|
args->SetBool("AI_CROUCH", AI_CROUCH != 0);
|
|
args->SetBool("AI_ONGROUND", AI_ONGROUND != 0);
|
|
args->SetBool("AI_JUMP", AI_JUMP != 0);
|
|
args->SetBool("AI_SOFTLANDING", AI_SOFTLANDING != 0);
|
|
args->SetBool("AI_HARDLANDING", AI_HARDLANDING != 0);
|
|
args->SetBool("AI_FORWARD", AI_FORWARD != 0);
|
|
args->SetBool("AI_BACKWARD", AI_BACKWARD != 0);
|
|
args->SetBool("AI_STRAFE_LEFT", AI_STRAFE_LEFT != 0);
|
|
args->SetBool("AI_STRAFE_RIGHT", AI_STRAFE_RIGHT != 0);
|
|
args->SetBool("AI_ATTACK_HELD", AI_ATTACK_HELD != 0);
|
|
args->SetBool("AI_WEAPON_FIRED", AI_WEAPON_FIRED != 0);
|
|
args->SetBool("AI_JUMP", AI_JUMP != 0);
|
|
args->SetBool("AI_DEAD", AI_DEAD != 0);
|
|
args->SetBool("AI_PAIN", AI_PAIN != 0);
|
|
args->SetBool("AI_RELOAD", AI_RELOAD != 0);
|
|
args->SetBool("AI_TELEPORT", AI_TELEPORT != 0);
|
|
args->SetBool("AI_TURN_LEFT", AI_TURN_LEFT != 0);
|
|
args->SetBool("AI_TURN_RIGHT", AI_TURN_RIGHT != 0);
|
|
args->SetBool("AI_ASIDE", AI_ASIDE != 0);
|
|
args->SetBool("AI_REALLYFALL", AI_REALLYFALL != 0); //HUMANHEAD rww
|
|
args->SetBool("HasGroundContacts", physicsObj.HasGroundContacts());
|
|
args->Set("animPrefix", animPrefix.c_str());
|
|
break;
|
|
case 2:
|
|
args->Set("Physics Axis", GetPhysics()->GetAxis().ToAngles().ToString());
|
|
args->Set("viewAxis", viewAxis.ToAngles().ToString());
|
|
args->Set("cmdAngles", cmdAngles.ToString());
|
|
args->Set("deltaViewAngles", GetDeltaViewAngles().ToString() );
|
|
args->Set("viewAngles", viewAngles.ToString());
|
|
args->Set("untransAngles", GetUntransformedViewAngles().ToString());
|
|
args->Set("xPhysics Axis", GetPhysics()->GetAxis().ToString());
|
|
args->Set("xviewAxis", viewAxis.ToString());
|
|
args->Set("idealLegsYaw", va("%.1f", idealLegsYaw));
|
|
args->SetInt("usercmd.forwardmove", usercmd.forwardmove);
|
|
args->SetInt("usercmd.rightmove", usercmd.rightmove);
|
|
args->SetInt("legsForward", legsForward);
|
|
|
|
switch(physicsObj.GetWaterLevel()) {
|
|
case WATERLEVEL_NONE: text = "none"; break;
|
|
case WATERLEVEL_FEET: text = "feet"; break;
|
|
case WATERLEVEL_WAIST: text = "waist"; break;
|
|
case WATERLEVEL_HEAD: text = "head"; break;
|
|
}
|
|
args->Set("waterlevel", text);
|
|
text = collisionModelManager->ContentsName(physicsObj.GetWaterType());
|
|
args->Set("watertype", text);
|
|
break;
|
|
case 3:
|
|
break;
|
|
}
|
|
idPlayer::FillDebugVars(args, page);
|
|
}
|
|
|
|
//
|
|
// GetAimPosition()
|
|
//
|
|
idVec3 hhPlayer::GetAimPosition() const {
|
|
return GetEyePosition();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::WriteToSnapshot
|
|
===============
|
|
*/
|
|
void hhPlayer::WriteToSnapshot( idBitMsgDelta &msg ) const {
|
|
bool vehControlling = vehicleInterfaceLocal.ControllingVehicle();
|
|
|
|
msg.WriteBits(vehControlling, 1);
|
|
|
|
//rww - our stuff is now intermingled with id's for more ideal send ordering
|
|
physicsObj.WriteToSnapshot( msg, vehControlling );
|
|
|
|
if (!vehControlling) {
|
|
//not syncing these causes denormalization/nan issues, FIXME
|
|
msg.WriteFloat(untransformedViewAngles[0]);
|
|
msg.WriteFloat(untransformedViewAngles[1]);
|
|
msg.WriteFloat(untransformedViewAngles[2]);
|
|
idCQuat q = untransformedViewAxis.ToCQuat();
|
|
msg.WriteFloat(q.x);
|
|
msg.WriteFloat(q.y);
|
|
msg.WriteFloat(q.z);
|
|
|
|
//rww - write cameraInterpolator
|
|
cameraInterpolator.WriteToSnapshot(msg, this);
|
|
|
|
//still need delta angles
|
|
//msg.WriteDeltaFloat( 0.0f, deltaViewAngles[0] );
|
|
//msg.WriteDeltaFloat( 0.0f, deltaViewAngles[1] );
|
|
//msg.WriteDeltaFloat( 0.0f, deltaViewAngles[2] );
|
|
}
|
|
#if 0 //for debugging differences in snapshot
|
|
else {
|
|
msg.WriteFloat(0.0f);
|
|
msg.WriteFloat(0.0f);
|
|
msg.WriteFloat(0.0f);
|
|
msg.WriteFloat(0.0f);
|
|
msg.WriteFloat(0.0f);
|
|
msg.WriteFloat(0.0f);
|
|
|
|
msg.WriteFloat(0.0f);
|
|
msg.WriteFloat(0.0f);
|
|
msg.WriteFloat(0.0f);
|
|
msg.WriteDeltaFloat(0.0f, 0.0f);
|
|
msg.WriteDeltaFloat(0.0f, 0.0f);
|
|
msg.WriteDeltaFloat(0.0f, 0.0f);
|
|
msg.WriteDeltaFloat(0.0f, 0.0f);
|
|
msg.WriteDeltaFloat(0.0f, 0.0f);
|
|
msg.WriteDeltaFloat(0.0f, 0.0f);
|
|
msg.WriteFloat(0.0f, 4, 4);
|
|
|
|
msg.WriteDeltaFloat(0.0f, 0.0f);
|
|
msg.WriteDeltaFloat(0.0f, 0.0f);
|
|
msg.WriteDeltaFloat(0.0f, 0.0f);
|
|
msg.WriteDeltaFloat(0.0f, 0.0f);
|
|
msg.WriteDeltaFloat(0.0f, 0.0f);
|
|
msg.WriteDeltaFloat(0.0f, 0.0f);
|
|
msg.WriteDeltaFloat(0.0f, 0.0f);
|
|
msg.WriteDeltaFloat(0.0f, 0.0f);
|
|
msg.WriteDeltaFloat(0.0f, 0.0f);
|
|
msg.WriteFloat(0.0f, 4, 4);
|
|
|
|
msg.WriteFloat(0.0f);
|
|
msg.WriteDeltaFloat(0.0f, 0.0f);
|
|
msg.WriteDeltaFloat(0.0f, 0.0f);
|
|
msg.WriteFloat(0.0f, 4, 4);
|
|
|
|
msg.WriteDeltaFloat(0.0f, 0.0f);
|
|
msg.WriteDeltaFloat(0.0f, 0.0f);
|
|
msg.WriteDeltaFloat(0.0f, 0.0f);
|
|
}
|
|
#endif
|
|
|
|
msg.WriteDeltaFloat( 0.0f, deltaViewAngles[0] );
|
|
msg.WriteDeltaFloat( 0.0f, deltaViewAngles[1] );
|
|
msg.WriteDeltaFloat( 0.0f, deltaViewAngles[2] );
|
|
|
|
msg.WriteShort( health );
|
|
msg.WriteBits( gameLocal.ServerRemapDecl( -1, DECL_ENTITYDEF, lastDamageDef ), gameLocal.entityDefBits );
|
|
msg.WriteDir( lastDamageDir, 9 );
|
|
msg.WriteShort( lastDamageLocation );
|
|
msg.WriteBits( idealWeapon, idMath::BitsForInteger( MAX_WEAPONS ) );
|
|
msg.WriteBits( inventory.weapons, MAX_WEAPONS );
|
|
msg.WriteBits( weapon.GetSpawnId(), 32 );
|
|
msg.WriteBits( spectator, idMath::BitsForInteger( MAX_CLIENTS ) );
|
|
msg.WriteBits( lastHitToggle, 1 );
|
|
msg.WriteBits( weaponGone, 1 );
|
|
WriteBindToSnapshot( msg );
|
|
//===================END OF ID DATA
|
|
msg.WriteShort(inventory.maxHealth); //rww - since maxhealth can go above 100 in mp now
|
|
|
|
msg.WriteBits( spiritProxy.GetSpawnId(), 32 );
|
|
|
|
//rww - more spiritwalk stuff
|
|
msg.WriteBits(lastWeaponSpirit, 32);
|
|
msg.WriteBits(bSpiritWalk, 1);
|
|
|
|
//not needed anymore
|
|
/*
|
|
msg.WriteBits( bShowProgressBar, 1 );
|
|
msg.WriteFloat( progressBarValue );
|
|
msg.WriteBits( progressBarState, 2 ); // possible values [0..2]
|
|
*/
|
|
|
|
//rww - send ammo for current weapon - this is because our weapon routines rely on if we have ammo
|
|
//in scripts and so on, and will be predicted wrong for other players (we get our own ammo in the playerstate)
|
|
//rwwFIXME: if we have proper weapon switching when you run out of ammo will this actually be necessary?
|
|
//i don't think it's all that costly, but still.
|
|
if (weapon.IsValid()) {
|
|
ammo_t ammoType = weapon->GetAmmoType();
|
|
if (ammoType > 0) {
|
|
msg.WriteBits(inventory.ammo[ammoType], ASYNC_PLAYER_INV_AMMO_BITS);
|
|
}
|
|
else {
|
|
msg.WriteBits(0, ASYNC_PLAYER_INV_AMMO_BITS);
|
|
}
|
|
ammoType = weapon->GetAltAmmoType();
|
|
if (ammoType > 0) {
|
|
msg.WriteBits(inventory.ammo[ammoType], ASYNC_PLAYER_INV_AMMO_BITS);
|
|
}
|
|
else {
|
|
msg.WriteBits(0, ASYNC_PLAYER_INV_AMMO_BITS);
|
|
}
|
|
}
|
|
else {
|
|
msg.WriteBits(0, ASYNC_PLAYER_INV_AMMO_BITS);
|
|
msg.WriteBits(0, ASYNC_PLAYER_INV_AMMO_BITS);
|
|
}
|
|
|
|
//need to sync buttonMask since we're using it for some state-based things
|
|
msg.WriteBits(buttonMask, 8);
|
|
|
|
msg.WriteFloat(EyeHeight());
|
|
|
|
//HUMANHEAD PCF rww 05/04/06 - do not sync AI_VEHICLE, it is now based purely on the clientside
|
|
//enter/exit of vehicles, with the vehControlling stack bool determining if we should be in a
|
|
//vehicle on the client or not.
|
|
//msg.WriteBits(AI_VEHICLE, 1);
|
|
|
|
//rww - hand stuff
|
|
//weaponHandState.WriteToSnapshot(msg);
|
|
msg.WriteBits(hand.GetSpawnId(), 32);
|
|
msg.WriteBits(handNext.GetSpawnId(), 32);
|
|
|
|
//jsh - vehicle stuff
|
|
msg.WriteBits( vehicleInterfaceLocal.GetVehicleSpawnId(), 32 );
|
|
msg.WriteBits( vehicleInterfaceLocal.GetHandSpawnId(), 32 );
|
|
//vehicleInterfaceLocal.GetWeaponHandState()->WriteToSnapshot(msg);
|
|
|
|
//rww - lighter sync validation
|
|
msg.WriteBits((lighterHandle != -1), 1);
|
|
|
|
//rww - tractor
|
|
msg.WriteBits(fl.isTractored, 1);
|
|
|
|
spiritwalkSoundController.WriteToSnapshot(msg);
|
|
//deathwalkSoundController.WriteToSnapshot(msg);
|
|
wallwalkSoundController.WriteToSnapshot(msg);
|
|
|
|
//HUMANHEAD rww - leechgun energy ammo
|
|
//note - current weapon's ammo always sent now
|
|
//ammo_t energyammo = hhWeaponFireController::GetAmmoType("ammo_energy");
|
|
//msg.WriteBits(inventory.ammo[energyammo], ASYNC_PLAYER_INV_AMMO_BITS);
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::ReadFromSnapshot
|
|
===============
|
|
*/
|
|
void hhPlayer::ReadFromSnapshot( const idBitMsgDelta &msg ) {
|
|
int i, oldHealth, newIdealWeapon, weaponSpawnId;
|
|
bool newHitToggle, stateHitch;
|
|
|
|
bool vehControlling = !!msg.ReadBits(1);
|
|
|
|
if ( snapshotSequence - lastSnapshotSequence > 1 ) {
|
|
stateHitch = true;
|
|
} else {
|
|
stateHitch = false;
|
|
}
|
|
lastSnapshotSequence = snapshotSequence;
|
|
|
|
oldHealth = health;
|
|
|
|
physicsObj.ReadFromSnapshot( msg, vehControlling );
|
|
|
|
if (!vehControlling) {
|
|
//not syncing these causes denormalization/nan issues, FIXME
|
|
if (bBufferNextSnapAngles) {
|
|
idAngles n;
|
|
n[0] = msg.ReadFloat();
|
|
n[1] = msg.ReadFloat();
|
|
n[2] = msg.ReadFloat();
|
|
BufferLoggedViewAngles(n);
|
|
untransformedViewAngles = n;
|
|
bBufferNextSnapAngles = false;
|
|
}
|
|
else {
|
|
untransformedViewAngles[0] = msg.ReadFloat();
|
|
untransformedViewAngles[1] = msg.ReadFloat();
|
|
untransformedViewAngles[2] = msg.ReadFloat();
|
|
}
|
|
idCQuat q;
|
|
q.x = msg.ReadFloat();
|
|
q.y = msg.ReadFloat();
|
|
q.z = msg.ReadFloat();
|
|
untransformedViewAxis = q.ToMat3();
|
|
|
|
//rww - read cameraInterpolator
|
|
cameraInterpolator.ReadFromSnapshot(msg, this);
|
|
|
|
//deltaViewAngles[0] = msg.ReadDeltaFloat( 0.0f );
|
|
//deltaViewAngles[1] = msg.ReadDeltaFloat( 0.0f );
|
|
//deltaViewAngles[2] = msg.ReadDeltaFloat( 0.0f );
|
|
}
|
|
#if 0 //for debugging differences in snapshot
|
|
else {
|
|
msg.ReadFloat();
|
|
msg.ReadFloat();
|
|
msg.ReadFloat();
|
|
msg.ReadFloat();
|
|
msg.ReadFloat();
|
|
msg.ReadFloat();
|
|
|
|
msg.ReadFloat();
|
|
msg.ReadFloat();
|
|
msg.ReadFloat();
|
|
msg.ReadDeltaFloat(0.0f);
|
|
msg.ReadDeltaFloat(0.0f);
|
|
msg.ReadDeltaFloat(0.0f);
|
|
msg.ReadDeltaFloat(0.0f);
|
|
msg.ReadDeltaFloat(0.0f);
|
|
msg.ReadDeltaFloat(0.0f);
|
|
msg.ReadFloat(4, 4);
|
|
|
|
msg.ReadDeltaFloat(0.0f);
|
|
msg.ReadDeltaFloat(0.0f);
|
|
msg.ReadDeltaFloat(0.0f);
|
|
msg.ReadDeltaFloat(0.0f);
|
|
msg.ReadDeltaFloat(0.0f);
|
|
msg.ReadDeltaFloat(0.0f);
|
|
msg.ReadDeltaFloat(0.0f);
|
|
msg.ReadDeltaFloat(0.0f);
|
|
msg.ReadDeltaFloat(0.0f);
|
|
msg.ReadFloat(4, 4);
|
|
|
|
msg.ReadFloat();
|
|
msg.ReadDeltaFloat(0.0f);
|
|
msg.ReadDeltaFloat(0.0f);
|
|
msg.ReadFloat(4, 4);
|
|
|
|
msg.ReadDeltaFloat(0.0f);
|
|
msg.ReadDeltaFloat(0.0f);
|
|
msg.ReadDeltaFloat(0.0f);
|
|
}
|
|
#endif
|
|
|
|
deltaViewAngles[0] = msg.ReadDeltaFloat( 0.0f );
|
|
deltaViewAngles[1] = msg.ReadDeltaFloat( 0.0f );
|
|
deltaViewAngles[2] = msg.ReadDeltaFloat( 0.0f );
|
|
|
|
health = msg.ReadShort();
|
|
lastDamageDef = gameLocal.ClientRemapDecl( DECL_ENTITYDEF, msg.ReadBits( gameLocal.entityDefBits ) );
|
|
lastDamageDir = msg.ReadDir( 9 );
|
|
lastDamageLocation = msg.ReadShort();
|
|
newIdealWeapon = msg.ReadBits( idMath::BitsForInteger( MAX_WEAPONS ) );
|
|
inventory.weapons = msg.ReadBits( MAX_WEAPONS );
|
|
weaponSpawnId = msg.ReadBits( 32 );
|
|
spectator = msg.ReadBits( idMath::BitsForInteger( MAX_CLIENTS ) );
|
|
newHitToggle = msg.ReadBits( 1 ) != 0;
|
|
weaponGone = msg.ReadBits( 1 ) != 0;
|
|
ReadBindFromSnapshot( msg );
|
|
|
|
//===================END OF ID DATA
|
|
|
|
inventory.maxHealth = msg.ReadShort(); //rww - since maxhealth can go above 100 in mp now
|
|
|
|
idQuat quat;
|
|
|
|
/*
|
|
if( spiritProxy.SetSpawnId( msg.ReadBits( 32 ) ) ) {
|
|
if (spiritProxy.GetEntity() && spiritProxy.GetEntity()->IsType(hhSpiritProxy::Type))
|
|
{
|
|
spiritProxy.GetEntity()->ActivateProxy( this, GetOrigin(), GetPhysics()->GetAxis(), viewAxis, viewAngles );
|
|
}
|
|
}*/
|
|
spiritProxy.SetSpawnId( msg.ReadBits( 32 ) );
|
|
|
|
//rww - more spiritwalk stuff
|
|
lastWeaponSpirit = msg.ReadBits(32);
|
|
|
|
bool spiritWalking = !!msg.ReadBits(1);
|
|
if (spiritWalking != bSpiritWalk && (weapon.IsValid() || !spiritWalking)) {
|
|
bSpiritWalk = spiritWalking;
|
|
|
|
LighterOff(); //make sure lighter is off when switching spiritwalk
|
|
|
|
if (bSpiritWalk) {
|
|
if (gameLocal.localClientNum == entityNumber) {
|
|
if (hud) {
|
|
hud->HandleNamedEvent("SwitchToEthereal");
|
|
}
|
|
gameLocal.SpiritWalkSoundMode( true );
|
|
}
|
|
|
|
// Set the player's skin to a glowy effect
|
|
SetSkinByName( spawnArgs.GetString("skin_Spiritwalk") );
|
|
SetShaderParm( SHADERPARM_TIMEOFFSET, 1.0f ); // TEMP: cjr - Required by the forcefield material. Can remove when a proper spiritwalk texture is made
|
|
|
|
//put bow in proper state
|
|
SelectEtherealWeapon();
|
|
}
|
|
else {
|
|
if (gameLocal.localClientNum == entityNumber) {
|
|
if (hud) {
|
|
hud->HandleNamedEvent("SwitchFromEthereal");
|
|
}
|
|
gameLocal.SpiritWalkSoundMode( false );
|
|
}
|
|
|
|
SetSkinByName( NULL );
|
|
}
|
|
}
|
|
|
|
//not needed anymore
|
|
/*
|
|
// Handle progress bar
|
|
bool bBar = msg.ReadBits(1) != 0;
|
|
float value = msg.ReadFloat();
|
|
int state = msg.ReadBits(2);
|
|
CL_UpdateProgress(bBar, value, state);
|
|
*/
|
|
|
|
// if not a local client assume the client has all ammo types
|
|
if ( entityNumber != gameLocal.localClientNum ) {
|
|
for( i = 0; i < AMMO_NUMTYPES; i++ ) {
|
|
inventory.ammo[ i ] = 999;
|
|
}
|
|
}
|
|
|
|
//rww - send ammo for current weapon - this is because our weapon routines rely on if we have ammo
|
|
//in scripts and so on, and will be predicted wrong for other players (we get our own ammo in the playerstate)
|
|
//rwwFIXME: if we have proper weapon switching when you run out of ammo will this actually be necessary?
|
|
//i don't think it's all that costly, but still.
|
|
int primAmmo = msg.ReadBits(ASYNC_PLAYER_INV_AMMO_BITS);
|
|
int altAmmo = msg.ReadBits(ASYNC_PLAYER_INV_AMMO_BITS);
|
|
if (entityNumber != gameLocal.localClientNum) {
|
|
//since we have our own ammo in the playerstate, don't want to stomp it
|
|
if (weapon.IsValid()) {
|
|
ammo_t ammoType = weapon->GetAmmoType();
|
|
if (ammoType > 0) {
|
|
inventory.ammo[ammoType] = primAmmo;
|
|
}
|
|
ammoType = weapon->GetAltAmmoType();
|
|
if (ammoType > 0) {
|
|
inventory.ammo[ammoType] = altAmmo;
|
|
}
|
|
}
|
|
}
|
|
|
|
//need to sync buttonMask since we're using it for some state-based things
|
|
buttonMask = msg.ReadBits(8);
|
|
|
|
SetEyeHeight(msg.ReadFloat());
|
|
|
|
//HUMANHEAD PCF rww 05/04/06 - do not sync AI_VEHICLE, it is now based purely on the clientside
|
|
//enter/exit of vehicles, with the vehControlling stack bool determining if we should be in a
|
|
//vehicle on the client or not.
|
|
//AI_VEHICLE = !!msg.ReadBits(1);
|
|
|
|
//rww - hand stuff
|
|
//weaponHandState.ReadFromSnapshot(msg);
|
|
hand.SetSpawnId(msg.ReadBits(32));
|
|
handNext.SetSpawnId(msg.ReadBits(32));
|
|
|
|
//HUMANHEAD PCF rww 05/04/06 - base this check on AI_VEHICLE and make sure the player is exited,
|
|
//even if the vehicle is not in the snapshot at this point.
|
|
if (!vehControlling && AI_VEHICLE) { //then exit on the client
|
|
if (vehicleInterfaceLocal.ControllingVehicle()) {
|
|
hhVehicle *veh = GetVehicleInterface()->GetVehicle();
|
|
if (veh && veh->GetPilot() == this) {
|
|
veh->EjectPilot();
|
|
}
|
|
}
|
|
ExitVehicle(NULL); //be extra safe in case vehicle is no longer around
|
|
}
|
|
|
|
//jsh - vehicle stuff
|
|
int vehSpawnId = msg.ReadBits( 32 );
|
|
//HUMANHEAD PCF rww 05/04/06 - base this check ON AI_VEHICLE, in case snapshot where controlling and
|
|
//where the vehicle is set do not exactly coincide.
|
|
vehicleInterfaceLocal.SetVehicleSpawnId( vehSpawnId );
|
|
if( !AI_VEHICLE ) {
|
|
if (vehControlling) {
|
|
hhVehicle *veh = vehicleInterfaceLocal.GetVehicle();
|
|
if (veh && veh->IsType(hhVehicle::Type)) {
|
|
//GetVehicleInterface()->TakeControl( vehicleInterfaceLocal.GetVehicle(), this );
|
|
EnterVehicle( vehicleInterfaceLocal.GetVehicle() );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
if( vehicleInterfaceLocal.SetHandSpawnId( msg.ReadBits( 32 ) ) ) {
|
|
vehicleInterfaceLocal.GetHandEntity()->AttachHand( this, true );
|
|
}
|
|
*/
|
|
vehicleInterfaceLocal.SetHandSpawnId( msg.ReadBits( 32 ) );
|
|
//vehicleInterfaceLocal.GetWeaponHandState()->ReadFromSnapshot(msg);
|
|
|
|
//rww - lighter sync validation
|
|
bool newLighterOn = !!msg.ReadBits(1);
|
|
if (newLighterOn != IsLighterOn()) {
|
|
ToggleLighter();
|
|
}
|
|
|
|
//rww - tractor
|
|
fl.isTractored = !!msg.ReadBits(1);
|
|
|
|
spiritwalkSoundController.ReadFromSnapshot(msg);
|
|
//deathwalkSoundController.ReadFromSnapshot(msg);
|
|
wallwalkSoundController.ReadFromSnapshot(msg);
|
|
|
|
//=========================================== start id code
|
|
if ( weapon.SetSpawnId( weaponSpawnId ) ) {
|
|
//HUMANHEAD rww - i am getting crashes here and the stack is all messed up,
|
|
//claiming that SetOwner is off into null, when supposedly the entity is not.
|
|
//rearranging this code so it's easier to see what's going on in a crash.
|
|
hhWeapon *weapEnt = weapon.GetEntity();
|
|
if ( weapEnt ) {
|
|
// maintain ownership locally
|
|
weapEnt->SetOwner( this );
|
|
}
|
|
currentWeapon = -1;
|
|
}
|
|
|
|
//HUMANHEAD rww - but we do want ammo count for the leechgun on other players for prediction
|
|
//note - current weapon's ammo always sent now
|
|
//ammo_t energyammo = hhWeaponFireController::GetAmmoType("ammo_energy");
|
|
//inventory.ammo[energyammo] = msg.ReadBits(ASYNC_PLAYER_INV_AMMO_BITS);
|
|
|
|
if ( oldHealth > 0 && health <= 0 ) {
|
|
if ( stateHitch ) {
|
|
// so we just hide and don't show a death skin
|
|
UpdateDeathSkin( true );
|
|
}
|
|
// die
|
|
AI_DEAD = true;
|
|
SetAnimState( ANIMCHANNEL_LEGS, "Legs_Death", 4 );
|
|
SetAnimState( ANIMCHANNEL_TORSO, "Torso_Death", 4 );
|
|
SetWaitState( "" );
|
|
animator.ClearAllJoints();
|
|
|
|
//HUMANHEAD rww - don't want this
|
|
/*
|
|
if ( entityNumber == gameLocal.localClientNum ) {
|
|
playerView.Fade( colorBlack, 12000 );
|
|
}
|
|
*/
|
|
//HUMANHEAD END
|
|
|
|
fl.clientEvents = true; //client event hackery for non-critical cosmetic cleanups
|
|
PostEventMS(&EV_RespawnCleanup, 32);
|
|
fl.clientEvents = false;
|
|
StartRagdoll();
|
|
physicsObj.SetMovementType( PM_DEAD );
|
|
if ( !stateHitch ) {
|
|
//HUMANHEAD PCF rww 09/15/06 - female mp sounds
|
|
if (IsFemale()) {
|
|
StartSound( "snd_death_female", SND_CHANNEL_VOICE, 0, false, NULL );
|
|
}
|
|
else {
|
|
//HUMANHEAD END
|
|
StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL );
|
|
}
|
|
}
|
|
if ( weapon.GetEntity() ) {
|
|
weapon.GetEntity()->OwnerDied();
|
|
}
|
|
|
|
//HUMANHEAD rww
|
|
GetPhysics()->SetContents(0);
|
|
Hide();
|
|
|
|
//so camera can operate on client.
|
|
minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME;
|
|
maxRespawnTime = minRespawnTime + 10000;
|
|
//HUMANHEAD END
|
|
} else if ( oldHealth <= 0 && health > 0 ) {
|
|
// respawn
|
|
Init();
|
|
StopRagdoll();
|
|
SetPhysics( &physicsObj );
|
|
physicsObj.EnableClip();
|
|
SetCombatContents( true );
|
|
//HUMANHEAD rww
|
|
if (!spectating) {
|
|
Show();
|
|
}
|
|
//HUMANHEAD END
|
|
} else if ( health < oldHealth && health > 0 ) {
|
|
if ( stateHitch ) {
|
|
lastDmgTime = gameLocal.time;
|
|
} else {
|
|
// damage feedback
|
|
const idDeclEntityDef *def = static_cast<const idDeclEntityDef *>( declManager->DeclByIndex( DECL_ENTITYDEF, lastDamageDef, false ) );
|
|
if ( def ) {
|
|
playerView.DamageImpulse( lastDamageDir * viewAxis.Transpose(), &def->dict );
|
|
AI_PAIN = Pain( NULL, NULL, oldHealth - health, lastDamageDir, lastDamageLocation );
|
|
lastDmgTime = gameLocal.time;
|
|
} else {
|
|
common->Warning( "NET: no damage def for damage feedback '%s'\n", lastDamageDef );
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the player is alive, restore proper physics object
|
|
if ( health > 0 && IsActiveAF() ) {
|
|
StopRagdoll();
|
|
SetPhysics( &physicsObj );
|
|
physicsObj.EnableClip();
|
|
SetCombatContents( true );
|
|
}
|
|
|
|
if ( idealWeapon != newIdealWeapon ) {
|
|
if ( stateHitch ) {
|
|
weaponCatchup = true;
|
|
}
|
|
idealWeapon = newIdealWeapon;
|
|
UpdateHudWeapon();
|
|
}
|
|
|
|
if ( lastHitToggle != newHitToggle ) {
|
|
SetLastHitTime( gameLocal.realClientTime );
|
|
}
|
|
//=========================================== end id code
|
|
|
|
if ( msg.HasChanged() ) {
|
|
UpdateVisuals();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
hhPlayer::ServerReceiveEvent
|
|
================
|
|
*/
|
|
bool hhPlayer::ServerReceiveEvent( int event, int time, const idBitMsg &msg ) {
|
|
if ( idEntity::ServerReceiveEvent( event, time, msg ) ) {
|
|
return true;
|
|
}
|
|
|
|
// client->server events
|
|
switch( event ) {
|
|
case EVENT_IMPULSE: {
|
|
int impulse = msg.ReadBits( 6 );
|
|
PerformImpulse(impulse);
|
|
if (vehicleInterfaceLocal.ControllingVehicle()) {
|
|
hhVehicle *veh = vehicleInterfaceLocal.GetVehicle();
|
|
if (veh) {
|
|
veh->DoPlayerImpulse(impulse);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
default: {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
hhPlayer::WritePlayerStateToSnapshot
|
|
================
|
|
*/
|
|
void hhPlayer::WritePlayerStateToSnapshot( idBitMsgDelta &msg ) const {
|
|
idPlayer::WritePlayerStateToSnapshot(msg);
|
|
|
|
//rww - extra playerView stuff
|
|
playerView.WriteToSnapshot(msg);
|
|
|
|
//rww - scope view
|
|
msg.WriteBits(bScopeView, 1);
|
|
|
|
//rww - zoom fov
|
|
msg.WriteFloat(zoomFov.GetDuration());
|
|
msg.WriteFloat(zoomFov.GetEndValue());
|
|
msg.WriteFloat(zoomFov.GetStartTime());
|
|
msg.WriteFloat(zoomFov.GetStartValue());
|
|
|
|
//rww - view angle sensitivity
|
|
msg.WriteFloat(GetViewAnglesSensitivity());
|
|
}
|
|
|
|
/*
|
|
================
|
|
hhPlayer::ReadPlayerStateFromSnapshot
|
|
================
|
|
*/
|
|
void hhPlayer::ReadPlayerStateFromSnapshot( const idBitMsgDelta &msg ) {
|
|
idPlayer::ReadPlayerStateFromSnapshot(msg);
|
|
|
|
//rww - extra playerView stuff
|
|
playerView.ReadFromSnapshot(msg);
|
|
|
|
//rww - scope and fov handling
|
|
bool canOverrideView = true;
|
|
if (weapon.IsValid() && weapon->IsType(hhWeaponZoomable::Type)) { //if we have a zoomed weapon, don't override with snapshot while the prediction fudge timer is on
|
|
hhWeaponZoomable *weap = static_cast<hhWeaponZoomable *>(weapon.GetEntity());
|
|
if (weap->clientZoomTime >= gameLocal.time) {
|
|
canOverrideView = false;
|
|
}
|
|
}
|
|
|
|
bool scopeView = !!msg.ReadBits(1);
|
|
float zfDur = msg.ReadFloat();
|
|
float zfEnd = msg.ReadFloat();
|
|
float zfStT = msg.ReadFloat();
|
|
float zfStV = msg.ReadFloat();
|
|
|
|
renderSystem->SetShuttleView( InVehicle() );
|
|
|
|
if (canOverrideView) { //if we are currently allowed to override with snapshot values, do so.
|
|
bScopeView = scopeView;
|
|
if (bScopeView != renderSystem->IsScopeView()) {
|
|
renderSystem->SetScopeView(bScopeView);
|
|
}
|
|
zoomFov.SetDuration(zfDur);
|
|
zoomFov.SetEndValue(zfEnd);
|
|
zoomFov.SetStartTime(zfStT);
|
|
zoomFov.SetStartValue(zfStV);
|
|
}
|
|
|
|
//rww - view angle sensitivity
|
|
SetViewAnglesSensitivity(msg.ReadFloat());
|
|
}
|
|
|
|
/*
|
|
================
|
|
hhPlayer::ClientPredictionThink
|
|
================
|
|
*/
|
|
void hhPlayer::ClientPredictionThink( void ) {
|
|
if (gameLocal.localClientNum != entityNumber && forcePredictionButtons) {
|
|
//used by some weapons which rely on prediction-based projectiles, to ensure we don't miss a launch
|
|
gameLocal.usercmds[ entityNumber ].buttons |= forcePredictionButtons;
|
|
if (gameLocal.isNewFrame) {
|
|
forcePredictionButtons = 0;
|
|
}
|
|
}
|
|
|
|
idPlayer::ClientPredictionThink();
|
|
|
|
if (InVehicle()) {
|
|
UpdateHud( GetVehicleInterfaceLocal()->GetHUD() );
|
|
}
|
|
else {
|
|
UpdateHud( hud );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
hhPlayer::GetPhysicsToVisualTransform
|
|
================
|
|
*/
|
|
bool hhPlayer::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) {
|
|
if ( af.IsActive() ) {
|
|
af.GetPhysicsToVisualTransform( origin, axis );
|
|
return true;
|
|
}
|
|
|
|
//rww - adopted id's smoothing code
|
|
if ( gameLocal.isClient && !bindMaster && gameLocal.framenum >= smoothedFrame && ( entityNumber != gameLocal.localClientNum || selfSmooth ) ) {
|
|
if (!smoothedOriginUpdated) {
|
|
idVec3 renderOrigin = TransformToPlayerSpace(modelOffset);
|
|
idVec3 originalOrigin = renderOrigin;
|
|
|
|
//id's cheesy smooth code (changed to vec3 because there is no "down" in prey)
|
|
idVec3 originDiff = renderOrigin - smoothedOrigin;
|
|
if (smoothedFrame == 0) { //for teleporting
|
|
originDiff = vec3_origin;
|
|
}
|
|
smoothedOrigin = renderOrigin;
|
|
if ( originDiff.LengthSqr() < Square( 100.0f ) ) {
|
|
// smoothen by pushing back to the previous position
|
|
if ( selfSmooth ) {
|
|
assert( entityNumber == gameLocal.localClientNum );
|
|
renderOrigin -= net_clientSelfSmoothing.GetFloat() * originDiff;
|
|
} else {
|
|
renderOrigin -= gameLocal.clientSmoothing * originDiff;
|
|
}
|
|
|
|
//get rid of the smoothing on the "vertical" axis
|
|
idVec3 a(renderOrigin.x*viewAxis[2].x, renderOrigin.y*viewAxis[2].y, renderOrigin.z*viewAxis[2].z);
|
|
idVec3 b(originalOrigin.x*viewAxis[2].x, originalOrigin.y*viewAxis[2].y, originalOrigin.z*viewAxis[2].z);
|
|
renderOrigin -= (a-b);
|
|
}
|
|
|
|
smoothedFrame = gameLocal.framenum;
|
|
smoothedOriginUpdated = true;
|
|
}
|
|
|
|
axis = viewAxis;
|
|
origin = ( smoothedOrigin - GetPhysics()->GetOrigin() ) * GetEyeAxis().Transpose();
|
|
return true;
|
|
}
|
|
|
|
if (bClampYaw) { //rww - lock model angles
|
|
idVec3 masterOrigin;
|
|
idMat3 masterAxis;
|
|
|
|
if (GetMasterPosition(masterOrigin, masterAxis)) {
|
|
axis = masterAxis;
|
|
origin = modelOffset;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
axis = viewAxis;
|
|
origin = modelOffset;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool hhPlayer::UpdateAnimationControllers( void ) {
|
|
bool retValue = idPlayer::UpdateAnimationControllers();
|
|
|
|
hhAnimator *theAnimator;
|
|
if (head.IsValid()) {
|
|
theAnimator = head->GetAnimator();
|
|
}
|
|
else {
|
|
theAnimator = GetAnimator();
|
|
}
|
|
JawFlap(theAnimator);
|
|
|
|
return retValue;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::Event_PlayWeaponAnim
|
|
===============
|
|
*/
|
|
void hhPlayer::Event_PlayWeaponAnim( const char* animName, int numTries ) {
|
|
//AOB: I would like a better solution then constantly banging until weapon is valid.
|
|
if( (!weapon.IsValid() || GetCurrentWeapon() != idealWeapon) && numTries > 0 ) {
|
|
CancelEvents( &EV_PlayWeaponAnim );
|
|
PostEventMS( &EV_PlayWeaponAnim, 50, animName, numTries - 1 );
|
|
return;
|
|
}
|
|
|
|
CancelEvents( &EV_PlayWeaponAnim );
|
|
if( weapon.IsValid() ) {
|
|
weapon->ProcessEvent( &EV_PlayAnimWhenReady, animName );
|
|
}
|
|
}
|
|
|
|
void hhPlayer::DialogStart(bool bDisallowPlayerDeath, bool bVoiceDucking, bool bLowerWeapon) {
|
|
bDialogDamageMode = bDisallowPlayerDeath;
|
|
bDialogWeaponMode = bLowerWeapon;
|
|
gameLocal.DialogSoundMode(bVoiceDucking);
|
|
if (bDialogWeaponMode) { // Lock weapon
|
|
preCinematicWeaponFlags = weaponFlags;
|
|
preCinematicWeapon = GetIdealWeapon();
|
|
LockWeapon(-1);
|
|
}
|
|
}
|
|
|
|
void hhPlayer::DialogStop() {
|
|
bDialogDamageMode = false;
|
|
gameLocal.DialogSoundMode(false);
|
|
if (bDialogWeaponMode) {
|
|
weaponFlags = preCinematicWeaponFlags;
|
|
SelectWeapon(preCinematicWeapon, true);
|
|
bDialogWeaponMode = false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::Event_DialogStart
|
|
HUMANHEAD pdm
|
|
===============
|
|
*/
|
|
void hhPlayer::Event_DialogStart( int bDisallowPlayerDeath, int bVoiceDucking, int bLowerWeapon ) {
|
|
DialogStart(bDisallowPlayerDeath != 0, bVoiceDucking != 0, bLowerWeapon != 0);
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::Event_DialogStop
|
|
HUMANHEAD pdm
|
|
===============
|
|
*/
|
|
void hhPlayer::Event_DialogStop() {
|
|
DialogStop();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::Event_LotaTunnelMode
|
|
HUMANHEAD pdm
|
|
===============
|
|
*/
|
|
void hhPlayer::Event_LotaTunnelMode(bool on) {
|
|
bLotaTunnelMode = on;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::Event_Cinematic
|
|
HUMANHEAD pdm
|
|
===============
|
|
*/
|
|
void hhPlayer::Event_Cinematic( int on, int lockView ) {
|
|
bool cinematic = (on != 0);
|
|
InCinematic( cinematic );
|
|
playerView.SetLetterBox(cinematic);
|
|
if (cinematic) {
|
|
if ( IsSpiritOrDeathwalking() ) {
|
|
StopSpiritWalk();
|
|
}
|
|
if ( IsPossessed() ) {
|
|
Unpossess();
|
|
}
|
|
|
|
// Lock weapon
|
|
preCinematicWeaponFlags = weaponFlags;
|
|
preCinematicWeapon = GetCurrentWeapon();
|
|
LockWeapon(-1);
|
|
|
|
// disable damage
|
|
fl.takedamage = false;
|
|
this->lockView = (lockView != 0);
|
|
}
|
|
else {
|
|
fl.takedamage = true;
|
|
this->lockView = false;
|
|
|
|
//UnlockWeapon(preCinematicWeapon);
|
|
weaponFlags = preCinematicWeaponFlags;
|
|
//SetCurrentWeapon(preCinematicWeapon);
|
|
SelectWeapon(preCinematicWeapon, true);
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::Event_PrepareToResurrect
|
|
//
|
|
// Flashes the screen / fades the FOV
|
|
//=============================================================================
|
|
|
|
void hhPlayer::Event_PrepareToResurrect() {
|
|
deathWalkFlash = 0;
|
|
possessionFOV = 90.0f;
|
|
PostEventSec( &EV_ResurrectScreenFade, 0 );
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::Event_ResurrectScreenFade
|
|
//
|
|
// The actual code to incrementally flash the screen
|
|
//=============================================================================
|
|
|
|
void hhPlayer::Event_ResurrectScreenFade() {
|
|
deathWalkFlash += spawnArgs.GetFloat( "resurrectFlashChange", "0.04" );
|
|
if ( deathWalkFlash >= 1.0f ) {
|
|
deathWalkFlash = 1.0f;
|
|
PostEventSec( &EV_Resurrect, 0.0f ); // Actually send player back to the physical realm
|
|
} else {
|
|
PostEventSec( &EV_ResurrectScreenFade, 0.02f );
|
|
}
|
|
|
|
possessionFOV += spawnArgs.GetFloat( "resurrectFOVChange", "1.0" );
|
|
playerView.SetViewOverlayColor( idVec4( 1.0f, 1.0f, 1.0f, deathWalkFlash ) );
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::Event_Resurrect
|
|
//
|
|
//=============================================================================
|
|
void hhPlayer::Event_Resurrect() {
|
|
Resurrect();
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::Event_Event_ShouldRemainAlignedToAxial
|
|
//
|
|
//=============================================================================
|
|
void hhPlayer::Event_ShouldRemainAlignedToAxial( bool remainAligned ) {
|
|
ShouldRemainAlignedToAxial( remainAligned );
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::Event_OrientToGravity
|
|
//
|
|
//=============================================================================
|
|
void hhPlayer::Event_OrientToGravity( bool orient ) {
|
|
OrientToGravity( orient );
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::Event_ResetGravity
|
|
// HUMANHEAD: pdm: Posted when entity is leaving a gravity zone
|
|
//=============================================================================
|
|
void hhPlayer::Event_ResetGravity() {
|
|
if( IsWallWalking() ) {
|
|
return; // Don't reset if wallwalking
|
|
}
|
|
|
|
if (spectating) { //HUMANHEAD rww - don't reset if spectating
|
|
return;
|
|
}
|
|
|
|
idPlayer::Event_ResetGravity();
|
|
|
|
OrientToGravity( true ); // let it reset orientation
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::Event_SetOverlayMaterial
|
|
//
|
|
//=============================================================================
|
|
void hhPlayer::Event_SetOverlayMaterial( const char *mtrName, const int requiresScratch ) {
|
|
if ( mtrName && mtrName[0] ) {
|
|
playerView.SetViewOverlayMaterial( declManager->FindMaterial(mtrName), requiresScratch );
|
|
} else {
|
|
playerView.SetViewOverlayMaterial( NULL );
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::Event_SetOverlayTime
|
|
//
|
|
//=============================================================================
|
|
void hhPlayer::Event_SetOverlayTime( const float newTime, const int requiresScratch ) {
|
|
playerView.SetViewOverlayTime( newTime, requiresScratch );
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::Event_SetOverlayColor
|
|
//
|
|
//=============================================================================
|
|
void hhPlayer::Event_SetOverlayColor( const float r, const float g, const float b, const float a ) {
|
|
idVec4 color;
|
|
|
|
color.x = r;
|
|
color.y = g;
|
|
color.z = b;
|
|
color.w = a;
|
|
|
|
playerView.SetViewOverlayColor( color );
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::Event_DDAHeartBeat
|
|
//
|
|
//=============================================================================
|
|
|
|
void hhPlayer::Event_DDAHeartBeat() {
|
|
gameLocal.GetDDA()->DDA_Heartbeat( this );
|
|
PostEventMS( &EV_DDAHeartbeat, ddaHeartbeatMS );
|
|
}
|
|
|
|
//================
|
|
//hhPlayer::Save
|
|
//================
|
|
void hhPlayer::Save( idSaveGame *savefile ) const {
|
|
// Public vars
|
|
savefile->Write(weaponInfo, sizeof(weaponInfo_t)*15);
|
|
savefile->Write(altWeaponInfo, sizeof(weaponInfo_t)*15);
|
|
savefile->WriteFloat( lighterTemperature );
|
|
savefile->WriteRenderLight( lighter );
|
|
//HUMANHEAD PCF mdl 05/04/06 - Don't save light handles
|
|
//savefile->WriteInt( lighterHandle );
|
|
spiritProxy.Save( savefile );
|
|
savefile->WriteInt( lastWeaponSpirit );
|
|
talon.Save( savefile );
|
|
savefile->WriteInt( nextTalonAttackCommentTime );
|
|
savefile->WriteBool( bTalonAttackComment );
|
|
savefile->WriteBool( bSpiritWalk );
|
|
savefile->WriteBool( bDeathWalk );
|
|
savefile->WriteBool( bReallyDead );
|
|
guiWantsControls.Save( savefile );
|
|
savefile->WriteFloat( deathWalkFlash );
|
|
savefile->WriteInt( deathWalkTime );
|
|
savefile->WriteInt( deathWalkPower );
|
|
savefile->WriteBool( bInDeathwalkTransition );
|
|
savefile->WriteBool( bInCinematic );
|
|
savefile->WriteBool( bPlayingLowHealthSound );
|
|
savefile->WriteBool( bPossessed );
|
|
savefile->WriteFloat( possessionTimer );
|
|
savefile->WriteFloat( possessionFOV );
|
|
savefile->WriteInt( preCinematicWeapon );
|
|
savefile->WriteInt( preCinematicWeaponFlags );
|
|
savefile->WriteInt( lastDamagedTime );
|
|
savefile->WriteStaticObject( weaponHandState );
|
|
hand.Save( savefile );
|
|
handNext.Save( savefile );
|
|
possessedTommy.Save( savefile );
|
|
|
|
savefile->WriteStaticObject( vehicleInterfaceLocal );
|
|
savefile->WriteObject( vehicleInterfaceLocal.GetVehicle() );
|
|
|
|
deathLookAtEntity.Save( savefile );
|
|
savefile->WriteString( deathLookAtBone );
|
|
savefile->WriteString( deathCameraBone );
|
|
savefile->WriteStaticObject( spiritwalkSoundController );
|
|
savefile->WriteStaticObject( deathwalkSoundController );
|
|
savefile->WriteStaticObject( wallwalkSoundController );
|
|
savefile->WriteBool( bShowProgressBar );
|
|
savefile->WriteFloat( progressBarValue );
|
|
|
|
savefile->WriteFloat( progressBarGuiValue.GetStartTime() ); // idInterpolate<float>
|
|
savefile->WriteFloat( progressBarGuiValue.GetDuration() );
|
|
savefile->WriteFloat( progressBarGuiValue.GetStartValue() );
|
|
savefile->WriteFloat( progressBarGuiValue.GetEndValue() );
|
|
|
|
savefile->WriteInt( progressBarState );
|
|
savefile->WriteBool( bClampYaw );
|
|
savefile->WriteFloat( maxRelativeYaw );
|
|
savefile->WriteFloat( maxRelativePitch );
|
|
savefile->WriteFloat( bob );
|
|
savefile->WriteInt( lastAppliedBobCycle );
|
|
savefile->WriteInt( prevStepUpTime );
|
|
savefile->WriteVec3( prevStepUpOrigin );
|
|
savefile->WriteFloat( crashlandSpeed_fatal );
|
|
savefile->WriteFloat( crashlandSpeed_soft );
|
|
savefile->WriteFloat( crashlandSpeed_jump );
|
|
// Protected vars
|
|
savefile->WriteUserInterface( guiOverlay, false );
|
|
thirdPersonCameraClipBounds.Save( savefile );
|
|
savefile->WriteFloat( viewAnglesSensitivity );
|
|
savefile->WriteInt( lastResurrectTime );
|
|
savefile->WriteInt( spiritDrainHeartbeatMS );
|
|
savefile->WriteInt( ddaHeartbeatMS );
|
|
|
|
savefile->WriteInt( spiritWalkToggleTime );
|
|
savefile->WriteBool( bDeathWalkStage2 );
|
|
savefile->WriteBool( bFrozen );
|
|
savefile->WriteAngles( untransformedViewAngles );
|
|
savefile->WriteMat3( untransformedViewAxis );
|
|
savefile->WriteInt( nextSpiritTime );
|
|
|
|
savefile->WriteFloat( cinematicFOV.GetStartTime() );
|
|
savefile->WriteFloat( cinematicFOV.GetAcceleration() );
|
|
savefile->WriteFloat( cinematicFOV.GetDeceleration() );
|
|
savefile->WriteFloat( cinematicFOV.GetDuration() );
|
|
savefile->WriteFloat( cinematicFOV.GetStartValue() );
|
|
savefile->WriteFloat( cinematicFOV.GetEndValue() );
|
|
savefile->WriteBool( bAllowSpirit );
|
|
savefile->WriteInt( airAttackerTime );
|
|
savefile->WriteBool( bScopeView );
|
|
savefile->WriteInt( ddaNumEnemies );
|
|
savefile->WriteFloat( ddaProbabilityAccum );
|
|
savefile->WriteInt( weaponFlags );
|
|
savefile->WriteBool( lockView );
|
|
|
|
savefile->WriteBool( bCollidingWithPortal );
|
|
savefile->WriteBool( bLotaTunnelMode );
|
|
savefile->WriteInt( forcePredictionButtons );
|
|
|
|
for (int ix=0; ix<MAX_TRACKED_ATTACKERS; ix++) {
|
|
lastAttackers[ix].attacker.Save( savefile );
|
|
savefile->WriteInt( lastAttackers[ix].time );
|
|
savefile->WriteBool( lastAttackers[ix].displayed );
|
|
}
|
|
|
|
if ( InVehicle() ) {
|
|
savefile->WriteMat3( vehicleInterfaceLocal.GetVehicle()->GetPhysics()->GetAxis() );
|
|
savefile->WriteVec3( vehicleInterfaceLocal.GetVehicle()->GetAxis()[0] );
|
|
}
|
|
|
|
//HUMANHEAD PCF mdl 04/28/06 - Moved camera interpolater down here to fix jump off wallwalk view angle problem
|
|
cameraInterpolator.Save( savefile );
|
|
//HUMANHEAD PCF mdl 05/04/06 - Save whether the light handle is active
|
|
savefile->WriteBool( IsLighterOn() );
|
|
}
|
|
|
|
//================
|
|
//hhPlayer::Restore
|
|
//================
|
|
void hhPlayer::Restore( idRestoreGame *savefile ) {
|
|
// Public vars
|
|
savefile->Read(weaponInfo, sizeof(weaponInfo_t)*15);
|
|
savefile->Read(altWeaponInfo, sizeof(weaponInfo_t)*15);
|
|
savefile->ReadFloat( lighterTemperature );
|
|
savefile->ReadRenderLight( lighter );
|
|
//HUMANHEAD PCF mdl 05/04/06 - Don't save light handles
|
|
//savefile->ReadInt( lighterHandle );
|
|
spiritProxy.Restore( savefile );
|
|
savefile->ReadInt( lastWeaponSpirit );
|
|
talon.Restore( savefile );
|
|
savefile->ReadInt( nextTalonAttackCommentTime );
|
|
savefile->ReadBool( bTalonAttackComment );
|
|
savefile->ReadBool( bSpiritWalk );
|
|
savefile->ReadBool( bDeathWalk );
|
|
savefile->ReadBool( bReallyDead );
|
|
guiWantsControls.Restore( savefile );
|
|
savefile->ReadFloat( deathWalkFlash );
|
|
savefile->ReadInt( deathWalkTime );
|
|
savefile->ReadInt( deathWalkPower );
|
|
savefile->ReadBool( bInDeathwalkTransition );
|
|
savefile->ReadBool( bInCinematic );
|
|
savefile->ReadBool( bPlayingLowHealthSound );
|
|
savefile->ReadBool( bPossessed );
|
|
savefile->ReadFloat( possessionTimer );
|
|
savefile->ReadFloat( possessionFOV );
|
|
savefile->ReadInt( preCinematicWeapon );
|
|
savefile->ReadInt( preCinematicWeaponFlags );
|
|
savefile->ReadInt( lastDamagedTime );
|
|
savefile->ReadStaticObject( weaponHandState );
|
|
hand.Restore( savefile );
|
|
handNext.Restore( savefile );
|
|
possessedTommy.Restore( savefile );
|
|
|
|
savefile->ReadStaticObject( vehicleInterfaceLocal );
|
|
SetVehicleInterface( &vehicleInterfaceLocal );
|
|
|
|
hhVehicle *vehicle;
|
|
savefile->ReadObject( reinterpret_cast<idClass *&> ( vehicle ) );
|
|
if( vehicle ) {
|
|
vehicle->RestorePilot( &vehicleInterfaceLocal );
|
|
}
|
|
|
|
deathLookAtEntity.Restore( savefile );
|
|
savefile->ReadString( deathLookAtBone );
|
|
savefile->ReadString( deathCameraBone );
|
|
savefile->ReadStaticObject( spiritwalkSoundController );
|
|
savefile->ReadStaticObject( deathwalkSoundController );
|
|
savefile->ReadStaticObject( wallwalkSoundController );
|
|
savefile->ReadBool( bShowProgressBar );
|
|
savefile->ReadFloat( progressBarValue );
|
|
|
|
float set;
|
|
savefile->ReadFloat( set ); // idInterpolate<float>
|
|
progressBarGuiValue.SetStartTime( set );
|
|
savefile->ReadFloat( set );
|
|
progressBarGuiValue.SetDuration( set );
|
|
savefile->ReadFloat( set );
|
|
progressBarGuiValue.SetStartValue(set);
|
|
savefile->ReadFloat( set );
|
|
progressBarGuiValue.SetEndValue( set );
|
|
|
|
savefile->ReadInt( progressBarState );
|
|
savefile->ReadBool( bClampYaw );
|
|
savefile->ReadFloat( maxRelativeYaw );
|
|
savefile->ReadFloat( maxRelativePitch );
|
|
savefile->ReadFloat( bob );
|
|
savefile->ReadInt( lastAppliedBobCycle );
|
|
savefile->ReadInt( prevStepUpTime );
|
|
savefile->ReadVec3( prevStepUpOrigin );
|
|
savefile->ReadFloat( crashlandSpeed_fatal );
|
|
savefile->ReadFloat( crashlandSpeed_soft );
|
|
savefile->ReadFloat( crashlandSpeed_jump );
|
|
// Protected vars
|
|
savefile->ReadUserInterface( guiOverlay );
|
|
|
|
thirdPersonCameraClipBounds.Restore( savefile );
|
|
savefile->ReadFloat( viewAnglesSensitivity );
|
|
savefile->ReadInt( lastResurrectTime );
|
|
savefile->ReadInt( spiritDrainHeartbeatMS );
|
|
savefile->ReadInt( ddaHeartbeatMS );
|
|
|
|
savefile->ReadInt( spiritWalkToggleTime );
|
|
savefile->ReadBool( bDeathWalkStage2 );
|
|
savefile->ReadBool( bFrozen );
|
|
savefile->ReadAngles( untransformedViewAngles );
|
|
savefile->ReadMat3( untransformedViewAxis );
|
|
savefile->ReadInt( nextSpiritTime );
|
|
|
|
float startTime, accelTime, decelTime, duration, startPos, endPos;
|
|
|
|
savefile->ReadFloat( startTime );
|
|
savefile->ReadFloat( accelTime );
|
|
savefile->ReadFloat( decelTime );
|
|
savefile->ReadFloat( duration );
|
|
savefile->ReadFloat( startPos );
|
|
savefile->ReadFloat( endPos );
|
|
cinematicFOV.Init( startTime, accelTime, decelTime, duration, startPos, endPos );
|
|
savefile->ReadBool( bAllowSpirit );
|
|
savefile->ReadInt( airAttackerTime );
|
|
savefile->ReadBool( bScopeView );
|
|
savefile->ReadInt( ddaNumEnemies );
|
|
savefile->ReadFloat( ddaProbabilityAccum );
|
|
savefile->ReadInt( weaponFlags );
|
|
savefile->ReadBool( lockView );
|
|
|
|
savefile->ReadBool( bCollidingWithPortal );
|
|
savefile->ReadBool( bLotaTunnelMode );
|
|
savefile->ReadInt( forcePredictionButtons );
|
|
|
|
for (int ix=0; ix<MAX_TRACKED_ATTACKERS; ix++) {
|
|
lastAttackers[ix].attacker.Restore( savefile );
|
|
savefile->ReadInt( lastAttackers[ix].time );
|
|
savefile->ReadBool( lastAttackers[ix].displayed );
|
|
}
|
|
|
|
// We don't want to preserve these
|
|
memset( &oldCmdAngles, 0, sizeof( oldCmdAngles ) );
|
|
|
|
kickSpring = spawnArgs.GetFloat( "kickSpring" ); //HUMANHEAD bjk
|
|
kickDamping = spawnArgs.GetFloat( "kickDamping" ); //HUMANHEAD bjk
|
|
|
|
if( InVehicle() ) {
|
|
hhVehicle *vehicle = vehicleInterfaceLocal.GetVehicle();
|
|
HH_ASSERT( vehicle );
|
|
|
|
idMat3 axis;
|
|
idVec3 axis0;
|
|
|
|
// This prevents a crashbug if the vehicle's physics doesn't exist yet.
|
|
savefile->ReadMat3( axis ); // vehicle->GetPhysics()->GetAxis()
|
|
savefile->ReadVec3( axis0 ); // vehicle->GetAxis()[0]
|
|
|
|
// Keep orientation correct in vehicles
|
|
RestoreOrientation( GetOrigin(), axis, axis0, axis0.ToAngles() );
|
|
|
|
//HUMANHEAD PCF mdl 05/02/06 - Added this to re-enable shuttle view
|
|
if (renderSystem) {
|
|
// Set shuttle view
|
|
renderSystem->SetShuttleView( true );
|
|
}
|
|
} else {
|
|
// Keep orientation correct for walkwalk and gravity rooms
|
|
RestoreOrientation( GetOrigin(), physicsObj.GetAxis(), viewAngles.ToMat3()[0], untransformedViewAngles );
|
|
}
|
|
|
|
//HUMANHEAD PCF mdl 04/28/06 - Moved camera interpolater down here to fix jump off wallwalk view angle problem
|
|
cameraInterpolator.Restore( savefile );
|
|
|
|
//HUMANHEAD PCF mdl 05/04/06 - Restore lighter
|
|
bool bLighter;
|
|
savefile->ReadBool( bLighter );
|
|
if ( bLighter ) {
|
|
lighterHandle = gameRenderWorld->AddLightDef( &lighter );
|
|
}
|
|
}
|
|
|
|
int hhPlayer::GetSpiritPower() {
|
|
ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName("ammo_spiritpower");
|
|
return inventory.ammo[ammo_spiritpower];
|
|
}
|
|
|
|
void hhPlayer::SetSpiritPower(int amount) {
|
|
ammo_t ammo_spiritpower = idWeapon::GetAmmoNumForName("ammo_spiritpower");
|
|
inventory.ammo[ammo_spiritpower] = amount;
|
|
if (inventory.ammo[ammo_spiritpower] > inventory.maxSpirit) {
|
|
inventory.ammo[ammo_spiritpower] = inventory.maxSpirit;
|
|
}
|
|
}
|
|
|
|
void hhPlayer::Event_StartHUDTranslation() {
|
|
vehicleInterfaceLocal.StartHUDTranslation();
|
|
}
|
|
|
|
void hhPlayer::Freeze(float unfreezeDelay) {
|
|
bFrozen = true;
|
|
StopFiring();
|
|
if ( weapon.IsValid() ) {
|
|
weapon->PutAway();
|
|
}
|
|
if ( unfreezeDelay > 0.0f ) {
|
|
PostEventSec( &EV_Unfreeze, unfreezeDelay );
|
|
}
|
|
}
|
|
|
|
void hhPlayer::Unfreeze(void) {
|
|
bFrozen = false;
|
|
if ( weapon.IsValid() ) {
|
|
weapon->PostEventMS( &EV_Show, 1000 );
|
|
weapon->PostEventMS( &EV_Weapon_WeaponRising, 1000 );
|
|
}
|
|
}
|
|
|
|
void hhPlayer::Event_Unfreeze() {
|
|
Unfreeze();
|
|
if ( bindMaster && bindMaster->RespondsTo( EV_BindUnfroze ) ) {
|
|
GetBindMaster()->PostEventMS( &EV_BindUnfroze, 0, this );
|
|
}
|
|
}
|
|
|
|
void hhPlayer::Event_GetSpiritPower() { //rww
|
|
idThread::ReturnFloat((float)GetSpiritPower());
|
|
}
|
|
|
|
void hhPlayer::Event_SetSpiritPower(const float s) { //rww
|
|
SetSpiritPower((int)s);
|
|
}
|
|
|
|
void hhPlayer::Event_OnGround() { // bg
|
|
idThread::ReturnFloat( physicsObj.HasGroundContacts() );
|
|
}
|
|
|
|
void hhPlayer::SetupWeaponFlags( void ) {
|
|
const char *mapName = gameLocal.serverInfo.GetString( "si_map" );
|
|
const idDecl *mapDecl = declManager->FindType(DECL_MAPDEF, mapName, false );
|
|
if ( !mapDecl ) {
|
|
weaponFlags = -1; // No map decls? Allow all weapons
|
|
return;
|
|
}
|
|
const idDeclEntityDef *mapInfo = static_cast<const idDeclEntityDef *>(mapDecl);
|
|
|
|
const char *weaponList = mapInfo->dict.GetString( "disableWeapons" );
|
|
weaponFlags = 0;
|
|
if ( weaponList && weaponList[0] ) {
|
|
if ( !idStr::Icmp( weaponList, "all" ) ) {
|
|
// No weapons are enabled, so just return
|
|
return;
|
|
}
|
|
|
|
if ( idStr::FindText( weaponList, "wrench", false ) == -1 ) {
|
|
weaponFlags |= HH_WEAPON_WRENCH;
|
|
}
|
|
if ( idStr::FindText( weaponList, "rifle", false ) == -1 ) {
|
|
weaponFlags |= HH_WEAPON_RIFLE;
|
|
}
|
|
if ( idStr::FindText( weaponList, "crawler", false ) == -1 ) {
|
|
weaponFlags |= HH_WEAPON_CRAWLER;
|
|
}
|
|
if ( idStr::FindText( weaponList, "autocannon", false ) == -1 ) {
|
|
weaponFlags |= HH_WEAPON_AUTOCANNON;
|
|
}
|
|
if ( idStr::FindText( weaponList, "hiderweapon", false ) == -1 ) {
|
|
weaponFlags |= HH_WEAPON_HIDERWEAPON;
|
|
}
|
|
if ( idStr::FindText( weaponList, "rocketlauncher", false ) == -1 ) {
|
|
weaponFlags |= HH_WEAPON_ROCKETLAUNCHER;
|
|
}
|
|
if ( idStr::FindText( weaponList, "soulstripper", false ) == -1 ){
|
|
weaponFlags |= HH_WEAPON_SOULSTRIPPER;
|
|
}
|
|
}
|
|
|
|
if ( weaponFlags == 0 ) {
|
|
// Allow all weapons by default
|
|
weaponFlags = -1;
|
|
}
|
|
}
|
|
|
|
void hhPlayer::LockWeapon( int weaponNum ) {
|
|
// Special case, -1 locks all weapons AND the lighter
|
|
if ( weaponNum == -1 ) {
|
|
if (IsLighterOn()) {
|
|
LighterOff();
|
|
}
|
|
if (weapon.IsValid()) {
|
|
weapon->PutAway();
|
|
}
|
|
weaponFlags = 0;
|
|
idealWeapon = 0;
|
|
currentWeapon = 0;
|
|
return;
|
|
}
|
|
|
|
if ( weaponNum <= 0 || weaponNum >= MAX_WEAPONS ) {
|
|
gameLocal.Error( "Attempted to unlock unknown weapon '%d'", weaponNum );
|
|
}
|
|
|
|
int flag = ( 1 << ( weaponNum - 1 ) );
|
|
if ( weaponFlags & flag ) { // Only lock if not already locked
|
|
weaponFlags &= ~flag;
|
|
if ( idealWeapon == weaponNum ) {
|
|
if (weapon.IsValid()) {
|
|
weapon->PutAway();
|
|
}
|
|
NextWeapon();
|
|
}
|
|
}
|
|
}
|
|
|
|
void hhPlayer::UnlockWeapon( int weaponNum ) {
|
|
// Special case, -1 unlocks all weapons
|
|
if ( weaponNum == -1 ) {
|
|
weaponFlags = -1;
|
|
if ( idealWeapon == 0 ) {
|
|
idealWeapon = 1; // Default to the wrench
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( weaponNum <= 0 || weaponNum >= MAX_WEAPONS ) {
|
|
gameLocal.Error( "Attempted to unlock unknown weapon '%d'", weaponNum );
|
|
}
|
|
|
|
int flag = ( 1 << ( weaponNum - 1 ) );
|
|
if ( weaponFlags & flag ) {
|
|
// Already unlocked
|
|
return;
|
|
}
|
|
|
|
weaponFlags |= flag;
|
|
idealWeapon = weaponNum;
|
|
}
|
|
|
|
bool hhPlayer::IsLocked(int weaponNum) {
|
|
//HUMANHEAD PCF mdl 05/05/06 - Changed to < 1 to catch weaponNum = -1
|
|
if (weaponNum < 1) {
|
|
return true; // CJR: if the player has no weapons, then consider all weapons locked
|
|
}
|
|
return !(weaponFlags & (1 << (weaponNum-1)));
|
|
}
|
|
|
|
void hhPlayer::Event_LockWeapon( int weaponNum ) {
|
|
LockWeapon( weaponNum );
|
|
}
|
|
|
|
void hhPlayer::Event_UnlockWeapon( int weaponNum ) {
|
|
UnlockWeapon( weaponNum );
|
|
}
|
|
|
|
//HUMANHEAD rdr
|
|
void hhPlayer::Event_SetPrivateCameraView( idEntity *camView, int noHide ) {
|
|
if ( camView && camView->IsType( idCamera::Type ) ) {
|
|
idPlayer::SetPrivateCameraView( static_cast<idCamera*>( camView ), noHide != 0 );
|
|
}
|
|
else {
|
|
idPlayer::SetPrivateCameraView( NULL );
|
|
}
|
|
}
|
|
|
|
//HUMANHEAD rdr
|
|
void hhPlayer::Event_SetCinematicFOV( float fieldOfView, float accelTime, float decelTime, float duration ) {
|
|
if ( duration > 0.f ) {
|
|
cinematicFOV.Init( gameLocal.time, SEC2MS( accelTime ), SEC2MS( decelTime ), SEC2MS( duration ), cinematicFOV.GetCurrentValue( gameLocal.GetTime() ), fieldOfView );
|
|
} else {
|
|
cinematicFOV.Init( gameLocal.time, 0.f, 0.f, 0.f, cinematicFOV.GetEndValue(), fieldOfView );
|
|
}
|
|
}
|
|
|
|
//HUMANHEAD rww
|
|
void hhPlayer::Event_StopSpiritWalk() {
|
|
StopSpiritWalk();
|
|
}
|
|
|
|
void hhPlayer::Event_DamagePlayer(idEntity *inflictor, idEntity *attacker, const idVec3 &dir, char *damageDefName, float damageScale, int location) {
|
|
Damage(inflictor, attacker, dir, damageDefName, damageScale, location);
|
|
}
|
|
//HUMANHEAD END
|
|
|
|
void hhPlayer::Event_GetSpiritProxy() {
|
|
if ( spiritProxy.IsValid() ) {
|
|
idThread::ReturnEntity( spiritProxy.GetEntity() );
|
|
} else {
|
|
idThread::ReturnEntity( NULL );
|
|
}
|
|
}
|
|
|
|
void hhPlayer::Event_IsSpiritWalking() {
|
|
idThread::ReturnInt( IsSpiritWalking() ? 1 : 0 );
|
|
}
|
|
|
|
void hhPlayer::Event_IsDeathWalking() {
|
|
idThread::ReturnInt( IsDeathWalking() ? 1 : 0 );
|
|
}
|
|
|
|
void hhPlayer::Event_AllowLighter( bool allow ) {
|
|
inventory.requirements.bCanUseLighter = allow;
|
|
if ( !allow && IsLighterOn() ) {
|
|
LighterOff();
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::Event_GetDDAValue
|
|
//
|
|
// Returns the current DDA value from zero to one
|
|
//=============================================================================
|
|
|
|
void hhPlayer::Event_GetDDAValue() {
|
|
idThread::ReturnFloat( gameLocal.GetDDAValue() );
|
|
}
|
|
|
|
//HUMANHEAD rww
|
|
hhArtificialPlayer::hhArtificialPlayer(void) {
|
|
memset(&lastAICmd, 0, sizeof(lastAICmd));
|
|
testCrouchActive = false;
|
|
testCrouchTime = 0;
|
|
}
|
|
|
|
void hhArtificialPlayer::Spawn( void ) {
|
|
idDict apUI;
|
|
gameLocal.GetAPUserInfo(apUI, entityNumber);
|
|
gameLocal.SetUserInfo(entityNumber, apUI, gameLocal.isClient, false);
|
|
|
|
SetPlayerModel(false); //force an update on the player model
|
|
}
|
|
|
|
void hhArtificialPlayer::Think( void ) {
|
|
PROFILE_START("hhArtificialPlayer::Think", PROFMASK_NORMAL);
|
|
bool iFeelHoppy = false;
|
|
usercmd_t *nextCmd = &gameLocal.usercmds[ entityNumber ];
|
|
idAngles idealAngles = GetViewAngles();
|
|
|
|
nextCmd->gameFrame = gameLocal.framenum;
|
|
nextCmd->gameTime = gameLocal.time;
|
|
|
|
nextCmd->forwardmove = idMath::ClampChar(127);
|
|
|
|
//this is not meant to resemble proper ai logic, it's just for testing.
|
|
idEntity *closestEnt = NULL;
|
|
float closestDist = idMath::INFINITY;
|
|
|
|
int numSourceAreas, sourceAreas[ idEntity::MAX_PVS_AREAS ];
|
|
numSourceAreas = gameRenderWorld->BoundsInAreas( GetPlayerPhysics()->GetAbsBounds(), sourceAreas, idEntity::MAX_PVS_AREAS );
|
|
pvsHandle_t pvsHandle = gameLocal.pvs.SetupCurrentPVS( sourceAreas, numSourceAreas, PVS_NORMAL );
|
|
|
|
for(int i=0;i<gameLocal.numClients;i++) {
|
|
idEntity *ent = gameLocal.entities[i];
|
|
if(ent) {
|
|
HH_ASSERT(ent->IsType(hhPlayer::Type));
|
|
hhPlayer *plEnt = static_cast<hhPlayer *>(ent);
|
|
|
|
float l = (ent->GetOrigin()-GetOrigin()).Length();
|
|
if(l < 4096.0f && (l < closestDist || !closestEnt) && ent->PhysicsTeamInPVS(pvsHandle)) {
|
|
trace_t tr;
|
|
|
|
gameLocal.clip.TracePoint(tr, GetOrigin(), ent->GetOrigin(), GetPhysics()->GetClipMask(), this);
|
|
if (tr.c.entityNum == ent->entityNumber) { //if we hit the thing then it's visible.
|
|
if (ent->health > 0) {
|
|
if (gameLocal.gameType != GAME_TDM || plEnt->team != team) {
|
|
closestDist = l;
|
|
closestEnt = ent;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
gameLocal.pvs.FreeCurrentPVS( pvsHandle );
|
|
|
|
if (closestEnt) {
|
|
idealAngles = (closestEnt->GetOrigin()-GetOrigin()).ToAngles();
|
|
}
|
|
else { //if no one to run mindlessly at, do some stuff.
|
|
const float testDist = 64.0f;
|
|
idVec3 fwd = GetOrigin()+(idealAngles.ToForward()*testDist);
|
|
|
|
trace_t tr;
|
|
gameLocal.clip.TraceBounds(tr, GetOrigin(), fwd, GetPhysics()->GetBounds(), GetPhysics()->GetClipMask(), this);
|
|
if (tr.fraction != 1.0f) { //if we hit something set the yaw to a new random angle.
|
|
iFeelHoppy = true;
|
|
|
|
idealAngles.yaw = rand()%360;
|
|
}
|
|
}
|
|
|
|
if ((closestEnt || health <= 0) && rand()%2 == 1) {
|
|
nextCmd->buttons |= BUTTON_ATTACK;
|
|
}
|
|
else {
|
|
nextCmd->buttons &= ~BUTTON_ATTACK;
|
|
}
|
|
|
|
if (testCrouchTime < gameLocal.time) {
|
|
testCrouchTime = gameLocal.time + rand()%4000;
|
|
testCrouchActive = !testCrouchActive;
|
|
}
|
|
|
|
if (iFeelHoppy) {
|
|
nextCmd->upmove = idMath::ClampChar(127);
|
|
}
|
|
else {
|
|
if (testCrouchActive) {
|
|
nextCmd->upmove = idMath::ClampChar(-127);
|
|
}
|
|
else {
|
|
nextCmd->upmove = 0;
|
|
}
|
|
}
|
|
|
|
idealAngles.pitch = idMath::AngleNormalize180(idealAngles.pitch);
|
|
idealAngles.yaw = idMath::AngleNormalize180(idealAngles.yaw);
|
|
idealAngles.roll = idMath::AngleNormalize180(idealAngles.roll);
|
|
SetUntransformedViewAngles(idAngles(0.0f, 0.0f, 0.0f));
|
|
UpdateDeltaViewAngles(idealAngles);
|
|
UpdateOrientation(idealAngles);
|
|
PROFILE_STOP("hhArtificialPlayer::Think", PROFMASK_NORMAL);
|
|
hhPlayer::Think();
|
|
}
|
|
|
|
void hhArtificialPlayer::ClientPredictionThink( void ) {
|
|
gameLocal.usercmds[entityNumber] = lastAICmd; //copy over the last manually snapshotted usercmd first
|
|
hhPlayer::ClientPredictionThink();
|
|
}
|
|
|
|
void hhArtificialPlayer::WriteToSnapshot( idBitMsgDelta &msg ) const {
|
|
//instead of sync'ing, just mirror what the server has in ::Spawn
|
|
/*
|
|
msg.WriteDict(*gameLocal.GetUserInfo(entityNumber)); //ap's don't broadcast their info to the server, so the server otherwise
|
|
//will never broadcast it out to other clients
|
|
*/
|
|
|
|
hhPlayer::WriteToSnapshot(msg);
|
|
|
|
usercmd_t &cmd = gameLocal.usercmds[entityNumber];
|
|
msg.WriteLong( cmd.gameTime );
|
|
msg.WriteByte( cmd.buttons );
|
|
msg.WriteShort( cmd.mx );
|
|
msg.WriteShort( cmd.my );
|
|
msg.WriteChar( cmd.forwardmove );
|
|
msg.WriteChar( cmd.rightmove );
|
|
msg.WriteChar( cmd.upmove );
|
|
msg.WriteShort( cmd.angles[0] );
|
|
msg.WriteShort( cmd.angles[1] );
|
|
msg.WriteShort( cmd.angles[2] );
|
|
|
|
msg.WriteBits(spectating, 1);
|
|
}
|
|
|
|
void hhArtificialPlayer::ReadFromSnapshot( const idBitMsgDelta &msg ) {
|
|
/*
|
|
((idBitMsgDelta)msg).ReadDict(*((idDict *)gameLocal.GetUserInfo(entityNumber)));
|
|
if (!clientReceivedUI) {
|
|
UserInfoChanged( false );
|
|
clientReceivedUI = true;
|
|
}
|
|
*/
|
|
|
|
hhPlayer::ReadFromSnapshot(msg);
|
|
|
|
usercmd_t &cmd = lastAICmd;
|
|
cmd.gameTime = msg.ReadLong();
|
|
cmd.buttons = msg.ReadByte();
|
|
cmd.mx = msg.ReadShort();
|
|
cmd.my = msg.ReadShort();
|
|
cmd.forwardmove = msg.ReadChar();
|
|
cmd.rightmove = msg.ReadChar();
|
|
cmd.upmove = msg.ReadChar();
|
|
cmd.angles[0] = msg.ReadShort();
|
|
cmd.angles[1] = msg.ReadShort();
|
|
cmd.angles[2] = msg.ReadShort();
|
|
|
|
bool spec = !!msg.ReadBits(1); //doesn't go through right for ap's or something based on events sometimes, hack fix
|
|
if (spec != spectating) {
|
|
Spectate(spec);
|
|
}
|
|
}
|
|
//HUMANHEAD END
|
|
|
|
void hhPlayer::DisableSpiritWalk(int timeout) {
|
|
nextSpiritTime = gameLocal.time + SEC2MS(timeout);
|
|
StopSpiritWalk();
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// hhPlayer::Event_UpdateDDA
|
|
//
|
|
// CJR: Update the probability the player will die this tick, and
|
|
// adjust the difficulty accordingly
|
|
//=============================================================================
|
|
|
|
void hhPlayer::Event_UpdateDDA() {
|
|
idEntity *ent;
|
|
int updateFlags;
|
|
|
|
// Find the number of alive, non-dormant creatures attacking the player
|
|
ddaNumEnemies = 0;
|
|
ddaProbabilityAccum = 1.0f;
|
|
updateFlags = 0;
|
|
|
|
for( ent = gameLocal.activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) {
|
|
if ( ent->fl.isDormant || !ent->IsType( hhMonsterAI::Type ) || ent->fl.hidden || ent->health <= 0 ) {
|
|
continue;
|
|
}
|
|
|
|
hhMonsterAI *monster = static_cast<hhMonsterAI *>(ent);
|
|
if ( monster->GetEnemy() != this ) {
|
|
continue;
|
|
}
|
|
|
|
// PVS Check: only include creatures that the player can attack or be attacked by
|
|
if ( !gameLocal.InPlayerPVS( ent ) ) {
|
|
continue;
|
|
}
|
|
|
|
// Accumulate the survival rate against each enemy
|
|
int index = monster->spawnArgs.GetInt( "ddaIndex", "0" );
|
|
|
|
if ( index == -1 ) { // Skip certain monsters, such as the vacuum or the crawlers
|
|
continue;
|
|
}
|
|
|
|
float prob = gameLocal.GetDDA()->DDA_GetProbability( index, GetHealth() );
|
|
|
|
ddaProbabilityAccum *= prob;
|
|
|
|
// Recompute the actual difficulty based upon the creatures in view
|
|
updateFlags |= ( 1 << index );
|
|
|
|
ddaNumEnemies++;
|
|
}
|
|
|
|
gameLocal.GetDDA()->RecalculateDifficulty( updateFlags );
|
|
|
|
PostEventSec( &EV_UpdateDDA, spawnArgs.GetFloat( "updateDDARate", "0.25" ) );
|
|
|
|
if ( g_printDDA.GetBool() ) {
|
|
if ( ddaNumEnemies > 0 ){
|
|
common->Printf("Probability [%.2f]\n", ddaProbabilityAccum );
|
|
}
|
|
}
|
|
}
|
|
|
|
void hhPlayer::Event_DisableSpirit(void) {
|
|
// HUMANHEAD PCF pdm 05-17-06: Removed assert, as scripters were potentially calling during deathwalk
|
|
// HUMANHEAD PCF pdm 05-17-06: Only exit spirit realm if spiritwalking, not deathwalking
|
|
|
|
if ( IsSpiritWalking() ) {
|
|
StopSpiritWalk();
|
|
}
|
|
|
|
bAllowSpirit = false;
|
|
}
|
|
|
|
void hhPlayer::Event_EnableSpirit(void) {
|
|
// HUMANHEAD PCF pdm 05-17-06: Removed assert
|
|
|
|
bAllowSpirit = true;
|
|
}
|
|
|
|
//HUMANHEAD bjk
|
|
void hhPlayer::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) {
|
|
// no need for these in sp
|
|
if( gameLocal.isMultiplayer ) {
|
|
idActor::ProjectOverlay( origin, dir, size, material );
|
|
}
|
|
}
|
|
|
|
// HUMANHEAD mdl: Ripped from idAI for short invulnerability after returning from deathwalk
|
|
void hhPlayer::Event_AllowDamage( void ) {
|
|
fl.takedamage = true;
|
|
}
|
|
|
|
void hhPlayer::Event_IgnoreDamage( void ) {
|
|
fl.takedamage = false;
|
|
}
|
|
|
|
// Precompute all the weapons' info for faster HUD updates
|
|
void hhPlayer::SetupWeaponInfo() {
|
|
const char *fireInfoName = NULL;
|
|
const idDeclEntityDef *decl = NULL;
|
|
const char *ammoName = NULL;
|
|
float maxAmmo;
|
|
|
|
memset(weaponInfo, 0, sizeof(weaponInfo_t)*15);
|
|
memset(altWeaponInfo, 0, sizeof(weaponInfo_t)*15);
|
|
|
|
for (int ix=0; ix<15; ix++) {
|
|
const char *weaponClassName = spawnArgs.GetString( va( "def_weapon%d", ix ), NULL );
|
|
if ( weaponClassName && *weaponClassName ) {
|
|
|
|
const idDeclEntityDef *weaponObjDecl = gameLocal.FindEntityDef( weaponClassName, false );
|
|
if ( weaponObjDecl ) {
|
|
fireInfoName = weaponObjDecl->dict.GetString("def_fireInfo");
|
|
decl = gameLocal.FindEntityDef( fireInfoName, false );
|
|
if ( decl ) {
|
|
weaponInfo[ix].ammoType = inventory.AmmoIndexForAmmoClass( decl->dict.GetString( "ammoType" ) );
|
|
weaponInfo[ix].ammoRequired = decl->dict.GetInt("ammoRequired");
|
|
weaponInfo[ix].ammoLow = decl->dict.GetInt("lowAmmo");
|
|
|
|
ammoName = idWeapon::GetAmmoNameForNum( weaponInfo[ix].ammoType );
|
|
maxAmmo = inventory.MaxAmmoForAmmoClass( this, ammoName );
|
|
weaponInfo[ix].ammoMax = max(1, maxAmmo);
|
|
}
|
|
|
|
fireInfoName = weaponObjDecl->dict.GetString("def_altFireInfo");
|
|
decl = gameLocal.FindEntityDef( fireInfoName, false );
|
|
if ( decl ) {
|
|
altWeaponInfo[ix].ammoType = inventory.AmmoIndexForAmmoClass( decl->dict.GetString( "ammoType" ) );
|
|
altWeaponInfo[ix].ammoRequired = decl->dict.GetInt("ammoRequired");
|
|
altWeaponInfo[ix].ammoLow = decl->dict.GetInt("lowAmmo");
|
|
|
|
ammoName = idWeapon::GetAmmoNameForNum( altWeaponInfo[ix].ammoType );
|
|
maxAmmo = inventory.MaxAmmoForAmmoClass( this, ammoName );
|
|
altWeaponInfo[ix].ammoMax = max(1, maxAmmo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
hhPlayer::ThrowGrenade
|
|
HUMANHEAD bjk
|
|
===============
|
|
*/
|
|
void hhPlayer::ThrowGrenade( void ) {
|
|
idMat3 axis;
|
|
idVec3 muzzle;
|
|
|
|
if( inventory.HasAmmo(inventory.AmmoIndexForAmmoClass( "ammo_crawler" ), 1) == false )
|
|
return;
|
|
|
|
if (IsSpiritOrDeathwalking() || InVehicle() || bReallyDead) {
|
|
return;
|
|
}
|
|
|
|
if ( privateCameraView || !weaponEnabled || spectating || gameLocal.inCinematic || health < 0 || gameLocal.isClient )
|
|
return;
|
|
|
|
if( weapon.IsValid() ) {
|
|
if ( inventory.weapons & ( 1 << 12 ) && currentWeapon != 12 && currentWeapon != 3 && idealWeapon == currentWeapon ) {
|
|
idealWeapon = 12;
|
|
previousWeapon = currentWeapon;
|
|
}
|
|
else if( currentWeapon == 3 ) {
|
|
FireWeapon();
|
|
usercmd.buttons = usercmd.buttons | BUTTON_ATTACK;
|
|
}
|
|
}
|
|
}
|
|
|
|
//HUMANHEAD bjk
|
|
void hhPlayer::Event_ReturnToWeapon() {
|
|
const char *weap;
|
|
|
|
if ( !weaponEnabled || spectating || gameLocal.inCinematic || health < 0 ) {
|
|
idThread::ReturnInt(0);
|
|
return;
|
|
}
|
|
|
|
if ( ( previousWeapon < 0 ) || ( previousWeapon >= MAX_WEAPONS ) ) {
|
|
idThread::ReturnInt(0);
|
|
return;
|
|
}
|
|
|
|
if ( gameLocal.isClient ) {
|
|
idThread::ReturnInt(0);
|
|
return;
|
|
}
|
|
|
|
weap = spawnArgs.GetString( va( "def_weapon%d", previousWeapon ) );
|
|
if ( !weap[ 0 ] ) {
|
|
gameLocal.Printf( "Invalid weapon\n" );
|
|
idThread::ReturnInt(0);
|
|
return;
|
|
}
|
|
|
|
if ( inventory.weapons & ( 1 << previousWeapon ) ) {
|
|
idealWeapon = previousWeapon;
|
|
}
|
|
|
|
idThread::ReturnInt(1);
|
|
}
|
|
|
|
//HUMANHEAD rww
|
|
void hhPlayer::Event_CanAnimateTorso(void) {
|
|
if (!gameLocal.isMultiplayer) { //don't worry about it
|
|
idThread::ReturnInt(1);
|
|
return;
|
|
}
|
|
|
|
if (!weapon.IsValid() || !weapon->IsType(hhWeapon::Type)) { //bad
|
|
idThread::ReturnInt(0);
|
|
return;
|
|
}
|
|
|
|
idThread::ReturnInt(1);
|
|
}
|
|
|
|
void hhPlayer::Show(void) {
|
|
idActor::Show();
|
|
|
|
hhWeapon *weap;
|
|
weap = weapon.GetEntity();
|
|
if ( weap ) {
|
|
if (!IsLocked(currentWeapon)) {
|
|
weap->ShowWorldModel();
|
|
} else {
|
|
weap->HideWorldModel();
|
|
}
|
|
}
|
|
}
|