//========= 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( (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(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(pPlayer); if( pHL1Player && !pHL1Player->IsPullingObject() ) { pHL1Player->StartPullingObject(pObject); } } }