diff options
Diffstat (limited to 'game/server/hl1/hl1_player.cpp')
| -rw-r--r-- | game/server/hl1/hl1_player.cpp | 2110 |
1 files changed, 2110 insertions, 0 deletions
diff --git a/game/server/hl1/hl1_player.cpp b/game/server/hl1/hl1_player.cpp new file mode 100644 index 0000000..b47cafe --- /dev/null +++ b/game/server/hl1/hl1_player.cpp @@ -0,0 +1,2110 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Player for HL1. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "hl1_player.h" +#include "gamerules.h" +#include "trains.h" +#include "hl1_basecombatweapon_shared.h" +#include "vcollide_parse.h" +#include "in_buttons.h" +#include "igamemovement.h" +#include "ai_hull.h" +#include "hl2_shareddefs.h" +#include "info_camera_link.h" +#include "point_camera.h" +#include "ndebugoverlay.h" +#include "globals.h" +#include "ai_interactions.h" +#include "engine/IEngineSound.h" +#include "vphysics/player_controller.h" +#include "vphysics/constraints.h" +#include "predicted_viewmodel.h" +#include "physics_saverestore.h" +#include "gamestats.h" + +#define DMG_FREEZE DMG_VEHICLE +#define DMG_SLOWFREEZE DMG_DISSOLVE + +// HL1_DMG_SHOWNHUD: Add DMG_VEHICLE because HL2 hijacked those bits from DMG_FREEZE, which is what they are in HL1 +// HL1_DMG_SHOWNHUD: Add DMG_DISSOLVE because HL2 hijacked those bits from DMG_SLOWFREEZE, which is what they are in HL1 +// See Halflife1 GameRules - Damage_GetShowOnHud() +//#define HL1_DMG_SHOWNHUD (DMG_POISON | DMG_ACID | DMG_FREEZE | DMG_SLOWFREEZE | DMG_DROWN | DMG_BURN | DMG_SLOWBURN | DMG_NERVEGAS | DMG_RADIATION | DMG_SHOCK) + +// TIME BASED DAMAGE AMOUNT +// tweak these values based on gameplay feedback: +#define PARALYZE_DURATION 2 // number of 2 second intervals to take damage +#define PARALYZE_DAMAGE 1.0 // damage to take each 2 second interval + +#define NERVEGAS_DURATION 2 +#define NERVEGAS_DAMAGE 5.0 + +#define POISON_DURATION 5 +#define POISON_DAMAGE 2.0 + +#define RADIATION_DURATION 2 +#define RADIATION_DAMAGE 1.0 + +#define ACID_DURATION 2 +#define ACID_DAMAGE 5.0 + +#define SLOWBURN_DURATION 2 +#define SLOWBURN_DAMAGE 1.0 + +#define SLOWFREEZE_DURATION 2 +#define SLOWFREEZE_DAMAGE 1.0 + +#define FLASH_DRAIN_TIME 1.2 //100 units/3 minutes +#define FLASH_CHARGE_TIME 0.2 // 100 units/20 seconds (seconds per unit) + +ConVar player_showpredictedposition( "player_showpredictedposition", "0" ); +ConVar player_showpredictedposition_timestep( "player_showpredictedposition_timestep", "1.0" ); + +ConVar sv_hl1_allowpickup( "sv_hl1_allowpickup", "0" ); + +LINK_ENTITY_TO_CLASS( player, CHL1_Player ); +PRECACHE_REGISTER(player); + +BEGIN_DATADESC( CHL1_Player ) + DEFINE_FIELD( m_nControlClass, FIELD_INTEGER ), + DEFINE_AUTO_ARRAY( m_vecMissPositions, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_nNumMissPositions, FIELD_INTEGER ), + + DEFINE_FIELD( m_flIdleTime, FIELD_TIME ), + DEFINE_FIELD( m_flMoveTime, FIELD_TIME ), + DEFINE_FIELD( m_flLastDamageTime, FIELD_TIME ), + DEFINE_FIELD( m_flTargetFindTime, FIELD_TIME ), + DEFINE_FIELD( m_bHasLongJump, FIELD_BOOLEAN ), + DEFINE_FIELD( m_nFlashBattery, FIELD_INTEGER ), + DEFINE_FIELD( m_flFlashLightTime, FIELD_TIME ), + + DEFINE_FIELD( m_flStartCharge, FIELD_FLOAT ), + DEFINE_FIELD( m_flAmmoStartCharge, FIELD_FLOAT ), + DEFINE_FIELD( m_flPlayAftershock, FIELD_FLOAT ), + DEFINE_FIELD( m_flNextAmmoBurn, FIELD_FLOAT ), + + DEFINE_PHYSPTR( m_pPullConstraint ), + DEFINE_FIELD( m_hPullObject, FIELD_EHANDLE ), + DEFINE_FIELD( m_bIsPullingObject, FIELD_BOOLEAN ), + +END_DATADESC() + + +IMPLEMENT_SERVERCLASS_ST( CHL1_Player, DT_HL1Player ) + SendPropInt( SENDINFO( m_bHasLongJump ), 1, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_nFlashBattery ), 8, SPROP_UNSIGNED ), + SendPropBool( SENDINFO( m_bIsPullingObject ) ), + + SendPropFloat( SENDINFO( m_flStartCharge ) ), + SendPropFloat( SENDINFO( m_flAmmoStartCharge ) ), + SendPropFloat( SENDINFO( m_flPlayAftershock ) ), + SendPropFloat( SENDINFO( m_flNextAmmoBurn ) ) + +END_SEND_TABLE() + +CHL1_Player::CHL1_Player() +{ + m_nNumMissPositions = 0; +} + +void CHL1_Player::Precache( void ) +{ + BaseClass::Precache(); + + PrecacheScriptSound( "Player.FlashlightOn" ); + PrecacheScriptSound( "Player.FlashlightOff" ); + PrecacheScriptSound( "Player.UseTrain" ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Allow pre-frame adjustments on the player +//----------------------------------------------------------------------------- +void CHL1_Player::PreThink(void) +{ + if ( player_showpredictedposition.GetBool() ) + { + Vector predPos; + + UTIL_PredictedPosition( this, player_showpredictedposition_timestep.GetFloat(), &predPos ); + + NDebugOverlay::Box( predPos, NAI_Hull::Mins( GetHullType() ), NAI_Hull::Maxs( GetHullType() ), 0, 255, 0, 0, 0.01f ); + NDebugOverlay::Line( GetAbsOrigin(), predPos, 0, 255, 0, 0, 0.01f ); + } + + int buttonsChanged; + buttonsChanged = m_afButtonPressed | m_afButtonReleased; + + UpdatePullingObject(); + + g_pGameRules->PlayerThink( this ); + + if ( g_fGameOver || IsPlayerLockedInPlace() ) + return; // intermission or finale + + ItemPreFrame( ); + WaterMove(); + + if ( g_pGameRules && g_pGameRules->FAllowFlashlight() ) + m_Local.m_iHideHUD &= ~HIDEHUD_FLASHLIGHT; + else + m_Local.m_iHideHUD |= HIDEHUD_FLASHLIGHT; + + + // checks if new client data (for HUD and view control) needs to be sent to the client + UpdateClientData(); + + CheckTimeBasedDamage(); + + CheckSuitUpdate(); + + if (m_lifeState >= LIFE_DYING) + { + PlayerDeathThink(); + return; + } + + // So the correct flags get sent to client asap. + // + if ( m_afPhysicsFlags & PFLAG_DIROVERRIDE ) + AddFlag( FL_ONTRAIN ); + else + RemoveFlag( FL_ONTRAIN ); + + // Train speed control + if ( m_afPhysicsFlags & PFLAG_DIROVERRIDE ) + { + CBaseEntity *pTrain = GetGroundEntity(); + float vel; + + if ( pTrain ) + { + if ( !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) ) + pTrain = NULL; + } + + if ( !pTrain ) + { + if ( GetActiveWeapon() && (GetActiveWeapon()->ObjectCaps() & FCAP_DIRECTIONAL_USE) ) + { + m_iTrain = TRAIN_ACTIVE | TRAIN_NEW; + + if ( m_nButtons & IN_FORWARD ) + { + m_iTrain |= TRAIN_FAST; + } + else if ( m_nButtons & IN_BACK ) + { + m_iTrain |= TRAIN_BACK; + } + else + { + m_iTrain |= TRAIN_NEUTRAL; + } + return; + } + else + { + trace_t trainTrace; + // Maybe this is on the other side of a level transition + UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector(0,0,-38), + MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &trainTrace ); + + if ( trainTrace.fraction != 1.0 && trainTrace.m_pEnt ) + pTrain = trainTrace.m_pEnt; + + + if ( !pTrain || !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) || !pTrain->OnControls(this) ) + { +// Warning( "In train mode with no train!\n" ); + m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + } + } + else if ( !( GetFlags() & FL_ONGROUND ) || pTrain->HasSpawnFlags( SF_TRACKTRAIN_NOCONTROL ) || (m_nButtons & (IN_MOVELEFT|IN_MOVERIGHT) ) ) + { + // Turn off the train if you jump, strafe, or the train controls go dead + m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + + SetAbsVelocity( vec3_origin ); + vel = 0; + if ( m_afButtonPressed & IN_FORWARD ) + { + vel = 1; + pTrain->Use( this, this, USE_SET, (float)vel ); + } + else if ( m_afButtonPressed & IN_BACK ) + { + vel = -1; + pTrain->Use( this, this, USE_SET, (float)vel ); + } + else if ( m_afButtonPressed & IN_JUMP ) + { + m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + } + + if (vel) + { + m_iTrain = TrainSpeed(pTrain->m_flSpeed, ((CFuncTrackTrain*)pTrain)->GetMaxSpeed()); + m_iTrain |= TRAIN_ACTIVE|TRAIN_NEW; + } + } + else if (m_iTrain & TRAIN_ACTIVE) + { + m_iTrain = TRAIN_NEW; // turn off train + } + + // THIS CODE DOESN'T SEEM TO DO ANYTHING!!! + // WHY IS IT STILL HERE? (sjb) + if (m_nButtons & IN_JUMP) + { + // If on a ladder, jump off the ladder + // else Jump + if( IsPullingObject() ) + { + StopPullingObject(); + } + + Jump(); + } + + // If trying to duck, already ducked, or in the process of ducking + if ((m_nButtons & IN_DUCK) || (GetFlags() & FL_DUCKING) || (m_afPhysicsFlags & PFLAG_DUCKING) ) + Duck(); + + // + // If we're not on the ground, we're falling. Update our falling velocity. + // + if ( !( GetFlags() & FL_ONGROUND ) ) + { + m_Local.m_flFallVelocity = -GetAbsVelocity().z; + } + + if ( m_afPhysicsFlags & PFLAG_ONBARNACLE ) + { + SetAbsVelocity( vec3_origin ); + } + // StudioFrameAdvance( );//!!!HACKHACK!!! Can't be hit by traceline when not animating? + + //Find targets for NPC to shoot if they decide to miss us + FindMissTargets(); +} + + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +Class_T CHL1_Player::Classify ( void ) +{ + // If player controlling another entity? If so, return this class + if (m_nControlClass != CLASS_NONE) + { + return m_nControlClass; + } + else + { + return CLASS_PLAYER; + } +} + +//----------------------------------------------------------------------------- +// Purpose: This is a generic function (to be implemented by sub-classes) to +// handle specific interactions between different types of characters +// (For example the barnacle grabbing an NPC) +// Input : Constant for the type of interaction +// Output : true - if sub-class has a response for the interaction +// false - if sub-class has no response +//----------------------------------------------------------------------------- +bool CHL1_Player::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt) +{ + if ( interactionType == g_interactionBarnacleVictimDangle ) + { + TakeDamage ( CTakeDamageInfo( sourceEnt, sourceEnt, m_iHealth + ArmorValue(), DMG_SLASH | DMG_ALWAYSGIB ) ); + return true; + } + + if (interactionType == g_interactionBarnacleVictimReleased) + { + m_afPhysicsFlags &= ~PFLAG_ONBARNACLE; + SetMoveType( MOVETYPE_WALK ); + return true; + } + else if (interactionType == g_interactionBarnacleVictimGrab) + { + m_afPhysicsFlags |= PFLAG_ONBARNACLE; + ClearUseEntity(); + return true; + } + return false; +} + + +void CHL1_Player::PlayerRunCommand(CUserCmd *ucmd, IMoveHelper *moveHelper) +{ + // Handle FL_FROZEN. + if ( m_afPhysicsFlags & PFLAG_ONBARNACLE ) + { + ucmd->forwardmove = 0; + ucmd->sidemove = 0; + ucmd->upmove = 0; + } + + //Update our movement information + if ( ( ucmd->forwardmove != 0 ) || ( ucmd->sidemove != 0 ) || ( ucmd->upmove != 0 ) ) + { + m_flIdleTime -= TICK_INTERVAL * 2.0f; + + if ( m_flIdleTime < 0.0f ) + { + m_flIdleTime = 0.0f; + } + + m_flMoveTime += TICK_INTERVAL; + + if ( m_flMoveTime > 4.0f ) + { + m_flMoveTime = 4.0f; + } + } + else + { + m_flIdleTime += TICK_INTERVAL; + + if ( m_flIdleTime > 4.0f ) + { + m_flIdleTime = 4.0f; + } + + m_flMoveTime -= TICK_INTERVAL * 2.0f; + + if ( m_flMoveTime < 0.0f ) + { + m_flMoveTime = 0.0f; + } + } + + //Msg("Player time: [ACTIVE: %f]\t[IDLE: %f]\n", m_flMoveTime, m_flIdleTime ); + + BaseClass::PlayerRunCommand( ucmd, moveHelper ); +} + +//----------------------------------------------------------------------------- +// Purpose: Create and give the named item to the player. Then return it. +//----------------------------------------------------------------------------- +CBaseEntity *CHL1_Player::GiveNamedItem( const char *pszName, int iSubType ) +{ + // If I already own this type don't create one + if ( Weapon_OwnsThisType(pszName, iSubType) ) + return NULL; + + // Msg( "giving %s\n", pszName ); + + EHANDLE pent; + + pent = CreateEntityByName(pszName); + if ( pent == NULL ) + { + Msg( "NULL Ent in GiveNamedItem!\n" ); + return NULL; + } + + pent->SetLocalOrigin( GetLocalOrigin() ); + pent->AddSpawnFlags( SF_NORESPAWN ); + + if ( iSubType ) + { + CBaseCombatWeapon *pWeapon = dynamic_cast<CBaseCombatWeapon*>( (CBaseEntity*)pent ); + if ( pWeapon ) + { + pWeapon->SetSubType( iSubType ); + } + } + + DispatchSpawn( pent ); + + if ( pent != NULL && !(pent->IsMarkedForDeletion()) ) + { + pent->Touch( this ); + } + + return pent; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL1_Player::StartPullingObject( CBaseEntity *pObject ) +{ + if ( pObject->VPhysicsGetObject() == NULL || VPhysicsGetObject() == NULL ) + { + return; + } + + if( !(GetFlags()&FL_ONGROUND) ) + { + //Msg("Can't grab in air!\n"); + return; + } + + if( GetGroundEntity() == pObject ) + { + //Msg("Can't grab something you're standing on!\n"); + return; + } + + constraint_ballsocketparams_t ballsocket; + ballsocket.Defaults(); + ballsocket.constraint.Defaults(); + ballsocket.constraint.forceLimit = lbs2kg(1000); + ballsocket.constraint.torqueLimit = lbs2kg(1000); + ballsocket.InitWithCurrentObjectState( VPhysicsGetObject(), pObject->VPhysicsGetObject(), WorldSpaceCenter() ); + m_pPullConstraint = physenv->CreateBallsocketConstraint( VPhysicsGetObject(), pObject->VPhysicsGetObject(), NULL, ballsocket ); + + m_hPullObject.Set(pObject); + m_bIsPullingObject = true; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL1_Player::StopPullingObject() +{ + if( m_pPullConstraint ) + { + physenv->DestroyConstraint( m_pPullConstraint ); + } + + m_hPullObject.Set(NULL); + m_pPullConstraint = NULL; + m_bIsPullingObject = false; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL1_Player::UpdatePullingObject() +{ + if( !IsPullingObject() ) + return; + + CBaseEntity *pObject= m_hPullObject.Get(); + + if( !pObject || !pObject->VPhysicsGetObject() ) + { + // Object broke or otherwise vanished. + StopPullingObject(); + return; + } + + if( m_afButtonReleased & IN_USE ) + { + // Player released +USE + StopPullingObject(); + return; + } + + + float flMaxDistSqr = Square(PLAYER_USE_RADIUS + 1.0f); + + Vector objectPos; + QAngle angle; + + pObject->VPhysicsGetObject()->GetPosition( &objectPos, &angle ); + + if( !FInViewCone(objectPos) ) + { + // Player turned away. + StopPullingObject(); + } + else if( objectPos.DistToSqr(WorldSpaceCenter()) > flMaxDistSqr ) + { + // Object got caught up on something and left behind + StopPullingObject(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Sets HL1specific defaults. +//----------------------------------------------------------------------------- +void CHL1_Player::Spawn(void) +{ + // In multiplayer ,this is handled in the super class + if ( !g_pGameRules->IsMultiplayer () ) + SetModel( "models/player.mdl" ); + + BaseClass::Spawn(); + + // + // Our player movement speed is set once here. This will override the cl_xxxx + // cvars unless they are set to be lower than this. + // + SetMaxSpeed( 1000 ); + + SetDefaultFOV( 0 ); + + m_nFlashBattery = 99; + m_flFlashLightTime = 1; + + m_flFieldOfView = 0.5; + + StopPullingObject(); + + m_Local.m_iHideHUD = 0; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL1_Player::Event_Killed( const CTakeDamageInfo &info ) +{ + StopPullingObject(); + BaseClass::Event_Killed(info); +} + +void CHL1_Player::CheckTimeBasedDamage( void ) +{ + int i; + byte bDuration = 0; + + static float gtbdPrev = 0.0; + + // If we don't have any time based damage return. + if ( !g_pGameRules->Damage_IsTimeBased( m_bitsDamageType ) ) + return; + + // only check for time based damage approx. every 2 seconds + if (abs(gpGlobals->curtime - m_tbdPrev) < 2.0) + return; + + m_tbdPrev = gpGlobals->curtime; + + for (i = 0; i < CDMG_TIMEBASED; i++) + { + // Make sure the damage type is really time-based. + // This is kind of hacky but necessary until we setup DamageType as an enum. + int iDamage = ( DMG_PARALYZE << i ); + if ( !g_pGameRules->Damage_IsTimeBased( iDamage ) ) + continue; + + // make sure bit is set for damage type + if ( m_bitsDamageType & iDamage ) + { + switch (i) + { + case itbd_Paralyze: + // UNDONE - flag movement as half-speed + bDuration = PARALYZE_DURATION; + break; + case itbd_NerveGas: +// OnTakeDamage(pev, pev, NERVEGAS_DAMAGE, DMG_GENERIC); + bDuration = NERVEGAS_DURATION; + break; + case itbd_PoisonRecover: + OnTakeDamage( CTakeDamageInfo( this, this, POISON_DAMAGE, DMG_GENERIC ) ); + bDuration = POISON_DURATION; + break; + case itbd_Radiation: +// OnTakeDamage(pev, pev, RADIATION_DAMAGE, DMG_GENERIC); + bDuration = RADIATION_DURATION; + break; + case itbd_DrownRecover: + // NOTE: this hack is actually used to RESTORE health + // after the player has been drowning and finally takes a breath + if (m_idrowndmg > m_idrownrestored) + { + int idif = MIN(m_idrowndmg - m_idrownrestored, 10); + + TakeHealth(idif, DMG_GENERIC); + m_idrownrestored += idif; + } + bDuration = 4; // get up to 5*10 = 50 points back + break; + + case itbd_Acid: +// OnTakeDamage(pev, pev, ACID_DAMAGE, DMG_GENERIC); + bDuration = ACID_DURATION; + break; + case itbd_SlowBurn: +// OnTakeDamage(pev, pev, SLOWBURN_DAMAGE, DMG_GENERIC); + bDuration = SLOWBURN_DURATION; + break; + case itbd_SlowFreeze: +// OnTakeDamage(pev, pev, SLOWFREEZE_DAMAGE, DMG_GENERIC); + bDuration = SLOWFREEZE_DURATION; + break; + default: + bDuration = 0; + } + + if (m_rgbTimeBasedDamage[i]) + { + // decrement damage duration, detect when done. + if (!m_rgbTimeBasedDamage[i] || --m_rgbTimeBasedDamage[i] == 0) + { + m_rgbTimeBasedDamage[i] = 0; + // if we're done, clear damage bits + m_bitsDamageType &= ~(DMG_PARALYZE << i); + } + } + else + // first time taking this damage type - init damage duration + m_rgbTimeBasedDamage[i] = bDuration; + } + } +} + + +class CPhysicsPlayerCallback : public IPhysicsPlayerControllerEvent +{ +public: + int ShouldMoveTo( IPhysicsObject *pObject, const Vector &position ) + { + CHL1_Player *pPlayer = (CHL1_Player *)pObject->GetGameData(); + if ( pPlayer ) + { + if ( pPlayer->TouchedPhysics() ) + { + return 0; + } + } + return 1; + } +}; + +static CPhysicsPlayerCallback playerCallback; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL1_Player::InitVCollision( const Vector &vecAbsOrigin, const Vector &vecAbsVelocity ) +{ + BaseClass::InitVCollision( vecAbsOrigin, vecAbsVelocity ); + + // Setup the HL2 specific callback. + GetPhysicsController()->SetEventHandler( &playerCallback ); +} + + +CHL1_Player::~CHL1_Player( void ) +{ +} + +extern int gEvilImpulse101; +void CHL1_Player::CheatImpulseCommands( int iImpulse ) +{ + if ( !sv_cheats->GetBool() ) + { + return; + } + + switch( iImpulse ) + { + case 101: + gEvilImpulse101 = true; + + GiveNamedItem( "item_suit" ); + GiveNamedItem( "item_battery" ); + GiveNamedItem( "weapon_crowbar" ); + GiveNamedItem( "weapon_glock" ); + GiveNamedItem( "ammo_9mmclip" ); + GiveNamedItem( "weapon_shotgun" ); + GiveNamedItem( "ammo_buckshot" ); + GiveNamedItem( "weapon_mp5" ); + GiveNamedItem( "ammo_9mmar" ); + GiveNamedItem( "ammo_argrenades" ); + GiveNamedItem( "weapon_handgrenade" ); + GiveNamedItem( "weapon_tripmine" ); + GiveNamedItem( "weapon_357" ); + GiveNamedItem( "ammo_357" ); + GiveNamedItem( "weapon_crossbow" ); + GiveNamedItem( "ammo_crossbow" ); + GiveNamedItem( "weapon_egon" ); + GiveNamedItem( "weapon_gauss" ); + GiveNamedItem( "ammo_gaussclip" ); + GiveNamedItem( "weapon_rpg" ); + GiveNamedItem( "ammo_rpgclip" ); + GiveNamedItem( "weapon_satchel" ); + GiveNamedItem( "weapon_snark" ); + GiveNamedItem( "weapon_hornetgun" ); + + gEvilImpulse101 = false; + break; + + case 0: + default: + BaseClass::CheatImpulseCommands( iImpulse ); + } +} + +void CHL1_Player::SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize ) +{ + int area = pViewEntity ? pViewEntity->NetworkProp()->AreaNum() : NetworkProp()->AreaNum(); + BaseClass::SetupVisibility( pViewEntity, pvs, pvssize ); + PointCameraSetupVisibility( this, area, pvs, pvssize ); +} + + +#define ARMOR_RATIO 0.2 // Armor Takes 80% of the damage +#define ARMOR_BONUS 0.5 // Each Point of Armor is work 1/x points of health + + +int CHL1_Player::OnTakeDamage( const CTakeDamageInfo &inputInfo ) +{ + // have suit diagnose the problem - ie: report damage type + int bitsDamage = inputInfo.GetDamageType(); + int ffound = true; + int fmajor; + int fcritical; + int fTookDamage; + int ftrivial; + float flRatio; + float flBonus; + float flHealthPrev = m_iHealth; + + CTakeDamageInfo info = inputInfo; + + if ( info.GetDamage() > 0.0f ) + { + m_flLastDamageTime = gpGlobals->curtime; + } + + flBonus = ARMOR_BONUS; + flRatio = ARMOR_RATIO; + + if ( ( info.GetDamageType() & DMG_BLAST ) && g_pGameRules->IsMultiplayer() ) + { + // blasts damage armor more. + flBonus *= 2; + } + + // Already dead + if ( !IsAlive() ) + return 0; + // go take the damage first + + + if ( !g_pGameRules->FPlayerCanTakeDamage( this, info.GetAttacker(), info ) ) + { + // Refuse the damage + return 0; + } + + // keep track of amount of damage last sustained + m_lastDamageAmount = info.GetDamage(); + + // Armor. + if ( ArmorValue() && + !(info.GetDamageType() & (DMG_FALL | DMG_DROWN | DMG_POISON)) && // armor doesn't protect against fall or drown damage! + !(GetFlags() & FL_GODMODE) ) + { + float flNew = info.GetDamage() * flRatio; + + float flArmor; + + flArmor = (info.GetDamage() - flNew) * flBonus; + + // Does this use more armor than we have? + if ( flArmor > ArmorValue() ) + { + flArmor = ArmorValue(); + flArmor *= (1/flBonus); + flNew = info.GetDamage() - flArmor; + SetArmorValue( 0 ); + } + else + SetArmorValue( ArmorValue() - flArmor ); + + info.SetDamage( flNew ); + } + + // this cast to INT is critical!!! If a player ends up with 0.5 health, the engine will get that + // as an int (zero) and think the player is dead! (this will incite a clientside screentilt, etc) + info.SetDamage( (int)info.GetDamage() ); + fTookDamage = CBaseCombatCharacter::OnTakeDamage( info ); // Bypass CBasePlayer's + + if ( fTookDamage ) + { + // add to the damage total for clients, which will be sent as a single + // message at the end of the frame + // todo: remove after combining shotgun blasts? + if ( info.GetInflictor() && info.GetInflictor()->edict() ) + m_DmgOrigin = info.GetInflictor()->GetAbsOrigin(); + + m_DmgTake += (int)info.GetDamage(); + } + + // Reset damage time countdown for each type of time based damage player just sustained + for (int i = 0; i < CDMG_TIMEBASED; i++) + { + // Make sure the damage type is really time-based. + // This is kind of hacky but necessary until we setup DamageType as an enum. + int iDamage = ( DMG_PARALYZE << i ); + if ( ( info.GetDamageType() & iDamage ) && g_pGameRules->Damage_IsTimeBased( iDamage ) ) + { + m_rgbTimeBasedDamage[i] = 0; + } + } + + // Display any effect associate with this damage type + DamageEffect(info.GetDamage(),bitsDamage); + + // how bad is it, doc? + ftrivial = (m_iHealth > 75 || m_lastDamageAmount < 5); + fmajor = (m_lastDamageAmount > 25); + fcritical = (m_iHealth < 30); + + // handle all bits set in this damage message, + // let the suit give player the diagnosis + + // UNDONE: add sounds for types of damage sustained (ie: burn, shock, slash ) + + // UNDONE: still need to record damage and heal messages for the following types + + // DMG_BURN + // DMG_FREEZE + // DMG_BLAST + // DMG_SHOCK + + m_bitsDamageType |= bitsDamage; // Save this so we can report it to the client + m_bitsHUDDamage = -1; // make sure the damage bits get resent + + bool bTimeBasedDamage = g_pGameRules->Damage_IsTimeBased( bitsDamage ); + while (fTookDamage && (!ftrivial || bTimeBasedDamage) && ffound && bitsDamage) + { + ffound = false; + + if (bitsDamage & DMG_CLUB) + { + if (fmajor) + SetSuitUpdate("!HEV_DMG4", false, SUIT_NEXT_IN_30SEC); // minor fracture + bitsDamage &= ~DMG_CLUB; + ffound = true; + } + if (bitsDamage & (DMG_FALL | DMG_CRUSH)) + { + if (fmajor) + SetSuitUpdate("!HEV_DMG5", false, SUIT_NEXT_IN_30SEC); // major fracture + else + SetSuitUpdate("!HEV_DMG4", false, SUIT_NEXT_IN_30SEC); // minor fracture + + bitsDamage &= ~(DMG_FALL | DMG_CRUSH); + ffound = true; + } + + if (bitsDamage & DMG_BULLET) + { + if (m_lastDamageAmount > 5) + SetSuitUpdate("!HEV_DMG6", false, SUIT_NEXT_IN_30SEC); // blood loss detected + //else + // SetSuitUpdate("!HEV_DMG0", false, SUIT_NEXT_IN_30SEC); // minor laceration + + bitsDamage &= ~DMG_BULLET; + ffound = true; + } + + if (bitsDamage & DMG_SLASH) + { + if (fmajor) + SetSuitUpdate("!HEV_DMG1", false, SUIT_NEXT_IN_30SEC); // major laceration + else + SetSuitUpdate("!HEV_DMG0", false, SUIT_NEXT_IN_30SEC); // minor laceration + + bitsDamage &= ~DMG_SLASH; + ffound = true; + } + + if (bitsDamage & DMG_SONIC) + { + if (fmajor) + SetSuitUpdate("!HEV_DMG2", false, SUIT_NEXT_IN_1MIN); // internal bleeding + bitsDamage &= ~DMG_SONIC; + ffound = true; + } + + if (bitsDamage & (DMG_POISON | DMG_PARALYZE)) + { + if (bitsDamage & DMG_POISON) + { + m_nPoisonDmg += info.GetDamage(); + m_tbdPrev = gpGlobals->curtime; + m_rgbTimeBasedDamage[itbd_PoisonRecover] = 0; + } + + SetSuitUpdate("!HEV_DMG3", false, SUIT_NEXT_IN_1MIN); // blood toxins detected + bitsDamage &= ~( DMG_POISON | DMG_PARALYZE ); + ffound = true; + } + + if (bitsDamage & DMG_ACID) + { + SetSuitUpdate("!HEV_DET1", false, SUIT_NEXT_IN_1MIN); // hazardous chemicals detected + bitsDamage &= ~DMG_ACID; + ffound = true; + } + + if (bitsDamage & DMG_NERVEGAS) + { + SetSuitUpdate("!HEV_DET0", false, SUIT_NEXT_IN_1MIN); // biohazard detected + bitsDamage &= ~DMG_NERVEGAS; + ffound = true; + } + + if (bitsDamage & DMG_RADIATION) + { + SetSuitUpdate("!HEV_DET2", false, SUIT_NEXT_IN_1MIN); // radiation detected + bitsDamage &= ~DMG_RADIATION; + ffound = true; + } + if (bitsDamage & DMG_SHOCK) + { + bitsDamage &= ~DMG_SHOCK; + ffound = true; + } + } + + m_Local.m_vecPunchAngle.SetX( -2 ); + + if (fTookDamage && !ftrivial && fmajor && flHealthPrev >= 75) + { + // first time we take major damage... + // turn automedic on if not on + SetSuitUpdate("!HEV_MED1", false, SUIT_NEXT_IN_30MIN); // automedic on + + // give morphine shot if not given recently + SetSuitUpdate("!HEV_HEAL7", false, SUIT_NEXT_IN_30MIN); // morphine shot + } + + if (fTookDamage && !ftrivial && fcritical && flHealthPrev < 75) + { + + // already took major damage, now it's critical... + if (m_iHealth < 6) + SetSuitUpdate("!HEV_HLTH3", false, SUIT_NEXT_IN_10MIN); // near death + else if (m_iHealth < 20) + SetSuitUpdate("!HEV_HLTH2", false, SUIT_NEXT_IN_10MIN); // health critical + + // give critical health warnings + if (!random->RandomInt(0,3) && flHealthPrev < 50) + SetSuitUpdate("!HEV_DMG7", false, SUIT_NEXT_IN_5MIN); //seek medical attention + } + + // if we're taking time based damage, warn about its continuing effects + if (fTookDamage && g_pGameRules->Damage_IsTimeBased( info.GetDamageType() ) && flHealthPrev < 75) + { + if (flHealthPrev < 50) + { + if (!random->RandomInt(0,3)) + SetSuitUpdate("!HEV_DMG7", false, SUIT_NEXT_IN_5MIN); //seek medical attention + } + else + SetSuitUpdate("!HEV_HLTH1", false, SUIT_NEXT_IN_10MIN); // health dropping + } + + // Do special explosion damage effect + if ( bitsDamage & DMG_BLAST ) + { + OnDamagedByExplosion( info ); + } + + gamestats->Event_PlayerDamage( this, info ); + + return fTookDamage; +} + + +int CHL1_Player::OnTakeDamage_Alive( const CTakeDamageInfo &info ) +{ + int nRet; + int nSavedHealth = m_iHealth; + + // Drown + if( info.GetDamageType() & DMG_DROWN ) + { + if( m_idrowndmg == m_idrownrestored ) + { + EmitSound( "Player.DrownStart" ); + } + else + { + EmitSound( "Player.DrownContinue" ); + } + } + + nRet = BaseClass::OnTakeDamage_Alive( info ); + + if ( GetFlags() & FL_GODMODE ) + { + m_iHealth = nSavedHealth; + } + else if (m_debugOverlays & OVERLAY_BUDDHA_MODE) + { + if ( m_iHealth <= 0 ) + { + m_iHealth = 1; + } + } + + return nRet; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL1_Player::FindMissTargets( void ) +{ + if ( m_flTargetFindTime > gpGlobals->curtime ) + return; + + m_flTargetFindTime = gpGlobals->curtime + 1.0f; + m_nNumMissPositions = 0; + + CBaseEntity *pEnts[256]; + Vector radius( 80, 80, 80); + + int numEnts = UTIL_EntitiesInBox( pEnts, 256, GetAbsOrigin()-radius, GetAbsOrigin()+radius, 0 ); + + for ( int i = 0; i < numEnts; i++ ) + { + if ( pEnts[i] == NULL ) + continue; + + if ( m_nNumMissPositions >= 16 ) + return; + + //See if it's a good target candidate + if ( FClassnameIs( pEnts[i], "prop_dynamic" ) || + FClassnameIs( pEnts[i], "dynamic_prop" ) || + FClassnameIs( pEnts[i], "prop_physics" ) || + FClassnameIs( pEnts[i], "physics_prop" ) ) + { + //NDebugOverlay::Cross3D( pEnts[i]->WorldSpaceCenter(), -Vector(4,4,4), Vector(4,4,4), 0, 255, 0, true, 1.0f ); + + m_vecMissPositions[m_nNumMissPositions++] = pEnts[i]->WorldSpaceCenter(); + continue; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Good position to shoot at +//----------------------------------------------------------------------------- +bool CHL1_Player::GetMissPosition( Vector *position ) +{ + if ( m_nNumMissPositions == 0 ) + return false; + + (*position) = m_vecMissPositions[ random->RandomInt( 0, m_nNumMissPositions-1 ) ]; + + return true; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CHL1_Player::FlashlightIsOn( void ) +{ + return IsEffectActive( EF_DIMLIGHT); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL1_Player::FlashlightTurnOn( void ) +{ + if ( IsSuitEquipped() ) + { + AddEffects( EF_DIMLIGHT ); + CPASAttenuationFilter filter( this ); + EmitSound( filter, entindex(), "Player.FlashlightOn" ); + + m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->curtime; + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL1_Player::FlashlightTurnOff( void ) +{ + RemoveEffects( EF_DIMLIGHT ); + CPASAttenuationFilter filter( this ); + EmitSound( filter, entindex(), "Player.FlashlightOff" ); + m_flFlashLightTime = FLASH_CHARGE_TIME + gpGlobals->curtime; +} + + +void CHL1_Player::UpdateClientData( void ) +{ + if (m_DmgTake || m_DmgSave || m_bitsHUDDamage != m_bitsDamageType) + { + // Comes from inside me if not set + Vector damageOrigin = GetLocalOrigin(); + // send "damage" message + // causes screen to flash, and pain compass to show direction of damage + damageOrigin = m_DmgOrigin; + + // only send down damage type that have hud art + int iShowHudDamage = g_pGameRules->Damage_GetShowOnHud(); + int visibleDamageBits = m_bitsDamageType & iShowHudDamage; + + m_DmgTake = clamp( m_DmgTake, 0, 255 ); + m_DmgSave = clamp( m_DmgSave, 0, 255 ); + + CSingleUserRecipientFilter user( this ); + user.MakeReliable(); + UserMessageBegin( user, "Damage" ); + WRITE_BYTE( m_DmgSave ); + WRITE_BYTE( m_DmgTake ); + WRITE_LONG( visibleDamageBits ); + WRITE_FLOAT( damageOrigin.x ); //BUG: Should be fixed point (to hud) not floats + WRITE_FLOAT( damageOrigin.y ); //BUG: However, the HUD does _not_ implement bitfield messages (yet) + WRITE_FLOAT( damageOrigin.z ); //BUG: We use WRITE_VEC3COORD for everything else + MessageEnd(); + + m_DmgTake = 0; + m_DmgSave = 0; + m_bitsHUDDamage = m_bitsDamageType; + + // Clear off non-time-based damage indicators + int iDamage = g_pGameRules->Damage_GetTimeBased(); + m_bitsDamageType &= iDamage; + } + + // Update Flashlight + if ( ( m_flFlashLightTime ) && ( m_flFlashLightTime <= gpGlobals->curtime ) ) + { + if ( FlashlightIsOn() ) + { + if ( m_nFlashBattery ) + { + m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->curtime; + m_nFlashBattery--; + + if ( !m_nFlashBattery ) + FlashlightTurnOff(); + } + } + else + { + if ( m_nFlashBattery < 100 ) + { + m_flFlashLightTime = FLASH_CHARGE_TIME + gpGlobals->curtime; + m_nFlashBattery++; + } + else + m_flFlashLightTime = 0; + } + } + + BaseClass::UpdateClientData(); +} + +void CHL1_Player::OnSave( IEntitySaveUtils *pUtils ) +{ + // If I'm pulling a box, stop. + StopPullingObject(); + + BaseClass::OnSave(pUtils); +} + +void CHL1_Player::CreateViewModel( int index /*=0*/ ) +{ + Assert( index >= 0 && index < MAX_VIEWMODELS ); + + if ( GetViewModel( index ) ) + return; + + CPredictedViewModel *vm = ( CPredictedViewModel * )CreateEntityByName( "predicted_viewmodel" ); + if ( vm ) + { + vm->SetAbsOrigin( GetAbsOrigin() ); + vm->SetOwner( this ); + vm->SetIndex( index ); + DispatchSpawn( vm ); + vm->FollowEntity( this ); + m_hViewModel.Set( index, vm ); + } +} + +void CHL1_Player::OnRestore( void ) +{ + BaseClass::OnRestore(); + + // If we are controlling a train, resend our train status + if( !FBitSet( m_iTrain, TRAIN_OFF ) ) + { + m_iTrain |= TRAIN_NEW; + } +} + + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +static void MatrixOrthogonalize( matrix3x4_t &matrix, int column ) +{ + Vector columns[3]; + int i; + + for ( i = 0; i < 3; i++ ) + { + MatrixGetColumn( matrix, i, columns[i] ); + } + + int index0 = column; + int index1 = (column+1)%3; + int index2 = (column+2)%3; + + columns[index2] = CrossProduct( columns[index0], columns[index1] ); + columns[index1] = CrossProduct( columns[index2], columns[index0] ); + VectorNormalize( columns[index2] ); + VectorNormalize( columns[index1] ); + MatrixSetColumn( columns[index1], index1, matrix ); + MatrixSetColumn( columns[index2], index2, matrix ); +} + +#define SIGN(x) ( (x) < 0 ? -1 : 1 ) + +static QAngle AlignAngles( const QAngle &angles, float cosineAlignAngle ) +{ + matrix3x4_t alignMatrix; + AngleMatrix( angles, alignMatrix ); + + for ( int j = 0; j < 3; j++ ) + { + Vector vec; + MatrixGetColumn( alignMatrix, j, vec ); + for ( int i = 0; i < 3; i++ ) + { + if ( fabs(vec[i]) > cosineAlignAngle ) + { + vec[i] = SIGN(vec[i]); + vec[(i+1)%3] = 0; + vec[(i+2)%3] = 0; + MatrixSetColumn( vec, j, alignMatrix ); + MatrixOrthogonalize( alignMatrix, j ); + break; + } + } + } + + QAngle out; + MatrixAngles( alignMatrix, out ); + return out; +} + + +static void TraceCollideAgainstBBox( const CPhysCollide *pCollide, const Vector &start, const Vector &end, const QAngle &angles, const Vector &boxOrigin, const Vector &mins, const Vector &maxs, trace_t *ptr ) +{ + physcollision->TraceBox( boxOrigin, boxOrigin + (start-end), mins, maxs, pCollide, start, angles, ptr ); + + if ( ptr->DidHit() ) + { + ptr->endpos = start * (1-ptr->fraction) + end * ptr->fraction; + ptr->startpos = start; + ptr->plane.dist = -ptr->plane.dist; + ptr->plane.normal *= -1; + } +} + +//--------------------------------------------- + +#include "player_pickup.h" +#include "props.h" +#include "vphysics/friction.h" +#include "physics_saverestore.h" +ConVar hl2_normspeed( "hl2_normspeed", "190" ); +ConVar player_throwforce( "player_throwforce", "1000" ); +ConVar physcannon_maxmass( "physcannon_maxmass", "250" ); + +// derive from this so we can add save/load data to it +struct game_shadowcontrol_params_t : public hlshadowcontrol_params_t +{ + DECLARE_SIMPLE_DATADESC(); +}; + +BEGIN_SIMPLE_DATADESC( game_shadowcontrol_params_t ) + + DEFINE_FIELD( targetPosition, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( targetRotation, FIELD_VECTOR ), + DEFINE_FIELD( maxAngular, FIELD_FLOAT ), + DEFINE_FIELD( maxDampAngular, FIELD_FLOAT ), + DEFINE_FIELD( maxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( maxDampSpeed, FIELD_FLOAT ), + DEFINE_FIELD( dampFactor, FIELD_FLOAT ), + DEFINE_FIELD( teleportDistance, FIELD_FLOAT ), + +END_DATADESC() + +// when looking level, hold bottom of object 8 inches below eye level +#define PLAYER_HOLD_LEVEL_EYES -8 + +// when looking down, hold bottom of object 0 inches from feet +#define PLAYER_HOLD_DOWN_FEET 2 + +// when looking up, hold bottom of object 24 inches above eye level +#define PLAYER_HOLD_UP_EYES 24 + +// use a +/-30 degree range for the entire range of motion of pitch +#define PLAYER_LOOK_PITCH_RANGE 30 + +// player can reach down 2ft below his feet (otherwise he'll hold the object above the bottom) +#define PLAYER_REACH_DOWN_DISTANCE 24 + +static void ComputePlayerMatrix( CBasePlayer *pPlayer, matrix3x4_t &out ) +{ + if ( !pPlayer ) + return; + + QAngle angles = pPlayer->EyeAngles(); + Vector origin = pPlayer->EyePosition(); + + // 0-360 / -180-180 + //angles.x = init ? 0 : AngleDistance( angles.x, 0 ); + //angles.x = clamp( angles.x, -PLAYER_LOOK_PITCH_RANGE, PLAYER_LOOK_PITCH_RANGE ); + angles.x = 0; + + float feet = pPlayer->GetAbsOrigin().z + pPlayer->WorldAlignMins().z; + float eyes = origin.z; + float zoffset = 0; + // moving up (negative pitch is up) + if ( angles.x < 0 ) + { + zoffset = RemapVal( angles.x, 0, -PLAYER_LOOK_PITCH_RANGE, PLAYER_HOLD_LEVEL_EYES, PLAYER_HOLD_UP_EYES ); + } + else + { + zoffset = RemapVal( angles.x, 0, PLAYER_LOOK_PITCH_RANGE, PLAYER_HOLD_LEVEL_EYES, PLAYER_HOLD_DOWN_FEET + (feet - eyes) ); + } + origin.z += zoffset; + angles.x = 0; + AngleMatrix( angles, origin, out ); +} + +//----------------------------------------------------------------------------- +class CGrabController : public IMotionEvent +{ + DECLARE_SIMPLE_DATADESC(); + +public: + + CGrabController( void ); + ~CGrabController( void ); + void AttachEntity( CBasePlayer *pPlayer, CBaseEntity *pEntity, IPhysicsObject *pPhys, bool bIsMegaPhysCannon = false ); + void DetachEntity(); + void OnRestore(); + + bool UpdateObject( CBasePlayer *pPlayer, float flError ); + + void SetTargetPosition( const Vector &target, const QAngle &targetOrientation ); + float ComputeError(); + float GetLoadWeight( void ) const { return m_flLoadWeight; } + void SetAngleAlignment( float alignAngleCosine ) { m_angleAlignment = alignAngleCosine; } + void SetIgnorePitch( bool bIgnore ) { m_bIgnoreRelativePitch = bIgnore; } + QAngle TransformAnglesToPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ); + QAngle TransformAnglesFromPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ); + + CBaseEntity *GetAttached() { return (CBaseEntity *)m_attachedEntity; } + + IMotionEvent::simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular ); + float GetSavedMass( IPhysicsObject *pObject ); + +private: + // Compute the max speed for an attached object + void ComputeMaxSpeed( CBaseEntity *pEntity, IPhysicsObject *pPhysics ); + + game_shadowcontrol_params_t m_shadow; + float m_timeToArrive; + float m_errorTime; + float m_error; + float m_contactAmount; + float m_angleAlignment; + bool m_bCarriedEntityBlocksLOS; + bool m_bIgnoreRelativePitch; + + float m_flLoadWeight; + float m_savedRotDamping[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + float m_savedMass[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + EHANDLE m_attachedEntity; + QAngle m_vecPreferredCarryAngles; + bool m_bHasPreferredCarryAngles; + + QAngle m_attachedAnglesPlayerSpace; + Vector m_attachedPositionObjectSpace; + + IPhysicsMotionController *m_controller; + + friend class CWeaponPhysCannon; +}; + +BEGIN_SIMPLE_DATADESC( CGrabController ) + + DEFINE_EMBEDDED( m_shadow ), + + DEFINE_FIELD( m_timeToArrive, FIELD_FLOAT ), + DEFINE_FIELD( m_errorTime, FIELD_FLOAT ), + DEFINE_FIELD( m_error, FIELD_FLOAT ), + DEFINE_FIELD( m_contactAmount, FIELD_FLOAT ), + DEFINE_AUTO_ARRAY( m_savedRotDamping, FIELD_FLOAT ), + DEFINE_AUTO_ARRAY( m_savedMass, FIELD_FLOAT ), + DEFINE_FIELD( m_flLoadWeight, FIELD_FLOAT ), + DEFINE_FIELD( m_bCarriedEntityBlocksLOS, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bIgnoreRelativePitch, FIELD_BOOLEAN ), + DEFINE_FIELD( m_attachedEntity, FIELD_EHANDLE ), + DEFINE_FIELD( m_angleAlignment, FIELD_FLOAT ), + DEFINE_FIELD( m_vecPreferredCarryAngles, FIELD_VECTOR ), + DEFINE_FIELD( m_bHasPreferredCarryAngles, FIELD_BOOLEAN ), + DEFINE_FIELD( m_attachedAnglesPlayerSpace, FIELD_VECTOR ), + DEFINE_FIELD( m_attachedPositionObjectSpace, FIELD_VECTOR ), + + // Physptrs can't be inside embedded classes + // DEFINE_PHYSPTR( m_controller ), + +END_DATADESC() + +const float DEFAULT_MAX_ANGULAR = 360.0f * 10.0f; +const float REDUCED_CARRY_MASS = 1.0f; + +CGrabController::CGrabController( void ) +{ + m_shadow.dampFactor = 1.0; + m_shadow.teleportDistance = 0; + m_errorTime = 0; + m_error = 0; + // make this controller really stiff! + m_shadow.maxSpeed = 1000; + m_shadow.maxAngular = DEFAULT_MAX_ANGULAR; + m_shadow.maxDampSpeed = m_shadow.maxSpeed*2; + m_shadow.maxDampAngular = m_shadow.maxAngular; + m_attachedEntity = NULL; + m_vecPreferredCarryAngles = vec3_angle; + m_bHasPreferredCarryAngles = false; +} + +CGrabController::~CGrabController( void ) +{ + DetachEntity(); +} + +void CGrabController::OnRestore() +{ + if ( m_controller ) + { + m_controller->SetEventHandler( this ); + } +} + +void CGrabController::SetTargetPosition( const Vector &target, const QAngle &targetOrientation ) +{ + m_shadow.targetPosition = target; + m_shadow.targetRotation = targetOrientation; + + m_timeToArrive = gpGlobals->frametime; + + CBaseEntity *pAttached = GetAttached(); + if ( pAttached ) + { + IPhysicsObject *pObj = pAttached->VPhysicsGetObject(); + + if ( pObj != NULL ) + { + pObj->Wake(); + } + else + { + DetachEntity(); + } + } +} + +float CGrabController::ComputeError() +{ + if ( m_errorTime <= 0 ) + return 0; + + CBaseEntity *pAttached = GetAttached(); + if ( pAttached ) + { + Vector pos; + IPhysicsObject *pObj = pAttached->VPhysicsGetObject(); + if ( pObj ) + { + pObj->GetShadowPosition( &pos, NULL ); + float error = (m_shadow.targetPosition - pos).Length(); + if ( m_errorTime > 0 ) + { + if ( m_errorTime > 1 ) + { + m_errorTime = 1; + } + float speed = error / m_errorTime; + if ( speed > m_shadow.maxSpeed ) + { + error *= 0.5; + } + m_error = (1-m_errorTime) * m_error + error * m_errorTime; + } + } + else + { + DevMsg( "Object attached to Physcannon has no physics object\n" ); + DetachEntity(); + return 9999; // force detach + } + } + + if ( pAttached->IsEFlagSet( EFL_IS_BEING_LIFTED_BY_BARNACLE ) ) + { + m_error *= 3.0f; + } + + m_errorTime = 0; + + return m_error; +} + + +#define MASS_SPEED_SCALE 60 +#define MAX_MASS 40 + + +void CGrabController::ComputeMaxSpeed( CBaseEntity *pEntity, IPhysicsObject *pPhysics ) +{ + m_shadow.maxSpeed = 1000; + m_shadow.maxAngular = DEFAULT_MAX_ANGULAR; + + // Compute total mass... + float flMass = PhysGetEntityMass( pEntity ); + float flMaxMass = physcannon_maxmass.GetFloat(); + if ( flMass <= flMaxMass ) + return; + + float flLerpFactor = clamp( flMass, flMaxMass, 500.0f ); + flLerpFactor = SimpleSplineRemapVal( flLerpFactor, flMaxMass, 500.0f, 0.0f, 1.0f ); + + float invMass = pPhysics->GetInvMass(); + float invInertia = pPhysics->GetInvInertia().Length(); + + float invMaxMass = 1.0f / MAX_MASS; + float ratio = invMaxMass / invMass; + invMass = invMaxMass; + invInertia *= ratio; + + float maxSpeed = invMass * MASS_SPEED_SCALE * 200; + float maxAngular = invInertia * MASS_SPEED_SCALE * 360; + + m_shadow.maxSpeed = Lerp( flLerpFactor, m_shadow.maxSpeed, maxSpeed ); + m_shadow.maxAngular = Lerp( flLerpFactor, m_shadow.maxAngular, maxAngular ); +} + + +QAngle CGrabController::TransformAnglesToPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ) +{ + if ( m_bIgnoreRelativePitch ) + { + matrix3x4_t test; + QAngle angleTest = pPlayer->EyeAngles(); + angleTest.x = 0; + AngleMatrix( angleTest, test ); + return TransformAnglesToLocalSpace( anglesIn, test ); + } + return TransformAnglesToLocalSpace( anglesIn, pPlayer->EntityToWorldTransform() ); +} + +QAngle CGrabController::TransformAnglesFromPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ) +{ + if ( m_bIgnoreRelativePitch ) + { + matrix3x4_t test; + QAngle angleTest = pPlayer->EyeAngles(); + angleTest.x = 0; + AngleMatrix( angleTest, test ); + return TransformAnglesToWorldSpace( anglesIn, test ); + } + return TransformAnglesToWorldSpace( anglesIn, pPlayer->EntityToWorldTransform() ); +} + + +void CGrabController::AttachEntity( CBasePlayer *pPlayer, CBaseEntity *pEntity, IPhysicsObject *pPhys, bool bIsMegaPhysCannon ) +{ + // play the impact sound of the object hitting the player + // used as feedback to let the player know he picked up the object + PhysicsImpactSound( pPlayer, pPhys, CHAN_STATIC, pPhys->GetMaterialIndex(), pPlayer->VPhysicsGetObject()->GetMaterialIndex(), 1.0, 64 ); + Vector position; + QAngle angles; + pPhys->GetPosition( &position, &angles ); + // If it has a preferred orientation, use that instead. + Pickup_GetPreferredCarryAngles( pEntity, pPlayer, pPlayer->EntityToWorldTransform(), angles ); + +// ComputeMaxSpeed( pEntity, pPhys ); + + // Carried entities can never block LOS + m_bCarriedEntityBlocksLOS = pEntity->BlocksLOS(); + pEntity->SetBlocksLOS( false ); + m_controller = physenv->CreateMotionController( this ); + m_controller->AttachObject( pPhys, true ); + // Don't do this, it's causing trouble with constraint solvers. + //m_controller->SetPriority( IPhysicsMotionController::HIGH_PRIORITY ); + + pPhys->Wake(); + PhysSetGameFlags( pPhys, FVPHYSICS_PLAYER_HELD ); + SetTargetPosition( position, angles ); + m_attachedEntity = pEntity; + IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + int count = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); + m_flLoadWeight = 0; + float damping = 10; + float flFactor = count / 7.5f; + if ( flFactor < 1.0f ) + { + flFactor = 1.0f; + } + for ( int i = 0; i < count; i++ ) + { + float mass = pList[i]->GetMass(); + pList[i]->GetDamping( NULL, &m_savedRotDamping[i] ); + m_flLoadWeight += mass; + m_savedMass[i] = mass; + + // reduce the mass to prevent the player from adding crazy amounts of energy to the system + pList[i]->SetMass( REDUCED_CARRY_MASS / flFactor ); + pList[i]->SetDamping( NULL, &damping ); + } + + // Give extra mass to the phys object we're actually picking up + pPhys->SetMass( REDUCED_CARRY_MASS ); + pPhys->EnableDrag( false ); + + m_errorTime = bIsMegaPhysCannon ? -1.5f : -1.0f; // 1 seconds until error starts accumulating + m_error = 0; + m_contactAmount = 0; + + m_attachedAnglesPlayerSpace = TransformAnglesToPlayerSpace( angles, pPlayer ); + if ( m_angleAlignment != 0 ) + { + m_attachedAnglesPlayerSpace = AlignAngles( m_attachedAnglesPlayerSpace, m_angleAlignment ); + } + + VectorITransform( pEntity->WorldSpaceCenter(), pEntity->EntityToWorldTransform(), m_attachedPositionObjectSpace ); + + // If it's a prop, see if it has desired carry angles + CPhysicsProp *pProp = dynamic_cast<CPhysicsProp *>(pEntity); + if ( pProp ) + { + m_bHasPreferredCarryAngles = pProp->GetPropDataAngles( "preferred_carryangles", m_vecPreferredCarryAngles ); + } + else + { + m_bHasPreferredCarryAngles = false; + } +} + +static void ClampPhysicsVelocity( IPhysicsObject *pPhys, float linearLimit, float angularLimit ) +{ + Vector vel; + AngularImpulse angVel; + pPhys->GetVelocity( &vel, &angVel ); + float speed = VectorNormalize(vel) - linearLimit; + float angSpeed = VectorNormalize(angVel) - angularLimit; + speed = speed < 0 ? 0 : -speed; + angSpeed = angSpeed < 0 ? 0 : -angSpeed; + vel *= speed; + angVel *= angSpeed; + pPhys->AddVelocity( &vel, &angVel ); +} + +void CGrabController::DetachEntity() +{ + CBaseEntity *pEntity = GetAttached(); + if ( pEntity ) + { + // Restore the LS blocking state + pEntity->SetBlocksLOS( m_bCarriedEntityBlocksLOS ); + IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + int count = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); + for ( int i = 0; i < count; i++ ) + { + IPhysicsObject *pPhys = pList[i]; + if ( !pPhys ) + continue; + + // on the odd chance that it's gone to sleep while under anti-gravity + pPhys->EnableDrag( true ); + pPhys->Wake(); + pPhys->SetMass( m_savedMass[i] ); + pPhys->SetDamping( NULL, &m_savedRotDamping[i] ); + PhysClearGameFlags( pPhys, FVPHYSICS_PLAYER_HELD ); + if ( pPhys->GetContactPoint( NULL, NULL ) ) + { + PhysForceClearVelocity( pPhys ); + } + else + { + ClampPhysicsVelocity( pPhys, hl2_normspeed.GetFloat() * 1.5f, 2.0f * 360.0f ); + } + + } + } + + m_attachedEntity = NULL; + physenv->DestroyMotionController( m_controller ); + m_controller = NULL; +} + +static bool InContactWithHeavyObject( IPhysicsObject *pObject, float heavyMass ) +{ + bool contact = false; + IPhysicsFrictionSnapshot *pSnapshot = pObject->CreateFrictionSnapshot(); + while ( pSnapshot->IsValid() ) + { + IPhysicsObject *pOther = pSnapshot->GetObject( 1 ); + if ( !pOther->IsMoveable() || pOther->GetMass() > heavyMass ) + { + contact = true; + break; + } + pSnapshot->NextFrictionData(); + } + pObject->DestroyFrictionSnapshot( pSnapshot ); + return contact; +} + +IMotionEvent::simresult_e CGrabController::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular ) +{ + game_shadowcontrol_params_t shadowParams = m_shadow; + if ( InContactWithHeavyObject( pObject, GetLoadWeight() ) ) + { + m_contactAmount = Approach( 0.1f, m_contactAmount, deltaTime*2.0f ); + } + else + { + m_contactAmount = Approach( 1.0f, m_contactAmount, deltaTime*2.0f ); + } + shadowParams.maxAngular = m_shadow.maxAngular * m_contactAmount * m_contactAmount * m_contactAmount; + m_timeToArrive = pObject->ComputeShadowControl( shadowParams, m_timeToArrive, deltaTime ); + + // Slide along the current contact points to fix bouncing problems + Vector velocity; + AngularImpulse angVel; + pObject->GetVelocity( &velocity, &angVel ); + PhysComputeSlideDirection( pObject, velocity, angVel, &velocity, &angVel, GetLoadWeight() ); + pObject->SetVelocityInstantaneous( &velocity, NULL ); + + linear.Init(); + angular.Init(); + m_errorTime += deltaTime; + + return SIM_LOCAL_ACCELERATION; +} + +float CGrabController::GetSavedMass( IPhysicsObject *pObject ) +{ + CBaseEntity *pHeld = m_attachedEntity; + if ( pHeld ) + { + if ( pObject->GetGameData() == (void*)pHeld ) + { + IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + int count = pHeld->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); + for ( int i = 0; i < count; i++ ) + { + if ( pList[i] == pObject ) + return m_savedMass[i]; + } + } + } + return 0.0f; +} + +bool CGrabController::UpdateObject( CBasePlayer *pPlayer, float flError ) +{ + CBaseEntity *pEntity = GetAttached(); + if ( !pEntity || ComputeError() > flError || pPlayer->GetGroundEntity() == pEntity || !pEntity->VPhysicsGetObject() ) + { + return false; + } + + //Adrian: Oops, our object became motion disabled, let go! + IPhysicsObject *pPhys = pEntity->VPhysicsGetObject(); + if ( pPhys && pPhys->IsMoveable() == false ) + { + return false; + } + + Vector forward, right, up; + QAngle playerAngles = pPlayer->EyeAngles(); + float pitch = AngleDistance(playerAngles.x,0); + playerAngles.x = clamp( pitch, -75, 75 ); + AngleVectors( playerAngles, &forward, &right, &up ); + + // Now clamp a sphere of object radius at end to the player's bbox + Vector radial = physcollision->CollideGetExtent( pPhys->GetCollide(), vec3_origin, pEntity->GetAbsAngles(), -forward ); + Vector player2d = pPlayer->CollisionProp()->OBBMaxs(); + float playerRadius = player2d.Length2D(); + float radius = playerRadius + fabs(DotProduct( forward, radial )); + + float distance = 24 + ( radius * 2.0f ); + + Vector start = pPlayer->Weapon_ShootPosition(); + Vector end = start + ( forward * distance ); + + trace_t tr; + CTraceFilterSkipTwoEntities traceFilter( pPlayer, pEntity, COLLISION_GROUP_NONE ); + Ray_t ray; + ray.Init( start, end ); + enginetrace->TraceRay( ray, MASK_SOLID_BRUSHONLY, &traceFilter, &tr ); + + if ( tr.fraction < 0.5 ) + { + end = start + forward * (radius*0.5f); + } + else if ( tr.fraction <= 1.0f ) + { + end = start + forward * ( distance - radius ); + } + Vector playerMins, playerMaxs, nearest; + pPlayer->CollisionProp()->WorldSpaceAABB( &playerMins, &playerMaxs ); + Vector playerLine = pPlayer->CollisionProp()->WorldSpaceCenter(); + CalcClosestPointOnLine( end, playerLine+Vector(0,0,playerMins.z), playerLine+Vector(0,0,playerMaxs.z), nearest, NULL ); + + Vector delta = end - nearest; + float len = VectorNormalize(delta); + if ( len < radius ) + { + end = nearest + radius * delta; + } +/* + //Show overlays of radius + if ( g_debug_physcannon.GetBool() ) + { + NDebugOverlay::Box( end, -Vector( 2,2,2 ), Vector(2,2,2), 0, 255, 0, true, 0 ); + + NDebugOverlay::Box( GetAttached()->WorldSpaceCenter(), + -Vector( radius, radius, radius), + Vector( radius, radius, radius ), + 255, 0, 0, + true, + 0.0f ); + } +*/ + + QAngle angles = TransformAnglesFromPlayerSpace( m_attachedAnglesPlayerSpace, pPlayer ); + + // If it has a preferred orientation, update to ensure we're still oriented correctly. + Pickup_GetPreferredCarryAngles( pEntity, pPlayer, pPlayer->EntityToWorldTransform(), angles ); + + // We may be holding a prop that has preferred carry angles + if ( m_bHasPreferredCarryAngles ) + { + matrix3x4_t tmp; + ComputePlayerMatrix( pPlayer, tmp ); + angles = TransformAnglesToWorldSpace( m_vecPreferredCarryAngles, tmp ); + } + + matrix3x4_t attachedToWorld; + Vector offset; + AngleMatrix( angles, attachedToWorld ); + VectorRotate( m_attachedPositionObjectSpace, attachedToWorld, offset ); + + SetTargetPosition( end - offset, angles ); + return true; +} + +//----------------------------------------------------------------------------- +// Player pickup controller +//----------------------------------------------------------------------------- + +class CPlayerPickupController : public CBaseEntity +{ + DECLARE_DATADESC(); + DECLARE_CLASS( CPlayerPickupController, CBaseEntity ); +public: + void Init( CBasePlayer *pPlayer, CBaseEntity *pObject ); + void Shutdown( bool bThrown = false ); + bool OnControls( CBaseEntity *pControls ) { return true; } + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void OnRestore() + { + m_grabController.OnRestore(); + } + void VPhysicsUpdate( IPhysicsObject *pPhysics ){} + void VPhysicsShadowUpdate( IPhysicsObject *pPhysics ) {} + + bool IsHoldingEntity( CBaseEntity *pEnt ); + CGrabController &GetGrabController() { return m_grabController; } + +private: + CGrabController m_grabController; + CBasePlayer *m_pPlayer; +}; + +LINK_ENTITY_TO_CLASS( player_pickup, CPlayerPickupController ); + +//--------------------------------------------------------- +// Save/Restore +//--------------------------------------------------------- +BEGIN_DATADESC( CPlayerPickupController ) + + DEFINE_EMBEDDED( m_grabController ), + + // Physptrs can't be inside embedded classes + DEFINE_PHYSPTR( m_grabController.m_controller ), + + DEFINE_FIELD( m_pPlayer, FIELD_CLASSPTR ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pPlayer - +// *pObject - +//----------------------------------------------------------------------------- +void CPlayerPickupController::Init( CBasePlayer *pPlayer, CBaseEntity *pObject ) +{ + // Holster player's weapon + if ( pPlayer->GetActiveWeapon() ) + { + if ( !pPlayer->GetActiveWeapon()->Holster() ) + { + Shutdown(); + return; + } + } + + // If the target is debris, convert it to non-debris + if ( pObject->GetCollisionGroup() == COLLISION_GROUP_DEBRIS ) + { + // Interactive debris converts back to debris when it comes to rest + pObject->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS ); + } + + // done so I'll go across level transitions with the player + SetParent( pPlayer ); + m_grabController.SetIgnorePitch( true ); + m_grabController.SetAngleAlignment( DOT_30DEGREE ); + m_pPlayer = pPlayer; + IPhysicsObject *pPhysics = pObject->VPhysicsGetObject(); + Pickup_OnPhysGunPickup( pObject, m_pPlayer ); + m_grabController.AttachEntity( pPlayer, pObject, pPhysics ); + + m_pPlayer->m_Local.m_iHideHUD |= HIDEHUD_WEAPONSELECTION; + m_pPlayer->SetUseEntity( this ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void CPlayerPickupController::Shutdown( bool bThrown ) +{ + CBaseEntity *pObject = m_grabController.GetAttached(); + + m_grabController.DetachEntity(); + + if ( pObject != NULL ) + { + Pickup_OnPhysGunDrop( pObject, m_pPlayer, bThrown ? THROWN_BY_PLAYER : DROPPED_BY_PLAYER ); + } + + if ( m_pPlayer ) + { + m_pPlayer->SetUseEntity( NULL ); + if ( m_pPlayer->GetActiveWeapon() ) + { + m_pPlayer->GetActiveWeapon()->Deploy(); + } + + m_pPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_WEAPONSELECTION; + } + Remove(); +} + + +void CPlayerPickupController::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( ToBasePlayer(pActivator) == m_pPlayer ) + { + CBaseEntity *pAttached = m_grabController.GetAttached(); + + // UNDONE: Use vphysics stress to decide to drop objects + // UNDONE: Must fix case of forcing objects into the ground you're standing on (causes stress) before that will work + if ( !pAttached || useType == USE_OFF || (m_pPlayer->m_nButtons & IN_ATTACK2) || m_grabController.ComputeError() > 12 ) + { + Shutdown(); + return; + } + + //Adrian: Oops, our object became motion disabled, let go! + IPhysicsObject *pPhys = pAttached->VPhysicsGetObject(); + if ( pPhys && pPhys->IsMoveable() == false ) + { + Shutdown(); + return; + } + +#if STRESS_TEST + vphysics_objectstress_t stress; + CalculateObjectStress( pAttached->VPhysicsGetObject(), pAttached, &stress ); + if ( stress.exertedStress > 250 ) + { + Shutdown(); + return; + } +#endif + // +ATTACK will throw phys objects + if ( m_pPlayer->m_nButtons & IN_ATTACK ) + { + Shutdown( true ); + Vector vecLaunch; + m_pPlayer->EyeVectors( &vecLaunch ); + // JAY: Scale this with mass because some small objects really go flying + float massFactor = clamp( pAttached->VPhysicsGetObject()->GetMass(), 0.5, 15 ); + massFactor = RemapVal( massFactor, 0.5, 15, 0.5, 4 ); + vecLaunch *= player_throwforce.GetFloat() * massFactor; + + pAttached->VPhysicsGetObject()->ApplyForceCenter( vecLaunch ); + AngularImpulse aVel = RandomAngularImpulse( -10, 10 ) * massFactor; + pAttached->VPhysicsGetObject()->ApplyTorqueCenter( aVel ); + return; + } + + if ( useType == USE_SET ) + { + // update position + m_grabController.UpdateObject( m_pPlayer, 12 ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEnt - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CPlayerPickupController::IsHoldingEntity( CBaseEntity *pEnt ) +{ + return ( m_grabController.GetAttached() == pEnt ); +} + +ConVar hl1_new_pull( "hl1_new_pull", "1" ); +void PlayerPickupObject( CBasePlayer *pPlayer, CBaseEntity *pObject ) +{ + if( hl1_new_pull.GetBool() ) + { + CHL1_Player *pHL1Player = dynamic_cast<CHL1_Player*>(pPlayer); + if( pHL1Player && !pHL1Player->IsPullingObject() ) + { + pHL1Player->StartPullingObject(pObject); + } + } +} |