//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Multiplayer Player for HL1. // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "hl1mp_player.h" #include "client.h" #include "team.h" class CTEPlayerAnimEvent : public CBaseTempEntity { public: DECLARE_CLASS( CTEPlayerAnimEvent, CBaseTempEntity ); DECLARE_SERVERCLASS(); CTEPlayerAnimEvent( const char *name ) : CBaseTempEntity( name ) { } CNetworkHandle( CBasePlayer, m_hPlayer ); CNetworkVar( int, m_iEvent ); CNetworkVar( int, m_nData ); }; IMPLEMENT_SERVERCLASS_ST_NOBASE( CTEPlayerAnimEvent, DT_TEPlayerAnimEvent ) SendPropEHandle( SENDINFO( m_hPlayer ) ), SendPropInt( SENDINFO( m_iEvent ), Q_log2( PLAYERANIMEVENT_COUNT ) + 1, SPROP_UNSIGNED ), SendPropInt( SENDINFO( m_nData ), 32 ) END_SEND_TABLE() static CTEPlayerAnimEvent g_TEPlayerAnimEvent( "PlayerAnimEvent" ); void TE_PlayerAnimEvent( CBasePlayer *pPlayer, PlayerAnimEvent_t event, int nData ) { CPVSFilter filter( pPlayer->EyePosition() ); // The player himself doesn't need to be sent his animation events // unless cs_showanimstate wants to show them. // if ( !ToolsEnabled() && ( cl_showanimstate.GetInt() == pPlayer->entindex() ) ) { // filter.RemoveRecipient( pPlayer ); } g_TEPlayerAnimEvent.m_hPlayer = pPlayer; g_TEPlayerAnimEvent.m_iEvent = event; g_TEPlayerAnimEvent.m_nData = nData; g_TEPlayerAnimEvent.Create( filter, 0 ); } ////////////////////////////////////////////////////////////////////////////////////////// extern int gEvilImpulse101; LINK_ENTITY_TO_CLASS( player_mp, CHL1MP_Player ); PRECACHE_REGISTER( player_mp ); IMPLEMENT_SERVERCLASS_ST( CHL1MP_Player, DT_HL1MP_PLAYER ) SendPropExclude( "DT_BaseAnimating", "m_flPoseParameter" ), SendPropExclude( "DT_BaseAnimating", "m_flPlaybackRate" ), SendPropExclude( "DT_BaseAnimating", "m_nSequence" ), SendPropExclude( "DT_BaseEntity", "m_angRotation" ), SendPropExclude( "DT_BaseAnimatingOverlay", "overlay_vars" ), // cs_playeranimstate and clientside animation takes care of these on the client // SendPropExclude( "DT_ServerAnimationData" , "m_flCycle" ), SendPropExclude( "DT_AnimTimeMustBeFirst" , "m_flAnimTime" ), SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 0), 11 ), SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 1), 11 ), SendPropEHandle( SENDINFO( m_hRagdoll ) ), SendPropInt( SENDINFO( m_iSpawnInterpCounter), 4 ), SendPropInt( SENDINFO( m_iRealSequence ), 9 ), // SendPropDataTable( SENDINFO_DT( m_Shared ), &REFERENCE_SEND_TABLE( DT_TFCPlayerShared ) ) END_SEND_TABLE() void cc_CreatePredictionError_f() { CBaseEntity *pEnt = CBaseEntity::Instance( 1 ); pEnt->SetAbsOrigin( pEnt->GetAbsOrigin() + Vector( 63, 0, 0 ) ); } ConCommand cc_CreatePredictionError( "CreatePredictionError", cc_CreatePredictionError_f, "Create a prediction error", FCVAR_CHEAT ); static const char * s_szModelPath = "models/player/mp/"; CHL1MP_Player::CHL1MP_Player() { m_PlayerAnimState = CreatePlayerAnimState( this ); // item_list = 0; UseClientSideAnimation(); m_angEyeAngles.Init(); // m_pCurStateInfo = NULL; m_lifeState = LIFE_DEAD; // Start "dead". m_iSpawnInterpCounter = 0; m_flNextModelChangeTime = 0; m_flNextTeamChangeTime = 0; // SetViewOffset( TFC_PLAYER_VIEW_OFFSET ); // SetContextThink( &CTFCPlayer::TFCPlayerThink, gpGlobals->curtime, "TFCPlayerThink" ); } CHL1MP_Player::~CHL1MP_Player() { m_PlayerAnimState->Release(); } void CHL1MP_Player::PostThink( void ) { BaseClass::PostThink(); QAngle angles = GetLocalAngles(); angles[PITCH] = 0; SetLocalAngles( angles ); // Store the eye angles pitch so the client can compute its animation state correctly. m_angEyeAngles = EyeAngles(); m_PlayerAnimState->Update( m_angEyeAngles[YAW], m_angEyeAngles[PITCH] ); } void CHL1MP_Player::Spawn( void ) { if ( !IsObserver() ) { RemoveEffects( EF_NODRAW ); SetMoveType( MOVETYPE_WALK ); RemoveSolidFlags( FSOLID_NOT_SOLID ); // if no model, force one if ( !GetModelPtr() ) SetModel( "models/player/mp/gordon/gordon.mdl" ); } m_flNextModelChangeTime = 0; m_flNextTeamChangeTime = 0; BaseClass::Spawn(); if ( !IsObserver() ) { GiveDefaultItems(); SetPlayerModel(); } m_bHasLongJump = false; m_iSpawnInterpCounter = (m_iSpawnInterpCounter + 1) % 8; } void CHL1MP_Player::DoAnimationEvent( PlayerAnimEvent_t event, int nData ) { m_PlayerAnimState->DoAnimationEvent( event, nData ); TE_PlayerAnimEvent( this, event, nData ); // Send to any clients who can see this guy. } void CHL1MP_Player::GiveDefaultItems( void ) { GiveNamedItem( "weapon_crowbar" ); GiveNamedItem( "weapon_glock" ); CBasePlayer::GiveAmmo( 68, "9mmRound" ); } void CHL1MP_Player::UpdateOnRemove( void ) { if ( m_hRagdoll ) { UTIL_RemoveImmediate( m_hRagdoll ); m_hRagdoll = NULL; } BaseClass::UpdateOnRemove(); } void CHL1MP_Player::DetonateSatchelCharges( void ) { CBaseEntity *pSatchel = NULL; while ( (pSatchel = gEntList.FindEntityByClassname( pSatchel, "monster_satchel" ) ) != NULL) { if ( pSatchel->GetOwnerEntity() == this ) { pSatchel->Use( this, this, USE_ON, 0 ); } } } void CHL1MP_Player::Event_Killed( const CTakeDamageInfo &info ) { DoAnimationEvent( PLAYERANIMEVENT_DIE ); // SetNumAnimOverlays( 0 ); // Note: since we're dead, it won't draw us on the client, but we don't set EF_NODRAW // because we still want to transmit to the clients in our PVS. if ( !IsHLTV() ) CreateRagdollEntity(); DetonateSatchelCharges(); BaseClass::Event_Killed( info ); m_lifeState = LIFE_DEAD; RemoveEffects( EF_NODRAW ); // still draw player body } void CHL1MP_Player::SetAnimation( PLAYER_ANIM playerAnim ) { // BaseClass::SetAnimation( playerAnim ); if ( playerAnim == PLAYER_ATTACK1 ) { DoAnimationEvent( PLAYERANIMEVENT_FIRE_GUN ); } int animDesired; char szAnim[64]; float speed; speed = GetAbsVelocity().Length2D(); if (GetFlags() & (FL_FROZEN|FL_ATCONTROLS)) { speed = 0; playerAnim = PLAYER_IDLE; } if ( playerAnim == PLAYER_ATTACK1 ) { if ( speed > 0 ) { playerAnim = PLAYER_WALK; } else { playerAnim = PLAYER_IDLE; } } Activity idealActivity = ACT_WALK;// TEMP!!!!! // This could stand to be redone. Why is playerAnim abstracted from activity? (sjb) if (playerAnim == PLAYER_JUMP) { idealActivity = ACT_HOP; } else if (playerAnim == PLAYER_SUPERJUMP) { idealActivity = ACT_LEAP; } else if (playerAnim == PLAYER_DIE) { if ( m_lifeState == LIFE_ALIVE ) { idealActivity = ACT_DIERAGDOLL; } } else if (playerAnim == PLAYER_ATTACK1) { if ( GetActivity() == ACT_HOVER || GetActivity() == ACT_SWIM || GetActivity() == ACT_HOP || GetActivity() == ACT_LEAP || GetActivity() == ACT_DIESIMPLE ) { idealActivity = GetActivity(); } else { idealActivity = ACT_RANGE_ATTACK1; } } else if (playerAnim == PLAYER_IDLE || playerAnim == PLAYER_WALK) { if ( !( GetFlags() & FL_ONGROUND ) && (GetActivity() == ACT_HOP || GetActivity() == ACT_LEAP) ) // Still jumping { idealActivity = GetActivity(); } else if ( GetWaterLevel() > 1 ) { if ( speed == 0 ) idealActivity = ACT_HOVER; else idealActivity = ACT_SWIM; } else if ( speed > 0 ) { idealActivity = ACT_WALK; } else { idealActivity = ACT_IDLE; } } if (idealActivity == ACT_RANGE_ATTACK1) { if ( GetFlags() & FL_DUCKING ) // crouching { Q_strncpy( szAnim, "crouch_shoot_" ,sizeof(szAnim)); } else { Q_strncpy( szAnim, "ref_shoot_" ,sizeof(szAnim)); } Q_strncat( szAnim, m_szAnimExtension ,sizeof(szAnim), COPY_ALL_CHARACTERS ); animDesired = LookupSequence( szAnim ); if (animDesired == -1) animDesired = 0; if ( GetSequence() != animDesired || !SequenceLoops() ) { SetCycle( 0 ); } // Tracker 24588: In single player when firing own weapon this causes eye and punchangle to jitter //if (!SequenceLoops()) //{ // IncrementInterpolationFrame(); //} SetActivity( idealActivity ); ResetSequence( animDesired ); } else if (idealActivity == ACT_IDLE) { if ( GetFlags() & FL_DUCKING ) { animDesired = LookupSequence( "crouch_idle" ); } else { animDesired = LookupSequence( "look_idle" ); } if (animDesired == -1) animDesired = 0; SetActivity( ACT_IDLE ); } else if ( idealActivity == ACT_WALK ) { if ( GetFlags() & FL_DUCKING ) { animDesired = SelectWeightedSequence( ACT_CROUCH ); SetActivity( ACT_CROUCH ); } else { animDesired = SelectWeightedSequence( ACT_RUN ); SetActivity( ACT_RUN ); } } else { if ( GetActivity() == idealActivity) return; SetActivity( idealActivity ); animDesired = SelectWeightedSequence( GetActivity() ); // Already using the desired animation? if (GetSequence() == animDesired) return; m_iRealSequence = animDesired; ResetSequence( animDesired ); SetCycle( 0 ); return; } // Already using the desired animation? if (GetSequence() == animDesired) return; m_iRealSequence = animDesired; //Msg( "Set animation to %d\n", animDesired ); // Reset to first frame of desired animation ResetSequence( animDesired ); SetCycle( 0 ); } static ConVar sv_debugweaponpickup( "sv_debugweaponpickup", "0", FCVAR_CHEAT, "Prints descriptive reasons as to why pickup did not work." ); // correct respawning of weapons bool CHL1MP_Player::BumpWeapon( CBaseCombatWeapon *pWeapon ) { CBaseCombatCharacter *pOwner = pWeapon->GetOwner(); // Can I have this weapon type? if ( !IsAllowedToPickupWeapons() ) { if ( sv_debugweaponpickup.GetBool() ) Msg("sv_debugweaponpickup: IsAllowedToPickupWeapons() returned false\n"); return false; } if ( pOwner || !Weapon_CanUse( pWeapon ) || !g_pGameRules->CanHavePlayerItem( this, pWeapon ) ) { if ( sv_debugweaponpickup.GetBool() && pOwner ) Msg("sv_debugweaponpickup: pOwner\n"); if ( sv_debugweaponpickup.GetBool() && !Weapon_CanUse( pWeapon ) ) Msg("sv_debugweaponpickup: Can't use weapon\n"); if ( sv_debugweaponpickup.GetBool() && !g_pGameRules->CanHavePlayerItem( this, pWeapon ) ) Msg("sv_debugweaponpickup: Gamerules says player can't have item\n"); if ( gEvilImpulse101 ) { UTIL_Remove( pWeapon ); } return false; } // Don't let the player fetch weapons through walls (use MASK_SOLID so that you can't pickup through windows) if( !pWeapon->FVisible( this, MASK_SOLID ) && !(GetFlags() & FL_NOTARGET) ) { if ( sv_debugweaponpickup.GetBool() && !FVisible( this, MASK_SOLID ) ) Msg("sv_debugweaponpickup: Can't fetch weapon through a wall\n"); if ( sv_debugweaponpickup.GetBool() && !(GetFlags() & FL_NOTARGET) ) Msg("sv_debugweaponpickup: NoTarget\n"); return false; } bool bOwnsWeaponAlready = !!Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType()); if ( bOwnsWeaponAlready == true ) { //If we have room for the ammo, then "take" the weapon too. if ( Weapon_EquipAmmoOnly( pWeapon ) ) { pWeapon->CheckRespawn(); UTIL_Remove( pWeapon ); if ( sv_debugweaponpickup.GetBool() ) Msg("sv_debugweaponpickup: Picking up weapon\n"); return true; } else { if ( sv_debugweaponpickup.GetBool() ) Msg("sv_debugweaponpickup: Owns weapon already\n"); return false; } } pWeapon->CheckRespawn(); Weapon_Equip( pWeapon ); if ( sv_debugweaponpickup.GetBool() ) Msg("sv_debugweaponpickup: Picking up weapon\n"); return true; } void CHL1MP_Player::ChangeTeam( int iTeamNum ) { bool bKill = false; if ( g_pGameRules->IsTeamplay() == true ) { if ( iTeamNum != GetTeamNumber() && GetTeamNumber() != TEAM_UNASSIGNED ) { bKill = true; } } BaseClass::ChangeTeam( iTeamNum ); m_flNextTeamChangeTime = gpGlobals->curtime + 5; if ( g_pGameRules->IsTeamplay() == true ) { SetPlayerTeamModel(); } else { SetPlayerModel(); } if ( bKill == true ) { CommitSuicide(); } } void CHL1MP_Player::SetPlayerTeamModel( void ) { int iTeamNum = GetTeamNumber(); if ( iTeamNum <= TEAM_SPECTATOR ) return; CTeam * pTeam = GetGlobalTeam( iTeamNum ); char szModelName[256]; Q_snprintf( szModelName, 256, "%s%s/%s.mdl", s_szModelPath, pTeam->GetName(), pTeam->GetName() ); // Check to see if the model was properly precached, do not error out if not. int i = modelinfo->GetModelIndex( szModelName ); if ( i == -1 ) { Warning("Model %s does not exist.\n", szModelName ); return; } SetModel( szModelName ); m_flNextModelChangeTime = gpGlobals->curtime + 5; } void CHL1MP_Player::SetPlayerModel( void ) { char szBaseName[128]; Q_FileBase( engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_playermodel" ), szBaseName, 128 ); // Don't let it be 'none'; default to Barney if ( Q_stricmp( "none", szBaseName ) == 0 ) { Q_strcpy( szBaseName, "gordon" ); } char szModelName[256]; Q_snprintf( szModelName, 256, "%s%s/%s.mdl", s_szModelPath, szBaseName, szBaseName ); // Check to see if the model was properly precached, do not error out if not. int i = modelinfo->GetModelIndex( szModelName ); if ( i == -1 ) { SetModel( "models/player/mp/gordon/gordon.mdl" ); engine->ClientCommand ( edict(), "cl_playermodel models/gordon.mdl\n" ); return; } SetModel( szModelName ); m_flNextModelChangeTime = gpGlobals->curtime + 5; } // -------------------------------------------------------------------------------- // // Ragdoll entities. // -------------------------------------------------------------------------------- // class CHL1MPRagdoll : public CBaseAnimatingOverlay { public: DECLARE_CLASS( CHL1MPRagdoll, CBaseAnimatingOverlay ); DECLARE_SERVERCLASS(); // Transmit ragdolls to everyone. virtual int UpdateTransmitState() { return SetTransmitState( FL_EDICT_ALWAYS ); } public: // In case the client has the player entity, we transmit the player index. // In case the client doesn't have it, we transmit the player's model index, origin, and angles // so they can create a ragdoll in the right place. CNetworkHandle( CBaseEntity, m_hPlayer ); // networked entity handle CNetworkVector( m_vecRagdollVelocity ); CNetworkVector( m_vecRagdollOrigin ); }; LINK_ENTITY_TO_CLASS( hl1mp_ragdoll, CHL1MPRagdoll ); IMPLEMENT_SERVERCLASS_ST_NOBASE( CHL1MPRagdoll, DT_HL1MPRagdoll ) SendPropVector ( SENDINFO( m_vecRagdollOrigin), -1, SPROP_COORD ), SendPropEHandle ( SENDINFO( m_hPlayer ) ), SendPropModelIndex( SENDINFO( m_nModelIndex ) ), SendPropInt ( SENDINFO( m_nForceBone), 8, 0 ), SendPropVector ( SENDINFO( m_vecForce), -1, SPROP_NOSCALE ), SendPropVector ( SENDINFO( m_vecRagdollVelocity ) ) END_SEND_TABLE() void CHL1MP_Player::CreateRagdollEntity( void ) { if ( m_hRagdoll ) { UTIL_RemoveImmediate( m_hRagdoll ); m_hRagdoll = NULL; } // If we already have a ragdoll, don't make another one. CHL1MPRagdoll *pRagdoll = dynamic_cast< CHL1MPRagdoll* >(m_hRagdoll.Get() ); if ( !pRagdoll ) { // Create a new one pRagdoll = dynamic_cast< CHL1MPRagdoll* >( CreateEntityByName( "hl1mp_ragdoll" ) ); } if ( pRagdoll ) { pRagdoll->m_hPlayer = this; pRagdoll->m_vecRagdollOrigin = GetAbsOrigin(); pRagdoll->m_vecRagdollVelocity = GetAbsVelocity(); pRagdoll->m_nModelIndex = m_nModelIndex; pRagdoll->m_nForceBone = m_nForceBone; //pRagdoll->m_vecForce = m_vecTotalBulletForce; pRagdoll->SetAbsOrigin( GetAbsOrigin() ); } m_hRagdoll = pRagdoll; } void CHL1MP_Player::CreateCorpse( void ) { }