From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/server/CommentarySystem.cpp | 3302 +++++++++++++++---------------- 1 file changed, 1651 insertions(+), 1651 deletions(-) (limited to 'mp/src/game/server/CommentarySystem.cpp') diff --git a/mp/src/game/server/CommentarySystem.cpp b/mp/src/game/server/CommentarySystem.cpp index fd160b4d..f846a029 100644 --- a/mp/src/game/server/CommentarySystem.cpp +++ b/mp/src/game/server/CommentarySystem.cpp @@ -1,1651 +1,1651 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: The system for handling director's commentary style production info in-game. -// -// $NoKeywords: $ -//=============================================================================// - -#include "cbase.h" - -#ifndef _XBOX -#include "tier0/icommandline.h" -#include "igamesystem.h" -#include "filesystem.h" -#include -#include "in_buttons.h" -#include "engine/IEngineSound.h" -#include "soundenvelope.h" -#include "utldict.h" -#include "isaverestore.h" -#include "eventqueue.h" -#include "saverestore_utlvector.h" -#include "gamestats.h" -#include "ai_basenpc.h" -#include "Sprite.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -static bool g_bTracingVsCommentaryNodes = false; -static const char *s_pCommentaryUpdateViewThink = "CommentaryUpdateViewThink"; - -#define COMMENTARY_SPAWNED_SEMAPHORE "commentary_semaphore" - -extern ConVar commentary; -ConVar commentary_available("commentary_available", "0", FCVAR_NONE, "Automatically set by the game when a commentary file is available for the current map." ); - -enum teleport_stages_t -{ - TELEPORT_NONE, - TELEPORT_FADEOUT, - TELEPORT_TELEPORT, - TELEPORT_FADEIN, -}; - -// Convar restoration save/restore -#define MAX_MODIFIED_CONVAR_STRING 128 -struct modifiedconvars_t -{ - DECLARE_SIMPLE_DATADESC(); - - char pszConvar[MAX_MODIFIED_CONVAR_STRING]; - char pszCurrentValue[MAX_MODIFIED_CONVAR_STRING]; - char pszOrgValue[MAX_MODIFIED_CONVAR_STRING]; -}; - -bool g_bInCommentaryMode = false; -bool IsInCommentaryMode( void ) -{ - return g_bInCommentaryMode; -} - -//----------------------------------------------------------------------------- -// Purpose: An entity that marks a spot for a piece of commentary -//----------------------------------------------------------------------------- -class CPointCommentaryNode : public CBaseAnimating -{ - DECLARE_CLASS( CPointCommentaryNode, CBaseAnimating ); -public: - DECLARE_DATADESC(); - DECLARE_SERVERCLASS(); - - void Spawn( void ); - void Precache( void ); - void Activate( void ); - void SpinThink( void ); - void StartCommentary( void ); - void FinishCommentary( bool bBlendOut = true ); - void CleanupPostCommentary( void ); - void UpdateViewThink( void ); - void UpdateViewPostThink( void ); - bool TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace ); - bool HasViewTarget( void ) { return (m_hViewTarget != NULL || m_hViewPosition.Get() != NULL); } - bool PreventsMovement( void ); - bool CannotBeStopped( void ) { return (m_bUnstoppable || m_bPreventChangesWhileMoving); } - int UpdateTransmitState( void ); - void SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways ); - void SetDisabled( bool bDisabled ); - void SetNodeNumber( int iCount ) { m_iNodeNumber = iCount; } - - // Called to tell the node when it's moved under/not-under the player's crosshair - void SetUnderCrosshair( bool bUnderCrosshair ); - - // Called when the player attempts to activate the node - void PlayerActivated( void ); - void StopPlaying( void ); - void AbortPlaying( void ); - void TeleportTo( CBasePlayer *pPlayer ); - bool CanTeleportTo( void ); - - // Inputs - void InputStartCommentary( inputdata_t &inputdata ); - void InputStartUnstoppableCommentary( inputdata_t &inputdata ); - void InputEnable( inputdata_t &inputdata ); - void InputDisable( inputdata_t &inputdata ); - -private: - string_t m_iszPreCommands; - string_t m_iszPostCommands; - CNetworkVar( string_t, m_iszCommentaryFile ); - CNetworkVar( string_t, m_iszCommentaryFileNoHDR ); - string_t m_iszViewTarget; - EHANDLE m_hViewTarget; - EHANDLE m_hViewTargetAngles; // Entity used to blend view angles to look at the target - string_t m_iszViewPosition; - CNetworkVar( EHANDLE, m_hViewPosition ); - EHANDLE m_hViewPositionMover; // Entity used to blend the view to the viewposition entity - bool m_bPreventMovement; - bool m_bUnderCrosshair; - bool m_bUnstoppable; - float m_flFinishedTime; - Vector m_vecFinishOrigin; - QAngle m_vecOriginalAngles; - QAngle m_vecFinishAngles; - bool m_bPreventChangesWhileMoving; - bool m_bDisabled; - Vector m_vecTeleportOrigin; - - COutputEvent m_pOnCommentaryStarted; - COutputEvent m_pOnCommentaryStopped; - - CNetworkVar( bool, m_bActive ); - CNetworkVar( float, m_flStartTime ); - CNetworkVar( string_t, m_iszSpeakers ); - CNetworkVar( int, m_iNodeNumber ); - CNetworkVar( int, m_iNodeNumberMax ); -}; - -BEGIN_DATADESC( CPointCommentaryNode ) - DEFINE_KEYFIELD( m_iszPreCommands, FIELD_STRING, "precommands" ), - DEFINE_KEYFIELD( m_iszPostCommands, FIELD_STRING, "postcommands" ), - DEFINE_KEYFIELD( m_iszCommentaryFile, FIELD_STRING, "commentaryfile" ), - DEFINE_KEYFIELD( m_iszCommentaryFileNoHDR, FIELD_STRING, "commentaryfile_nohdr" ), - DEFINE_KEYFIELD( m_iszViewTarget, FIELD_STRING, "viewtarget" ), - DEFINE_FIELD( m_hViewTarget, FIELD_EHANDLE ), - DEFINE_FIELD( m_hViewTargetAngles, FIELD_EHANDLE ), - DEFINE_KEYFIELD( m_iszViewPosition, FIELD_STRING, "viewposition" ), - DEFINE_FIELD( m_hViewPosition, FIELD_EHANDLE ), - DEFINE_FIELD( m_hViewPositionMover, FIELD_EHANDLE ), - DEFINE_KEYFIELD( m_bPreventMovement, FIELD_BOOLEAN, "prevent_movement" ), - DEFINE_FIELD( m_bUnderCrosshair, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bUnstoppable, FIELD_BOOLEAN ), - DEFINE_FIELD( m_flFinishedTime, FIELD_TIME ), - DEFINE_FIELD( m_vecFinishOrigin, FIELD_VECTOR ), - DEFINE_FIELD( m_vecOriginalAngles, FIELD_VECTOR ), - DEFINE_FIELD( m_vecFinishAngles, FIELD_VECTOR ), - DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ), - DEFINE_FIELD( m_flStartTime, FIELD_TIME ), - DEFINE_KEYFIELD( m_iszSpeakers, FIELD_STRING, "speakers" ), - DEFINE_FIELD( m_iNodeNumber, FIELD_INTEGER ), - DEFINE_FIELD( m_iNodeNumberMax, FIELD_INTEGER ), - DEFINE_FIELD( m_bPreventChangesWhileMoving, FIELD_BOOLEAN ), - DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "start_disabled" ), - DEFINE_KEYFIELD( m_vecTeleportOrigin, FIELD_VECTOR, "teleport_origin" ), - - // Outputs - DEFINE_OUTPUT( m_pOnCommentaryStarted, "OnCommentaryStarted" ), - DEFINE_OUTPUT( m_pOnCommentaryStopped, "OnCommentaryStopped" ), - - // Inputs - DEFINE_INPUTFUNC( FIELD_VOID, "StartCommentary", InputStartCommentary ), - DEFINE_INPUTFUNC( FIELD_VOID, "StartUnstoppableCommentary", InputStartUnstoppableCommentary ), - DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), - DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), - - // Functions - DEFINE_THINKFUNC( SpinThink ), - DEFINE_THINKFUNC( UpdateViewThink ), - DEFINE_THINKFUNC( UpdateViewPostThink ), -END_DATADESC() - -IMPLEMENT_SERVERCLASS_ST( CPointCommentaryNode, DT_PointCommentaryNode ) - SendPropBool( SENDINFO(m_bActive) ), - SendPropStringT( SENDINFO(m_iszCommentaryFile) ), - SendPropStringT( SENDINFO(m_iszCommentaryFileNoHDR) ), - SendPropTime( SENDINFO(m_flStartTime) ), - SendPropStringT( SENDINFO(m_iszSpeakers) ), - SendPropInt( SENDINFO(m_iNodeNumber), 8, SPROP_UNSIGNED ), - SendPropInt( SENDINFO(m_iNodeNumberMax), 8, SPROP_UNSIGNED ), - SendPropEHandle( SENDINFO(m_hViewPosition) ), -END_SEND_TABLE() - -LINK_ENTITY_TO_CLASS( point_commentary_node, CPointCommentaryNode ); - -//----------------------------------------------------------------------------- -// Laser Dot -//----------------------------------------------------------------------------- -class CCommentaryViewPosition : public CSprite -{ - DECLARE_CLASS( CCommentaryViewPosition, CSprite ); -public: - virtual void Spawn( void ) - { - Precache(); - SetModelName( MAKE_STRING("sprites/redglow1.vmt") ); - - BaseClass::Spawn(); - - SetMoveType( MOVETYPE_NONE ); - AddSolidFlags( FSOLID_NOT_SOLID ); - AddEffects( EF_NOSHADOW ); - UTIL_SetSize( this, vec3_origin, vec3_origin ); - } - - virtual void Precache( void ) - { - PrecacheModel( "sprites/redglow1.vmt" ); - } -}; - -LINK_ENTITY_TO_CLASS( point_commentary_viewpoint, CCommentaryViewPosition ); - -//----------------------------------------------------------------------------- -// Purpose: In multiplayer, always return player 1 -//----------------------------------------------------------------------------- -CBasePlayer *GetCommentaryPlayer( void ) -{ - CBasePlayer *pPlayer; - - if ( gpGlobals->maxClients <= 1 ) - { - pPlayer = UTIL_GetLocalPlayer(); - } - else - { - // only respond to the first player - pPlayer = UTIL_PlayerByIndex(1); - } - - return pPlayer; -} - -//=========================================================================================================== -// COMMENTARY GAME SYSTEM -//=========================================================================================================== -void CV_GlobalChange_Commentary( IConVar *var, const char *pOldString, float flOldValue ); - -//----------------------------------------------------------------------------- -// Purpose: Game system to kickstart the director's commentary -//----------------------------------------------------------------------------- -class CCommentarySystem : public CAutoGameSystemPerFrame -{ -public: - DECLARE_DATADESC(); - - CCommentarySystem() : CAutoGameSystemPerFrame( "CCommentarySystem" ) - { - m_iCommentaryNodeCount = 0; - } - - virtual void LevelInitPreEntity() - { - m_hCurrentNode = NULL; - m_bCommentaryConvarsChanging = false; - m_iClearPressedButtons = 0; - - // If the map started via the map_commentary cmd, start in commentary - g_bInCommentaryMode = (engine->IsInCommentaryMode() != 0); - - CalculateCommentaryState(); - } - - void CalculateCommentaryState( void ) - { - // Set the available cvar if we can find commentary data for this level - char szFullName[512]; - Q_snprintf(szFullName,sizeof(szFullName), "maps/%s_commentary.txt", STRING( gpGlobals->mapname) ); - if ( filesystem->FileExists( szFullName ) ) - { - commentary_available.SetValue( true ); - - // If the user wanted commentary, kick it on - if ( commentary.GetBool() ) - { - g_bInCommentaryMode = true; - } - } - else - { - g_bInCommentaryMode = false; - commentary_available.SetValue( false ); - } - } - - virtual void LevelShutdownPreEntity() - { - ShutDownCommentary(); - } - - void ParseEntKVBlock( CBaseEntity *pNode, KeyValues *pkvNode ) - { - KeyValues *pkvNodeData = pkvNode->GetFirstSubKey(); - while ( pkvNodeData ) - { - // Handle the connections block - if ( !Q_strcmp(pkvNodeData->GetName(), "connections") ) - { - ParseEntKVBlock( pNode, pkvNodeData ); - } - else - { - #define COMMENTARY_STRING_LENGTH_MAX 1024 - - const char *pszValue = pkvNodeData->GetString(); - Assert( Q_strlen(pszValue) < COMMENTARY_STRING_LENGTH_MAX ); - if ( Q_strnchr(pszValue, '^', COMMENTARY_STRING_LENGTH_MAX) ) - { - // We want to support quotes in our strings so that we can specify multiple parameters in - // an output inside our commentary files. We convert ^s to "s here. - char szTmp[COMMENTARY_STRING_LENGTH_MAX]; - Q_strncpy( szTmp, pszValue, COMMENTARY_STRING_LENGTH_MAX ); - int len = Q_strlen( szTmp ); - for ( int i = 0; i < len; i++ ) - { - if ( szTmp[i] == '^' ) - { - szTmp[i] = '"'; - } - } - - pNode->KeyValue( pkvNodeData->GetName(), szTmp ); - } - else - { - pNode->KeyValue( pkvNodeData->GetName(), pszValue ); - } - } - - pkvNodeData = pkvNodeData->GetNextKey(); - } - } - - virtual void LevelInitPostEntity( void ) - { - if ( !IsInCommentaryMode() ) - return; - - // Don't spawn commentary entities when loading a savegame - if ( gpGlobals->eLoadType == MapLoad_LoadGame || gpGlobals->eLoadType == MapLoad_Background ) - return; - - m_bCommentaryEnabledMidGame = false; - InitCommentary(); - - IGameEvent *event = gameeventmanager->CreateEvent( "playing_commentary" ); - gameeventmanager->FireEventClientSide( event ); - } - - CPointCommentaryNode *GetNodeUnderCrosshair() - { - CBasePlayer *pPlayer = GetCommentaryPlayer(); - if ( !pPlayer ) - return NULL; - - // See if the player's looking at a commentary node - trace_t tr; - Vector vecSrc = pPlayer->EyePosition(); - Vector vecForward = pPlayer->GetAutoaimVector( AUTOAIM_SCALE_DIRECT_ONLY ); - - g_bTracingVsCommentaryNodes = true; - UTIL_TraceLine( vecSrc, vecSrc + vecForward * MAX_TRACE_LENGTH, MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr ); - g_bTracingVsCommentaryNodes = false; - - if ( !tr.m_pEnt ) - return NULL; - - return dynamic_cast(tr.m_pEnt); - } - - void PrePlayerRunCommand( CBasePlayer *pPlayer, CUserCmd *pUserCmds ) - { - if ( !IsInCommentaryMode() ) - return; - - if ( pPlayer->IsFakeClient() ) - return; - - CPointCommentaryNode *pCurrentNode = GetNodeUnderCrosshair(); - - // Changed nodes? - if ( m_hCurrentNode != pCurrentNode ) - { - // Stop animating the old one - if ( m_hCurrentNode.Get() ) - { - m_hCurrentNode->SetUnderCrosshair( false ); - } - - // Start animating the new one - if ( pCurrentNode ) - { - pCurrentNode->SetUnderCrosshair( true ); - } - - m_hCurrentNode = pCurrentNode; - } - - // Check for commentary node activations - if ( pPlayer ) - { - // Has the player pressed down an attack button? - int buttonsChanged = m_afPlayersLastButtons ^ pUserCmds->buttons; - int buttonsPressed = buttonsChanged & pUserCmds->buttons; - m_afPlayersLastButtons = pUserCmds->buttons; - - if ( !(pUserCmds->buttons & COMMENTARY_BUTTONS) ) - { - m_iClearPressedButtons &= ~COMMENTARY_BUTTONS; - } - - // Detect press events to start/stop commentary nodes - if (buttonsPressed & COMMENTARY_BUTTONS) - { - if ( buttonsPressed & IN_ATTACK2 ) - { - if ( !(GetActiveNode() && GetActiveNode()->CannotBeStopped()) ) - { - JumpToNextNode( pPlayer ); - pUserCmds->buttons &= ~COMMENTARY_BUTTONS; - m_iClearPressedButtons |= (buttonsPressed & COMMENTARY_BUTTONS); - } - } - else - { - // Looking at a node? - if ( m_hCurrentNode ) - { - // Ignore input while an unstoppable node is playing - if ( !GetActiveNode() || !GetActiveNode()->CannotBeStopped() ) - { - // If we have an active node already, stop it - if ( GetActiveNode() && GetActiveNode() != m_hCurrentNode ) - { - GetActiveNode()->StopPlaying(); - } - - m_hCurrentNode->PlayerActivated(); - } - - // Prevent weapon firing when toggling nodes - pUserCmds->buttons &= ~COMMENTARY_BUTTONS; - m_iClearPressedButtons |= (buttonsPressed & COMMENTARY_BUTTONS); - } - else if ( GetActiveNode() && GetActiveNode()->HasViewTarget() ) - { - if ( !GetActiveNode()->CannotBeStopped() ) - { - GetActiveNode()->StopPlaying(); - } - - // Prevent weapon firing when toggling nodes - pUserCmds->buttons &= ~COMMENTARY_BUTTONS; - m_iClearPressedButtons |= (buttonsPressed & COMMENTARY_BUTTONS); - } - } - } - - if ( GetActiveNode() && GetActiveNode()->PreventsMovement() ) - { - pUserCmds->buttons &= ~(IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT | IN_JUMP | IN_DUCK ); - pUserCmds->upmove = 0; - pUserCmds->sidemove = 0; - pUserCmds->forwardmove = 0; - } - - // When we swallow button down events, we have to keep clearing that button - // until the player releases the button. Otherwise, the frame after we swallow - // it, the code detects the button down and goes ahead as normal. - pUserCmds->buttons &= ~m_iClearPressedButtons; - } - - if ( m_iTeleportStage != TELEPORT_NONE ) - { - if ( m_flNextTeleportTime <= gpGlobals->curtime ) - { - if ( m_iTeleportStage == TELEPORT_FADEOUT ) - { - m_iTeleportStage = TELEPORT_TELEPORT; - m_flNextTeleportTime = gpGlobals->curtime + 0.35; - - color32_s clr = { 0,0,0,255 }; - UTIL_ScreenFade( pPlayer, clr, 0.3, 0, FFADE_OUT | FFADE_PURGE | FFADE_STAYOUT ); - } - else if ( m_iTeleportStage == TELEPORT_TELEPORT ) - { - if ( m_hLastCommentaryNode ) - { - m_hLastCommentaryNode->TeleportTo( pPlayer ); - } - - m_iTeleportStage = TELEPORT_FADEIN; - m_flNextTeleportTime = gpGlobals->curtime + 0.6; - } - else if ( m_iTeleportStage == TELEPORT_FADEIN ) - { - m_iTeleportStage = TELEPORT_NONE; - m_flNextTeleportTime = gpGlobals->curtime + 0.25; - - color32_s clr = { 0,0,0,255 }; - UTIL_ScreenFade( pPlayer, clr, 0.3, 0, FFADE_IN | FFADE_PURGE ); - } - } - } - } - - CPointCommentaryNode *GetActiveNode( void ) - { - return m_hActiveCommentaryNode; - } - - void SetActiveNode( CPointCommentaryNode *pNode ) - { - m_hActiveCommentaryNode = pNode; - if ( pNode ) - { - m_hLastCommentaryNode = pNode; - } - } - - int GetCommentaryNodeCount( void ) - { - return m_iCommentaryNodeCount; - } - - bool CommentaryConvarsChanging( void ) - { - return m_bCommentaryConvarsChanging; - } - - void SetCommentaryConvarsChanging( bool bChanging ) - { - m_bCommentaryConvarsChanging = bChanging; - } - - void ConvarChanged( IConVar *pConVar, const char *pOldString, float flOldValue ) - { - ConVarRef var( pConVar ); - - // A convar has been changed by a commentary node. We need to store - // the old state. If the engine shuts down, we need to restore any - // convars that the commentary changed to their previous values. - for ( int i = 0; i < m_ModifiedConvars.Count(); i++ ) - { - // If we find it, just update the current value - if ( !Q_strncmp( var.GetName(), m_ModifiedConvars[i].pszConvar, MAX_MODIFIED_CONVAR_STRING ) ) - { - Q_strncpy( m_ModifiedConvars[i].pszCurrentValue, var.GetString(), MAX_MODIFIED_CONVAR_STRING ); - //Msg(" Updating Convar %s: value %s (org %s)\n", m_ModifiedConvars[i].pszConvar, m_ModifiedConvars[i].pszCurrentValue, m_ModifiedConvars[i].pszOrgValue ); - return; - } - } - - // We didn't find it in our list, so add it - modifiedconvars_t newConvar; - Q_strncpy( newConvar.pszConvar, var.GetName(), MAX_MODIFIED_CONVAR_STRING ); - Q_strncpy( newConvar.pszCurrentValue, var.GetString(), MAX_MODIFIED_CONVAR_STRING ); - Q_strncpy( newConvar.pszOrgValue, pOldString, MAX_MODIFIED_CONVAR_STRING ); - m_ModifiedConvars.AddToTail( newConvar ); - - /* - Msg(" Commentary changed '%s' to '%s' (was '%s')\n", var->GetName(), var->GetString(), pOldString ); - Msg(" Convars stored: %d\n", m_ModifiedConvars.Count() ); - for ( int i = 0; i < m_ModifiedConvars.Count(); i++ ) - { - Msg(" Convar %d: %s, value %s (org %s)\n", i, m_ModifiedConvars[i].pszConvar, m_ModifiedConvars[i].pszCurrentValue, m_ModifiedConvars[i].pszOrgValue ); - } - */ - } - - void InitCommentary( void ) - { - // Install the global cvar callback - cvar->InstallGlobalChangeCallback( CV_GlobalChange_Commentary ); - - m_flNextTeleportTime = 0; - m_iTeleportStage = TELEPORT_NONE; - m_hLastCommentaryNode = NULL; - - // If we find the commentary semaphore, the commentary entities already exist. - // This occurs when you transition back to a map that has saved commentary nodes in it. - if ( gEntList.FindEntityByName( NULL, COMMENTARY_SPAWNED_SEMAPHORE ) ) - return; - - // Spawn the commentary semaphore entity - CBaseEntity *pSemaphore = CreateEntityByName( "info_target" ); - pSemaphore->SetName( MAKE_STRING(COMMENTARY_SPAWNED_SEMAPHORE) ); - - bool oldLock = engine->LockNetworkStringTables( false ); - - // Find the commentary file - char szFullName[512]; - Q_snprintf(szFullName,sizeof(szFullName), "maps/%s_commentary.txt", STRING( gpGlobals->mapname )); - KeyValues *pkvFile = new KeyValues( "Commentary" ); - if ( pkvFile->LoadFromFile( filesystem, szFullName, "MOD" ) ) - { - Msg( "Commentary: Loading commentary data from %s. \n", szFullName ); - - // Load each commentary block, and spawn the entities - KeyValues *pkvNode = pkvFile->GetFirstSubKey(); - while ( pkvNode ) - { - // Get node name - const char *pNodeName = pkvNode->GetName(); - - // Skip the trackinfo - if ( !Q_strncmp( pNodeName, "trackinfo", 9 ) ) - { - pkvNode = pkvNode->GetNextKey(); - continue; - } - - KeyValues *pClassname = pkvNode->FindKey( "classname" ); - if ( pClassname ) - { - // Use the classname instead - pNodeName = pClassname->GetString(); - } - - // Spawn the commentary entity - CBaseEntity *pNode = CreateEntityByName( pNodeName ); - if ( pNode ) - { - ParseEntKVBlock( pNode, pkvNode ); - DispatchSpawn( pNode ); - - EHANDLE hHandle; - hHandle = pNode; - m_hSpawnedEntities.AddToTail( hHandle ); - - CPointCommentaryNode *pCommNode = dynamic_cast(pNode); - if ( pCommNode ) - { - m_iCommentaryNodeCount++; - pCommNode->SetNodeNumber( m_iCommentaryNodeCount ); - } - } - else - { - Warning("Commentary: Failed to spawn commentary entity, type: '%s'\n", pNodeName ); - } - - // Move to next entity - pkvNode = pkvNode->GetNextKey(); - } - - // Then activate all the entities - for ( int i = 0; i < m_hSpawnedEntities.Count(); i++ ) - { - m_hSpawnedEntities[i]->Activate(); - } - } - else - { - Msg( "Commentary: Could not find commentary data file '%s'. \n", szFullName ); - } - - engine->LockNetworkStringTables( oldLock ); - } - - void ShutDownCommentary( void ) - { - if ( GetActiveNode() ) - { - GetActiveNode()->AbortPlaying(); - } - - // Destroy all the entities created by commentary - for ( int i = m_hSpawnedEntities.Count()-1; i >= 0; i-- ) - { - if ( m_hSpawnedEntities[i] ) - { - UTIL_Remove( m_hSpawnedEntities[i] ); - } - } - m_hSpawnedEntities.Purge(); - m_iCommentaryNodeCount = 0; - - // Remove the commentary semaphore - CBaseEntity *pSemaphore = gEntList.FindEntityByName( NULL, COMMENTARY_SPAWNED_SEMAPHORE ); - if ( pSemaphore ) - { - UTIL_Remove( pSemaphore ); - } - - // Remove our global convar callback - cvar->RemoveGlobalChangeCallback( CV_GlobalChange_Commentary ); - - // Reset any convars that have been changed by the commentary - for ( int i = 0; i < m_ModifiedConvars.Count(); i++ ) - { - ConVar *pConVar = (ConVar *)cvar->FindVar( m_ModifiedConvars[i].pszConvar ); - if ( pConVar ) - { - pConVar->SetValue( m_ModifiedConvars[i].pszOrgValue ); - } - } - m_ModifiedConvars.Purge(); - - m_hCurrentNode = NULL; - m_hActiveCommentaryNode = NULL; - m_hLastCommentaryNode = NULL; - m_flNextTeleportTime = 0; - m_iTeleportStage = TELEPORT_NONE; - } - - void SetCommentaryMode( bool bCommentaryMode ) - { - g_bInCommentaryMode = bCommentaryMode; - CalculateCommentaryState(); - - // If we're turning on commentary, create all the entities. - if ( IsInCommentaryMode() ) - { - m_bCommentaryEnabledMidGame = true; - InitCommentary(); - } - else - { - ShutDownCommentary(); - } - } - - void OnRestore( void ) - { - cvar->RemoveGlobalChangeCallback( CV_GlobalChange_Commentary ); - - if ( !IsInCommentaryMode() ) - return; - - // Set any convars that have already been changed by the commentary before the save - for ( int i = 0; i < m_ModifiedConvars.Count(); i++ ) - { - ConVar *pConVar = (ConVar *)cvar->FindVar( m_ModifiedConvars[i].pszConvar ); - if ( pConVar ) - { - //Msg(" Restoring Convar %s: value %s (org %s)\n", m_ModifiedConvars[i].pszConvar, m_ModifiedConvars[i].pszCurrentValue, m_ModifiedConvars[i].pszOrgValue ); - pConVar->SetValue( m_ModifiedConvars[i].pszCurrentValue ); - } - } - - // Install the global cvar callback - cvar->InstallGlobalChangeCallback( CV_GlobalChange_Commentary ); - } - - bool CommentaryWasEnabledMidGame( void ) - { - return m_bCommentaryEnabledMidGame; - } - - void JumpToNextNode( CBasePlayer *pPlayer ) - { - if ( m_flNextTeleportTime > gpGlobals->curtime || m_iTeleportStage != TELEPORT_NONE ) - return; - - CBaseEntity *pEnt = m_hLastCommentaryNode; - while ( ( pEnt = gEntList.FindEntityByClassname( pEnt, "point_commentary_node" ) ) != m_hLastCommentaryNode ) - { - CPointCommentaryNode *pNode = dynamic_cast( pEnt ); - if ( pNode && pNode->CanTeleportTo() ) - { - m_iTeleportStage = TELEPORT_FADEOUT; - m_hLastCommentaryNode = pNode; - m_flNextTeleportTime = gpGlobals->curtime; - - // Stop any active nodes - if ( m_hActiveCommentaryNode ) - { - m_hActiveCommentaryNode->StopPlaying(); - } - break; - } - } - } - -private: - int m_afPlayersLastButtons; - int m_iCommentaryNodeCount; - bool m_bCommentaryConvarsChanging; - int m_iClearPressedButtons; - bool m_bCommentaryEnabledMidGame; - float m_flNextTeleportTime; - int m_iTeleportStage; - - CUtlVector< modifiedconvars_t > m_ModifiedConvars; - CUtlVector m_hSpawnedEntities; - CHandle m_hCurrentNode; - CHandle m_hActiveCommentaryNode; - CHandle m_hLastCommentaryNode; -}; - -CCommentarySystem g_CommentarySystem; - -void CommentarySystem_PePlayerRunCommand( CBasePlayer *player, CUserCmd *ucmd ) -{ - g_CommentarySystem.PrePlayerRunCommand( player, ucmd ); -} - -BEGIN_DATADESC_NO_BASE( CCommentarySystem ) - //int m_afPlayersLastButtons; DON'T SAVE - //bool m_bCommentaryConvarsChanging; DON'T SAVE - //int m_iClearPressedButtons; DON'T SAVE - - DEFINE_FIELD( m_bCommentaryEnabledMidGame, FIELD_BOOLEAN ), - DEFINE_FIELD( m_flNextTeleportTime, FIELD_TIME ), - DEFINE_FIELD( m_iTeleportStage, FIELD_INTEGER ), - - DEFINE_UTLVECTOR( m_ModifiedConvars, FIELD_EMBEDDED ), - DEFINE_UTLVECTOR( m_hSpawnedEntities, FIELD_EHANDLE ), - DEFINE_FIELD( m_hCurrentNode, FIELD_EHANDLE ), - DEFINE_FIELD( m_hActiveCommentaryNode, FIELD_EHANDLE ), - DEFINE_FIELD( m_hLastCommentaryNode, FIELD_EHANDLE ), - DEFINE_FIELD( m_iCommentaryNodeCount, FIELD_INTEGER ), -END_DATADESC() - -BEGIN_SIMPLE_DATADESC( modifiedconvars_t ) - DEFINE_ARRAY( pszConvar, FIELD_CHARACTER, MAX_MODIFIED_CONVAR_STRING ), - DEFINE_ARRAY( pszCurrentValue, FIELD_CHARACTER, MAX_MODIFIED_CONVAR_STRING ), - DEFINE_ARRAY( pszOrgValue, FIELD_CHARACTER, MAX_MODIFIED_CONVAR_STRING ), -END_DATADESC() - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CC_CommentaryChanged( IConVar *pConVar, const char *pOldString, float flOldValue ) -{ - ConVarRef var( pConVar ); - if ( var.GetBool() != g_bInCommentaryMode ) - { - g_CommentarySystem.SetCommentaryMode( var.GetBool() ); - } -} -ConVar commentary("commentary", "0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "Desired commentary mode state.", CC_CommentaryChanged ); - -//----------------------------------------------------------------------------- -// Purpose: We need to revert back any convar changes that are made by the -// commentary system during commentary. This code stores convar changes -// made by the commentary system, and reverts them when finished. -//----------------------------------------------------------------------------- -void CV_GlobalChange_Commentary( IConVar *var, const char *pOldString, float flOldValue ) -{ - if ( !g_CommentarySystem.CommentaryConvarsChanging() ) - { - // A convar has changed, but not due to commentary nodes. Ignore it. - return; - } - - g_CommentarySystem.ConvarChanged( var, pOldString, flOldValue ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CC_CommentaryNotChanging( void ) -{ - g_CommentarySystem.SetCommentaryConvarsChanging( false ); -} -static ConCommand commentary_cvarsnotchanging("commentary_cvarsnotchanging", CC_CommentaryNotChanging, 0 ); - -bool IsListeningToCommentary( void ) -{ - return ( g_CommentarySystem.GetActiveNode() != NULL ); -} - -//=========================================================================================================== -// COMMENTARY NODES -//=========================================================================================================== - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPointCommentaryNode::Spawn( void ) -{ - // No model specified? - char *szModel = (char *)STRING( GetModelName() ); - if (!szModel || !*szModel) - { - szModel = "models/extras/info_speech.mdl"; - SetModelName( AllocPooledString(szModel) ); - } - - Precache(); - SetModel( szModel ); - UTIL_SetSize( this, -Vector(16,16,16), Vector(16,16,16) ); - SetSolid( SOLID_BBOX ); - AddSolidFlags( FSOLID_CUSTOMRAYTEST | FSOLID_CUSTOMBOXTEST ); - AddEffects( EF_NOSHADOW ); - - // Setup for animation - ResetSequence( LookupSequence("idle") ); - SetThink( &CPointCommentaryNode::SpinThink ); - SetNextThink( gpGlobals->curtime + 0.1f ); - - m_iNodeNumber = 0; - m_iNodeNumberMax = 0; - - SetDisabled( m_bDisabled ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPointCommentaryNode::Activate( void ) -{ - m_iNodeNumberMax = g_CommentarySystem.GetCommentaryNodeCount(); - - if ( m_iszViewTarget != NULL_STRING ) - { - m_hViewTarget = gEntList.FindEntityByName( NULL, m_iszViewTarget ); - if ( !m_hViewTarget ) - { - Warning("%s: %s could not find viewtarget %s.\n", GetClassname(), GetDebugName(), STRING(m_iszViewTarget) ); - } - } - - if ( m_iszViewPosition != NULL_STRING ) - { - m_hViewPosition = gEntList.FindEntityByName( NULL, m_iszViewPosition ); - if ( !m_hViewPosition.Get() ) - { - Warning("%s: %s could not find viewposition %s.\n", GetClassname(), GetDebugName(), STRING(m_iszViewPosition) ); - } - } - - BaseClass::Activate(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPointCommentaryNode::Precache() -{ - PrecacheModel( STRING( GetModelName() ) ); - - if ( m_iszCommentaryFile.Get() != NULL_STRING ) - { - PrecacheScriptSound( STRING( m_iszCommentaryFile.Get() ) ); - } - else - { - Warning("%s: %s has no commentary file.\n", GetClassname(), GetDebugName() ); - } - - if ( m_iszCommentaryFileNoHDR.Get() != NULL_STRING ) - { - PrecacheScriptSound( STRING( m_iszCommentaryFileNoHDR.Get() ) ); - } - - BaseClass::Precache(); -} - -//----------------------------------------------------------------------------- -// Purpose: Called to tell the node when it's moved under/not-under the player's crosshair -//----------------------------------------------------------------------------- -void CPointCommentaryNode::SetUnderCrosshair( bool bUnderCrosshair ) -{ - if ( bUnderCrosshair ) - { - // Start animating - m_bUnderCrosshair = true; - - if ( !m_bActive ) - { - m_flAnimTime = gpGlobals->curtime; - } - } - else - { - // Stop animating - m_bUnderCrosshair = false; - } -} - -//------------------------------------------------------------------------------ -// Purpose: Prevent collisions of everything except the trace from the commentary system -//------------------------------------------------------------------------------ -bool CPointCommentaryNode::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace ) -{ - if ( !g_bTracingVsCommentaryNodes ) - return false; - if ( m_bDisabled ) - return false; - - return BaseClass::TestCollision( ray, mask, trace ); -} - -//------------------------------------------------------------------------------ -// Purpose: -//------------------------------------------------------------------------------ -void CPointCommentaryNode::SpinThink( void ) -{ - // Rotate if we're active, or under the crosshair. Don't rotate if we're - // under the crosshair, but we've already been listened to. - if ( m_bActive || (m_bUnderCrosshair && m_nSkin == 0) ) - { - if ( m_bActive ) - { - m_flPlaybackRate = 3.0; - } - else - { - m_flPlaybackRate = 1.0; - } - StudioFrameAdvance(); - DispatchAnimEvents(this); - } - - SetNextThink( gpGlobals->curtime + 0.1f ); -} - -//------------------------------------------------------------------------------ -// Purpose: -//------------------------------------------------------------------------------ -void CPointCommentaryNode::PlayerActivated( void ) -{ - gamestats->Event_Commentary(); - - if ( m_bActive ) - { - StopPlaying(); - } - else - { - StartCommentary(); - g_CommentarySystem.SetActiveNode( this ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPointCommentaryNode::StopPlaying( void ) -{ - if ( m_bActive ) - { - FinishCommentary(); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Stop playing the node, but snap completely out of the node. -// Used when players shut down commentary while we're in the middle -// of playing a node, so we can't smoothly blend out (since the -// commentary entities need to be removed). -//----------------------------------------------------------------------------- -void CPointCommentaryNode::AbortPlaying( void ) -{ - if ( m_bActive ) - { - FinishCommentary( false ); - } - else if ( m_bPreventChangesWhileMoving ) - { - // We're a node that's not active, but is in the process of transitioning the view. Finish movement. - CleanupPostCommentary(); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CPointCommentaryNode::CanTeleportTo( void ) -{ - //return ( m_vecTeleportOrigin != vec3_origin ); - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPointCommentaryNode::TeleportTo( CBasePlayer *pPlayer ) -{ - Vector vecTarget = m_vecTeleportOrigin; - if ( m_vecTeleportOrigin == vec3_origin ) - { - vecTarget = GetAbsOrigin(); - } - - trace_t trace; - UTIL_TraceHull( vecTarget, vecTarget + Vector( 0, 0, -500 ), pPlayer->WorldAlignMins(), pPlayer->WorldAlignMaxs(), MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &trace ); - - pPlayer->Teleport( &trace.endpos, NULL, &vec3_origin ); - - Vector vecToNode = GetAbsOrigin() - pPlayer->EyePosition(); - VectorNormalize( vecToNode ); - QAngle vecAngle; - VectorAngles( vecToNode, Vector(0,0,1), vecAngle ); - - pPlayer->SnapEyeAngles( vecAngle ); -} - -//------------------------------------------------------------------------------ -// Purpose: -//------------------------------------------------------------------------------ -void CPointCommentaryNode::StartCommentary( void ) -{ - CBasePlayer *pPlayer = GetCommentaryPlayer(); - - if ( !pPlayer ) - return; - - m_bActive = true; - - m_flAnimTime = gpGlobals->curtime; - m_flPrevAnimTime = gpGlobals->curtime; - - // Switch to the greyed out skin - m_nSkin = 1; - - m_pOnCommentaryStarted.FireOutput( this, this ); - - // Fire off our precommands - if ( m_iszPreCommands != NULL_STRING ) - { - g_CommentarySystem.SetCommentaryConvarsChanging( true ); - engine->ClientCommand( pPlayer->edict(), STRING(m_iszPreCommands) ); - engine->ClientCommand( pPlayer->edict(), "commentary_cvarsnotchanging\n" ); - } - - // Start the commentary - m_flStartTime = gpGlobals->curtime; - - // If we have a view target, start blending towards it - if ( m_hViewTarget || m_hViewPosition.Get() ) - { - m_vecOriginalAngles = pPlayer->EyeAngles(); - SetContextThink( &CPointCommentaryNode::UpdateViewThink, gpGlobals->curtime, s_pCommentaryUpdateViewThink ); - } - - //SetContextThink( &CPointCommentaryNode::FinishCommentary, gpGlobals->curtime + flDuration, s_pFinishCommentaryThink ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CC_CommentaryFinishNode( void ) -{ - // We were told by the client DLL that our commentary has finished - if ( g_CommentarySystem.GetActiveNode() ) - { - g_CommentarySystem.GetActiveNode()->StopPlaying(); - } -} -static ConCommand commentary_finishnode("commentary_finishnode", CC_CommentaryFinishNode, 0 ); - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPointCommentaryNode::UpdateViewThink( void ) -{ - if ( !m_bActive ) - return; - CBasePlayer *pPlayer = GetCommentaryPlayer(); - if ( !pPlayer ) - return; - - // Swing the view towards the target - if ( m_hViewTarget ) - { - if ( !m_hViewTargetAngles && !m_hViewPositionMover ) - { - // Make an invisible entity to attach view angles to - m_hViewTargetAngles = CreateEntityByName( "point_commentary_viewpoint" ); - m_hViewTargetAngles->SetAbsOrigin( pPlayer->EyePosition() ); - m_hViewTargetAngles->SetAbsAngles( pPlayer->EyeAngles() ); - pPlayer->SetViewEntity( m_hViewTargetAngles ); - - if ( pPlayer->GetActiveWeapon() ) - { - pPlayer->GetActiveWeapon()->Holster(); - } - } - - QAngle angGoal; - QAngle angCurrent; - if ( m_hViewPositionMover ) - { - angCurrent = m_hViewPositionMover->GetAbsAngles(); - VectorAngles( m_hViewTarget->WorldSpaceCenter() - m_hViewPositionMover->GetAbsOrigin(), angGoal ); - } - else if ( m_hViewTargetAngles ) - { - angCurrent = m_hViewTargetAngles->GetAbsAngles(); - m_hViewTargetAngles->SetAbsOrigin( pPlayer->EyePosition() ); - VectorAngles( m_hViewTarget->WorldSpaceCenter() - m_hViewTargetAngles->GetAbsOrigin(), angGoal ); - } - else - { - angCurrent = pPlayer->EyeAngles(); - VectorAngles( m_hViewTarget->WorldSpaceCenter() - pPlayer->EyePosition(), angGoal ); - } - - // Accelerate towards the target goal angles - float dx = AngleDiff( angGoal.x, angCurrent.x ); - float dy = AngleDiff( angGoal.y, angCurrent.y ); - float mod = 1.0 - ExponentialDecay( 0.5, 0.3, gpGlobals->frametime ); - float dxmod = dx * mod; - float dymod = dy * mod; - - angCurrent.x = AngleNormalize( angCurrent.x + dxmod ); - angCurrent.y = AngleNormalize( angCurrent.y + dymod ); - - if ( m_hViewPositionMover ) - { - m_hViewPositionMover->SetAbsAngles( angCurrent ); - } - else if ( m_hViewTargetAngles ) - { - m_hViewTargetAngles->SetAbsAngles( angCurrent ); - pPlayer->SnapEyeAngles( angCurrent ); - } - else - { - pPlayer->SnapEyeAngles( angCurrent ); - } - - SetNextThink( gpGlobals->curtime, s_pCommentaryUpdateViewThink ); - } - - if ( m_hViewPosition.Get() ) - { - if ( pPlayer->GetActiveWeapon() ) - { - pPlayer->GetActiveWeapon()->Holster(); - } - - if ( !m_hViewPositionMover ) - { - // Make an invisible info target entity for us to attach the view to, - // and move it to the desired view position. - m_hViewPositionMover = CreateEntityByName( "point_commentary_viewpoint" ); - m_hViewPositionMover->SetAbsAngles( pPlayer->EyeAngles() ); - pPlayer->SetViewEntity( m_hViewPositionMover ); - } - - // Blend to the target position over time. - float flCurTime = (gpGlobals->curtime - m_flStartTime); - float flBlendPerc = clamp( flCurTime * 0.5f, 0.f, 1.f ); - - // Figure out the current view position - Vector vecCurEye; - VectorLerp( pPlayer->EyePosition(), m_hViewPosition.Get()->GetAbsOrigin(), flBlendPerc, vecCurEye ); - m_hViewPositionMover->SetAbsOrigin( vecCurEye ); - - SetNextThink( gpGlobals->curtime, s_pCommentaryUpdateViewThink ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPointCommentaryNode::UpdateViewPostThink( void ) -{ - CBasePlayer *pPlayer = GetCommentaryPlayer(); - if ( !pPlayer ) - return; - - if ( m_hViewPosition.Get() && m_hViewPositionMover ) - { - // Blend back to the player's position over time. - float flCurTime = (gpGlobals->curtime - m_flFinishedTime); - float flTimeToBlend = MIN( 2.0, m_flFinishedTime - m_flStartTime ); - float flBlendPerc = 1.0f - clamp( flCurTime / flTimeToBlend, 0.f, 1.f ); - - //Msg("OUT: CurTime %.2f, BlendTime: %.2f, Blend: %.3f\n", flCurTime, flTimeToBlend, flBlendPerc ); - - // Only do this while we're still moving - if ( flBlendPerc > 0 ) - { - // Figure out the current view position - Vector vecPlayerPos = pPlayer->EyePosition(); - Vector vecToPosition = (m_vecFinishOrigin - vecPlayerPos); - Vector vecCurEye = pPlayer->EyePosition() + (vecToPosition * flBlendPerc); - m_hViewPositionMover->SetAbsOrigin( vecCurEye ); - - if ( m_hViewTarget ) - { - Quaternion quatFinish; - Quaternion quatOriginal; - Quaternion quatCurrent; - AngleQuaternion( m_vecOriginalAngles, quatOriginal ); - AngleQuaternion( m_vecFinishAngles, quatFinish ); - QuaternionSlerp( quatFinish, quatOriginal, 1.0 - flBlendPerc, quatCurrent ); - QAngle angCurrent; - QuaternionAngles( quatCurrent, angCurrent ); - m_hViewPositionMover->SetAbsAngles( angCurrent ); - } - - SetNextThink( gpGlobals->curtime, s_pCommentaryUpdateViewThink ); - return; - } - - pPlayer->SnapEyeAngles( m_hViewPositionMover->GetAbsAngles() ); - } - - // We're done - CleanupPostCommentary(); - - m_bPreventChangesWhileMoving = false; -} - -//------------------------------------------------------------------------------ -// Purpose: -//------------------------------------------------------------------------------ -void CPointCommentaryNode::FinishCommentary( bool bBlendOut ) -{ - CBasePlayer *pPlayer = GetCommentaryPlayer(); - if ( !pPlayer ) - return; - - // Fire off our postcommands - if ( m_iszPostCommands != NULL_STRING ) - { - g_CommentarySystem.SetCommentaryConvarsChanging( true ); - engine->ClientCommand( pPlayer->edict(), STRING(m_iszPostCommands) ); - engine->ClientCommand( pPlayer->edict(), "commentary_cvarsnotchanging\n" ); - } - - // Stop the commentary - m_flFinishedTime = gpGlobals->curtime; - - if ( bBlendOut && m_hViewPositionMover ) - { - m_bActive = false; - m_flPlaybackRate = 1.0; - m_vecFinishOrigin = m_hViewPositionMover->GetAbsOrigin(); - m_vecFinishAngles = m_hViewPositionMover->GetAbsAngles(); - - m_bPreventChangesWhileMoving = true; - - // We've moved away from the player's position. Move back to it before ending - SetContextThink( &CPointCommentaryNode::UpdateViewPostThink, gpGlobals->curtime, s_pCommentaryUpdateViewThink ); - return; - } - - CleanupPostCommentary(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPointCommentaryNode::CleanupPostCommentary( void ) -{ - CBasePlayer *pPlayer = GetCommentaryPlayer(); - if ( !pPlayer ) - return; - - if ( ( m_hViewPositionMover || m_hViewTargetAngles ) && pPlayer->GetActiveWeapon() ) - { - pPlayer->GetActiveWeapon()->Deploy(); - } - - if ( m_hViewTargetAngles && pPlayer->GetViewEntity() == m_hViewTargetAngles ) - { - pPlayer->SetViewEntity( NULL ); - } - UTIL_Remove( m_hViewTargetAngles ); - - if ( m_hViewPositionMover && pPlayer->GetViewEntity() == m_hViewPositionMover ) - { - pPlayer->SetViewEntity( NULL ); - } - UTIL_Remove( m_hViewPositionMover ); - - m_bActive = false; - m_flPlaybackRate = 1.0; - m_bUnstoppable = false; - m_flFinishedTime = 0; - SetContextThink( NULL, 0, s_pCommentaryUpdateViewThink ); - - // Clear out any events waiting on our start commentary - g_EventQueue.CancelEvents( this ); - - m_pOnCommentaryStopped.FireOutput( this, this ); - - g_CommentarySystem.SetActiveNode( NULL ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPointCommentaryNode::InputStartCommentary( inputdata_t &inputdata ) -{ - if ( !m_bActive ) - { - if ( g_CommentarySystem.GetActiveNode() ) - { - g_CommentarySystem.GetActiveNode()->StopPlaying(); - } - - PlayerActivated(); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPointCommentaryNode::InputStartUnstoppableCommentary( inputdata_t &inputdata ) -{ - if ( !m_bActive ) - { - m_bUnstoppable = true; - InputStartCommentary( inputdata ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPointCommentaryNode::InputEnable( inputdata_t &inputdata ) -{ - SetDisabled( false ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPointCommentaryNode::InputDisable( inputdata_t &inputdata ) -{ - SetDisabled( true ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPointCommentaryNode::SetDisabled( bool bDisabled ) -{ - m_bDisabled = bDisabled; - - if ( m_bDisabled ) - { - AddEffects( EF_NODRAW ); - } - else - { - RemoveEffects( EF_NODRAW ); - } -} - -//----------------------------------------------------------------------------- -// Purpose Force our lighting landmark to be transmitted -//----------------------------------------------------------------------------- -int CPointCommentaryNode::UpdateTransmitState( void ) -{ - return SetTransmitState( FL_EDICT_ALWAYS ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CPointCommentaryNode::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways ) -{ - // Are we already marked for transmission? - if ( pInfo->m_pTransmitEdict->Get( entindex() ) ) - return; - - BaseClass::SetTransmit( pInfo, bAlways ); - - // Force our camera view position entity to be sent - if ( m_hViewTarget ) - { - m_hViewTarget->SetTransmit( pInfo, bAlways ); - } - if ( m_hViewTargetAngles ) - { - m_hViewTargetAngles->SetTransmit( pInfo, bAlways ); - } - if ( m_hViewPosition.Get() ) - { - m_hViewPosition.Get()->SetTransmit( pInfo, bAlways ); - } - if ( m_hViewPositionMover ) - { - m_hViewPositionMover->SetTransmit( pInfo, bAlways ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -bool CPointCommentaryNode::PreventsMovement( void ) -{ - // If we're moving the player's view at all, prevent movement - if ( m_hViewPosition.Get() ) - return true; - - return m_bPreventMovement; -} - -//----------------------------------------------------------------------------- -// COMMENTARY SAVE / RESTORE -//----------------------------------------------------------------------------- -static short COMMENTARY_SAVE_RESTORE_VERSION = 2; - -class CCommentary_SaveRestoreBlockHandler : public CDefSaveRestoreBlockHandler -{ -public: - const char *GetBlockName() - { - return "Commentary"; - } - - //--------------------------------- - - void Save( ISave *pSave ) - { - pSave->WriteBool( &g_bInCommentaryMode ); - if ( IsInCommentaryMode() ) - { - pSave->WriteAll( &g_CommentarySystem, g_CommentarySystem.GetDataDescMap() ); - pSave->WriteInt( &CAI_BaseNPC::m_nDebugBits ); - } - } - - //--------------------------------- - - void WriteSaveHeaders( ISave *pSave ) - { - pSave->WriteShort( &COMMENTARY_SAVE_RESTORE_VERSION ); - } - - //--------------------------------- - - void ReadRestoreHeaders( IRestore *pRestore ) - { - // No reason why any future version shouldn't try to retain backward compatability. The default here is to not do so. - short version; - pRestore->ReadShort( &version ); - m_fDoLoad = ( version == COMMENTARY_SAVE_RESTORE_VERSION ); - } - - //--------------------------------- - - void Restore( IRestore *pRestore, bool createPlayers ) - { - if ( m_fDoLoad ) - { - pRestore->ReadBool( &g_bInCommentaryMode ); - if ( g_bInCommentaryMode ) - { - pRestore->ReadAll( &g_CommentarySystem, g_CommentarySystem.GetDataDescMap() ); - CAI_BaseNPC::m_nDebugBits = pRestore->ReadInt(); - } - - // Force the commentary convar to match the saved game state - commentary.SetValue( g_bInCommentaryMode ); - } - } - -private: - bool m_fDoLoad; -}; - -//----------------------------------------------------------------------------- - -CCommentary_SaveRestoreBlockHandler g_Commentary_SaveRestoreBlockHandler; - -//------------------------------------- - -ISaveRestoreBlockHandler *GetCommentarySaveRestoreBlockHandler() -{ - return &g_Commentary_SaveRestoreBlockHandler; -} - -//----------------------------------------------------------------------------- -// Purpose: Commentary specific logic_auto replacement. -// Fires outputs based upon how commentary mode has been activated. -//----------------------------------------------------------------------------- -class CCommentaryAuto : public CBaseEntity -{ - DECLARE_CLASS( CCommentaryAuto, CBaseEntity ); -public: - DECLARE_DATADESC(); - - void Spawn(void); - void Think(void); - - void InputMultiplayerSpawned( inputdata_t &inputdata ); - -private: - // fired if commentary started due to new map - COutputEvent m_OnCommentaryNewGame; - - // fired if commentary was turned on in the middle of a map - COutputEvent m_OnCommentaryMidGame; - - // fired when the player spawns in a multiplayer game - COutputEvent m_OnCommentaryMultiplayerSpawn; -}; - -LINK_ENTITY_TO_CLASS(commentary_auto, CCommentaryAuto); - -BEGIN_DATADESC( CCommentaryAuto ) - // Inputs - DEFINE_INPUTFUNC( FIELD_VOID, "MultiplayerSpawned", InputMultiplayerSpawned ), - - // Outputs - DEFINE_OUTPUT(m_OnCommentaryNewGame, "OnCommentaryNewGame"), - DEFINE_OUTPUT(m_OnCommentaryMidGame, "OnCommentaryMidGame"), - DEFINE_OUTPUT(m_OnCommentaryMultiplayerSpawn, "OnCommentaryMultiplayerSpawn"), -END_DATADESC() - -//------------------------------------------------------------------------------ -// Purpose : Fire my outputs here if I fire on map reload -//------------------------------------------------------------------------------ -void CCommentaryAuto::Spawn(void) -{ - BaseClass::Spawn(); - SetNextThink( gpGlobals->curtime + 0.1 ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCommentaryAuto::Think(void) -{ - if ( g_CommentarySystem.CommentaryWasEnabledMidGame() ) - { - m_OnCommentaryMidGame.FireOutput(NULL, this); - } - else - { - m_OnCommentaryNewGame.FireOutput(NULL, this); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CCommentaryAuto::InputMultiplayerSpawned( inputdata_t &inputdata ) -{ - m_OnCommentaryMultiplayerSpawn.FireOutput( NULL, this ); -} - -#else - -bool IsInCommentaryMode( void ) -{ - return false; -} - -#endif +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: The system for handling director's commentary style production info in-game. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#ifndef _XBOX +#include "tier0/icommandline.h" +#include "igamesystem.h" +#include "filesystem.h" +#include +#include "in_buttons.h" +#include "engine/IEngineSound.h" +#include "soundenvelope.h" +#include "utldict.h" +#include "isaverestore.h" +#include "eventqueue.h" +#include "saverestore_utlvector.h" +#include "gamestats.h" +#include "ai_basenpc.h" +#include "Sprite.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static bool g_bTracingVsCommentaryNodes = false; +static const char *s_pCommentaryUpdateViewThink = "CommentaryUpdateViewThink"; + +#define COMMENTARY_SPAWNED_SEMAPHORE "commentary_semaphore" + +extern ConVar commentary; +ConVar commentary_available("commentary_available", "0", FCVAR_NONE, "Automatically set by the game when a commentary file is available for the current map." ); + +enum teleport_stages_t +{ + TELEPORT_NONE, + TELEPORT_FADEOUT, + TELEPORT_TELEPORT, + TELEPORT_FADEIN, +}; + +// Convar restoration save/restore +#define MAX_MODIFIED_CONVAR_STRING 128 +struct modifiedconvars_t +{ + DECLARE_SIMPLE_DATADESC(); + + char pszConvar[MAX_MODIFIED_CONVAR_STRING]; + char pszCurrentValue[MAX_MODIFIED_CONVAR_STRING]; + char pszOrgValue[MAX_MODIFIED_CONVAR_STRING]; +}; + +bool g_bInCommentaryMode = false; +bool IsInCommentaryMode( void ) +{ + return g_bInCommentaryMode; +} + +//----------------------------------------------------------------------------- +// Purpose: An entity that marks a spot for a piece of commentary +//----------------------------------------------------------------------------- +class CPointCommentaryNode : public CBaseAnimating +{ + DECLARE_CLASS( CPointCommentaryNode, CBaseAnimating ); +public: + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + void Spawn( void ); + void Precache( void ); + void Activate( void ); + void SpinThink( void ); + void StartCommentary( void ); + void FinishCommentary( bool bBlendOut = true ); + void CleanupPostCommentary( void ); + void UpdateViewThink( void ); + void UpdateViewPostThink( void ); + bool TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace ); + bool HasViewTarget( void ) { return (m_hViewTarget != NULL || m_hViewPosition.Get() != NULL); } + bool PreventsMovement( void ); + bool CannotBeStopped( void ) { return (m_bUnstoppable || m_bPreventChangesWhileMoving); } + int UpdateTransmitState( void ); + void SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways ); + void SetDisabled( bool bDisabled ); + void SetNodeNumber( int iCount ) { m_iNodeNumber = iCount; } + + // Called to tell the node when it's moved under/not-under the player's crosshair + void SetUnderCrosshair( bool bUnderCrosshair ); + + // Called when the player attempts to activate the node + void PlayerActivated( void ); + void StopPlaying( void ); + void AbortPlaying( void ); + void TeleportTo( CBasePlayer *pPlayer ); + bool CanTeleportTo( void ); + + // Inputs + void InputStartCommentary( inputdata_t &inputdata ); + void InputStartUnstoppableCommentary( inputdata_t &inputdata ); + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + +private: + string_t m_iszPreCommands; + string_t m_iszPostCommands; + CNetworkVar( string_t, m_iszCommentaryFile ); + CNetworkVar( string_t, m_iszCommentaryFileNoHDR ); + string_t m_iszViewTarget; + EHANDLE m_hViewTarget; + EHANDLE m_hViewTargetAngles; // Entity used to blend view angles to look at the target + string_t m_iszViewPosition; + CNetworkVar( EHANDLE, m_hViewPosition ); + EHANDLE m_hViewPositionMover; // Entity used to blend the view to the viewposition entity + bool m_bPreventMovement; + bool m_bUnderCrosshair; + bool m_bUnstoppable; + float m_flFinishedTime; + Vector m_vecFinishOrigin; + QAngle m_vecOriginalAngles; + QAngle m_vecFinishAngles; + bool m_bPreventChangesWhileMoving; + bool m_bDisabled; + Vector m_vecTeleportOrigin; + + COutputEvent m_pOnCommentaryStarted; + COutputEvent m_pOnCommentaryStopped; + + CNetworkVar( bool, m_bActive ); + CNetworkVar( float, m_flStartTime ); + CNetworkVar( string_t, m_iszSpeakers ); + CNetworkVar( int, m_iNodeNumber ); + CNetworkVar( int, m_iNodeNumberMax ); +}; + +BEGIN_DATADESC( CPointCommentaryNode ) + DEFINE_KEYFIELD( m_iszPreCommands, FIELD_STRING, "precommands" ), + DEFINE_KEYFIELD( m_iszPostCommands, FIELD_STRING, "postcommands" ), + DEFINE_KEYFIELD( m_iszCommentaryFile, FIELD_STRING, "commentaryfile" ), + DEFINE_KEYFIELD( m_iszCommentaryFileNoHDR, FIELD_STRING, "commentaryfile_nohdr" ), + DEFINE_KEYFIELD( m_iszViewTarget, FIELD_STRING, "viewtarget" ), + DEFINE_FIELD( m_hViewTarget, FIELD_EHANDLE ), + DEFINE_FIELD( m_hViewTargetAngles, FIELD_EHANDLE ), + DEFINE_KEYFIELD( m_iszViewPosition, FIELD_STRING, "viewposition" ), + DEFINE_FIELD( m_hViewPosition, FIELD_EHANDLE ), + DEFINE_FIELD( m_hViewPositionMover, FIELD_EHANDLE ), + DEFINE_KEYFIELD( m_bPreventMovement, FIELD_BOOLEAN, "prevent_movement" ), + DEFINE_FIELD( m_bUnderCrosshair, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bUnstoppable, FIELD_BOOLEAN ), + DEFINE_FIELD( m_flFinishedTime, FIELD_TIME ), + DEFINE_FIELD( m_vecFinishOrigin, FIELD_VECTOR ), + DEFINE_FIELD( m_vecOriginalAngles, FIELD_VECTOR ), + DEFINE_FIELD( m_vecFinishAngles, FIELD_VECTOR ), + DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ), + DEFINE_FIELD( m_flStartTime, FIELD_TIME ), + DEFINE_KEYFIELD( m_iszSpeakers, FIELD_STRING, "speakers" ), + DEFINE_FIELD( m_iNodeNumber, FIELD_INTEGER ), + DEFINE_FIELD( m_iNodeNumberMax, FIELD_INTEGER ), + DEFINE_FIELD( m_bPreventChangesWhileMoving, FIELD_BOOLEAN ), + DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "start_disabled" ), + DEFINE_KEYFIELD( m_vecTeleportOrigin, FIELD_VECTOR, "teleport_origin" ), + + // Outputs + DEFINE_OUTPUT( m_pOnCommentaryStarted, "OnCommentaryStarted" ), + DEFINE_OUTPUT( m_pOnCommentaryStopped, "OnCommentaryStopped" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "StartCommentary", InputStartCommentary ), + DEFINE_INPUTFUNC( FIELD_VOID, "StartUnstoppableCommentary", InputStartUnstoppableCommentary ), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + + // Functions + DEFINE_THINKFUNC( SpinThink ), + DEFINE_THINKFUNC( UpdateViewThink ), + DEFINE_THINKFUNC( UpdateViewPostThink ), +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST( CPointCommentaryNode, DT_PointCommentaryNode ) + SendPropBool( SENDINFO(m_bActive) ), + SendPropStringT( SENDINFO(m_iszCommentaryFile) ), + SendPropStringT( SENDINFO(m_iszCommentaryFileNoHDR) ), + SendPropTime( SENDINFO(m_flStartTime) ), + SendPropStringT( SENDINFO(m_iszSpeakers) ), + SendPropInt( SENDINFO(m_iNodeNumber), 8, SPROP_UNSIGNED ), + SendPropInt( SENDINFO(m_iNodeNumberMax), 8, SPROP_UNSIGNED ), + SendPropEHandle( SENDINFO(m_hViewPosition) ), +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS( point_commentary_node, CPointCommentaryNode ); + +//----------------------------------------------------------------------------- +// Laser Dot +//----------------------------------------------------------------------------- +class CCommentaryViewPosition : public CSprite +{ + DECLARE_CLASS( CCommentaryViewPosition, CSprite ); +public: + virtual void Spawn( void ) + { + Precache(); + SetModelName( MAKE_STRING("sprites/redglow1.vmt") ); + + BaseClass::Spawn(); + + SetMoveType( MOVETYPE_NONE ); + AddSolidFlags( FSOLID_NOT_SOLID ); + AddEffects( EF_NOSHADOW ); + UTIL_SetSize( this, vec3_origin, vec3_origin ); + } + + virtual void Precache( void ) + { + PrecacheModel( "sprites/redglow1.vmt" ); + } +}; + +LINK_ENTITY_TO_CLASS( point_commentary_viewpoint, CCommentaryViewPosition ); + +//----------------------------------------------------------------------------- +// Purpose: In multiplayer, always return player 1 +//----------------------------------------------------------------------------- +CBasePlayer *GetCommentaryPlayer( void ) +{ + CBasePlayer *pPlayer; + + if ( gpGlobals->maxClients <= 1 ) + { + pPlayer = UTIL_GetLocalPlayer(); + } + else + { + // only respond to the first player + pPlayer = UTIL_PlayerByIndex(1); + } + + return pPlayer; +} + +//=========================================================================================================== +// COMMENTARY GAME SYSTEM +//=========================================================================================================== +void CV_GlobalChange_Commentary( IConVar *var, const char *pOldString, float flOldValue ); + +//----------------------------------------------------------------------------- +// Purpose: Game system to kickstart the director's commentary +//----------------------------------------------------------------------------- +class CCommentarySystem : public CAutoGameSystemPerFrame +{ +public: + DECLARE_DATADESC(); + + CCommentarySystem() : CAutoGameSystemPerFrame( "CCommentarySystem" ) + { + m_iCommentaryNodeCount = 0; + } + + virtual void LevelInitPreEntity() + { + m_hCurrentNode = NULL; + m_bCommentaryConvarsChanging = false; + m_iClearPressedButtons = 0; + + // If the map started via the map_commentary cmd, start in commentary + g_bInCommentaryMode = (engine->IsInCommentaryMode() != 0); + + CalculateCommentaryState(); + } + + void CalculateCommentaryState( void ) + { + // Set the available cvar if we can find commentary data for this level + char szFullName[512]; + Q_snprintf(szFullName,sizeof(szFullName), "maps/%s_commentary.txt", STRING( gpGlobals->mapname) ); + if ( filesystem->FileExists( szFullName ) ) + { + commentary_available.SetValue( true ); + + // If the user wanted commentary, kick it on + if ( commentary.GetBool() ) + { + g_bInCommentaryMode = true; + } + } + else + { + g_bInCommentaryMode = false; + commentary_available.SetValue( false ); + } + } + + virtual void LevelShutdownPreEntity() + { + ShutDownCommentary(); + } + + void ParseEntKVBlock( CBaseEntity *pNode, KeyValues *pkvNode ) + { + KeyValues *pkvNodeData = pkvNode->GetFirstSubKey(); + while ( pkvNodeData ) + { + // Handle the connections block + if ( !Q_strcmp(pkvNodeData->GetName(), "connections") ) + { + ParseEntKVBlock( pNode, pkvNodeData ); + } + else + { + #define COMMENTARY_STRING_LENGTH_MAX 1024 + + const char *pszValue = pkvNodeData->GetString(); + Assert( Q_strlen(pszValue) < COMMENTARY_STRING_LENGTH_MAX ); + if ( Q_strnchr(pszValue, '^', COMMENTARY_STRING_LENGTH_MAX) ) + { + // We want to support quotes in our strings so that we can specify multiple parameters in + // an output inside our commentary files. We convert ^s to "s here. + char szTmp[COMMENTARY_STRING_LENGTH_MAX]; + Q_strncpy( szTmp, pszValue, COMMENTARY_STRING_LENGTH_MAX ); + int len = Q_strlen( szTmp ); + for ( int i = 0; i < len; i++ ) + { + if ( szTmp[i] == '^' ) + { + szTmp[i] = '"'; + } + } + + pNode->KeyValue( pkvNodeData->GetName(), szTmp ); + } + else + { + pNode->KeyValue( pkvNodeData->GetName(), pszValue ); + } + } + + pkvNodeData = pkvNodeData->GetNextKey(); + } + } + + virtual void LevelInitPostEntity( void ) + { + if ( !IsInCommentaryMode() ) + return; + + // Don't spawn commentary entities when loading a savegame + if ( gpGlobals->eLoadType == MapLoad_LoadGame || gpGlobals->eLoadType == MapLoad_Background ) + return; + + m_bCommentaryEnabledMidGame = false; + InitCommentary(); + + IGameEvent *event = gameeventmanager->CreateEvent( "playing_commentary" ); + gameeventmanager->FireEventClientSide( event ); + } + + CPointCommentaryNode *GetNodeUnderCrosshair() + { + CBasePlayer *pPlayer = GetCommentaryPlayer(); + if ( !pPlayer ) + return NULL; + + // See if the player's looking at a commentary node + trace_t tr; + Vector vecSrc = pPlayer->EyePosition(); + Vector vecForward = pPlayer->GetAutoaimVector( AUTOAIM_SCALE_DIRECT_ONLY ); + + g_bTracingVsCommentaryNodes = true; + UTIL_TraceLine( vecSrc, vecSrc + vecForward * MAX_TRACE_LENGTH, MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr ); + g_bTracingVsCommentaryNodes = false; + + if ( !tr.m_pEnt ) + return NULL; + + return dynamic_cast(tr.m_pEnt); + } + + void PrePlayerRunCommand( CBasePlayer *pPlayer, CUserCmd *pUserCmds ) + { + if ( !IsInCommentaryMode() ) + return; + + if ( pPlayer->IsFakeClient() ) + return; + + CPointCommentaryNode *pCurrentNode = GetNodeUnderCrosshair(); + + // Changed nodes? + if ( m_hCurrentNode != pCurrentNode ) + { + // Stop animating the old one + if ( m_hCurrentNode.Get() ) + { + m_hCurrentNode->SetUnderCrosshair( false ); + } + + // Start animating the new one + if ( pCurrentNode ) + { + pCurrentNode->SetUnderCrosshair( true ); + } + + m_hCurrentNode = pCurrentNode; + } + + // Check for commentary node activations + if ( pPlayer ) + { + // Has the player pressed down an attack button? + int buttonsChanged = m_afPlayersLastButtons ^ pUserCmds->buttons; + int buttonsPressed = buttonsChanged & pUserCmds->buttons; + m_afPlayersLastButtons = pUserCmds->buttons; + + if ( !(pUserCmds->buttons & COMMENTARY_BUTTONS) ) + { + m_iClearPressedButtons &= ~COMMENTARY_BUTTONS; + } + + // Detect press events to start/stop commentary nodes + if (buttonsPressed & COMMENTARY_BUTTONS) + { + if ( buttonsPressed & IN_ATTACK2 ) + { + if ( !(GetActiveNode() && GetActiveNode()->CannotBeStopped()) ) + { + JumpToNextNode( pPlayer ); + pUserCmds->buttons &= ~COMMENTARY_BUTTONS; + m_iClearPressedButtons |= (buttonsPressed & COMMENTARY_BUTTONS); + } + } + else + { + // Looking at a node? + if ( m_hCurrentNode ) + { + // Ignore input while an unstoppable node is playing + if ( !GetActiveNode() || !GetActiveNode()->CannotBeStopped() ) + { + // If we have an active node already, stop it + if ( GetActiveNode() && GetActiveNode() != m_hCurrentNode ) + { + GetActiveNode()->StopPlaying(); + } + + m_hCurrentNode->PlayerActivated(); + } + + // Prevent weapon firing when toggling nodes + pUserCmds->buttons &= ~COMMENTARY_BUTTONS; + m_iClearPressedButtons |= (buttonsPressed & COMMENTARY_BUTTONS); + } + else if ( GetActiveNode() && GetActiveNode()->HasViewTarget() ) + { + if ( !GetActiveNode()->CannotBeStopped() ) + { + GetActiveNode()->StopPlaying(); + } + + // Prevent weapon firing when toggling nodes + pUserCmds->buttons &= ~COMMENTARY_BUTTONS; + m_iClearPressedButtons |= (buttonsPressed & COMMENTARY_BUTTONS); + } + } + } + + if ( GetActiveNode() && GetActiveNode()->PreventsMovement() ) + { + pUserCmds->buttons &= ~(IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT | IN_JUMP | IN_DUCK ); + pUserCmds->upmove = 0; + pUserCmds->sidemove = 0; + pUserCmds->forwardmove = 0; + } + + // When we swallow button down events, we have to keep clearing that button + // until the player releases the button. Otherwise, the frame after we swallow + // it, the code detects the button down and goes ahead as normal. + pUserCmds->buttons &= ~m_iClearPressedButtons; + } + + if ( m_iTeleportStage != TELEPORT_NONE ) + { + if ( m_flNextTeleportTime <= gpGlobals->curtime ) + { + if ( m_iTeleportStage == TELEPORT_FADEOUT ) + { + m_iTeleportStage = TELEPORT_TELEPORT; + m_flNextTeleportTime = gpGlobals->curtime + 0.35; + + color32_s clr = { 0,0,0,255 }; + UTIL_ScreenFade( pPlayer, clr, 0.3, 0, FFADE_OUT | FFADE_PURGE | FFADE_STAYOUT ); + } + else if ( m_iTeleportStage == TELEPORT_TELEPORT ) + { + if ( m_hLastCommentaryNode ) + { + m_hLastCommentaryNode->TeleportTo( pPlayer ); + } + + m_iTeleportStage = TELEPORT_FADEIN; + m_flNextTeleportTime = gpGlobals->curtime + 0.6; + } + else if ( m_iTeleportStage == TELEPORT_FADEIN ) + { + m_iTeleportStage = TELEPORT_NONE; + m_flNextTeleportTime = gpGlobals->curtime + 0.25; + + color32_s clr = { 0,0,0,255 }; + UTIL_ScreenFade( pPlayer, clr, 0.3, 0, FFADE_IN | FFADE_PURGE ); + } + } + } + } + + CPointCommentaryNode *GetActiveNode( void ) + { + return m_hActiveCommentaryNode; + } + + void SetActiveNode( CPointCommentaryNode *pNode ) + { + m_hActiveCommentaryNode = pNode; + if ( pNode ) + { + m_hLastCommentaryNode = pNode; + } + } + + int GetCommentaryNodeCount( void ) + { + return m_iCommentaryNodeCount; + } + + bool CommentaryConvarsChanging( void ) + { + return m_bCommentaryConvarsChanging; + } + + void SetCommentaryConvarsChanging( bool bChanging ) + { + m_bCommentaryConvarsChanging = bChanging; + } + + void ConvarChanged( IConVar *pConVar, const char *pOldString, float flOldValue ) + { + ConVarRef var( pConVar ); + + // A convar has been changed by a commentary node. We need to store + // the old state. If the engine shuts down, we need to restore any + // convars that the commentary changed to their previous values. + for ( int i = 0; i < m_ModifiedConvars.Count(); i++ ) + { + // If we find it, just update the current value + if ( !Q_strncmp( var.GetName(), m_ModifiedConvars[i].pszConvar, MAX_MODIFIED_CONVAR_STRING ) ) + { + Q_strncpy( m_ModifiedConvars[i].pszCurrentValue, var.GetString(), MAX_MODIFIED_CONVAR_STRING ); + //Msg(" Updating Convar %s: value %s (org %s)\n", m_ModifiedConvars[i].pszConvar, m_ModifiedConvars[i].pszCurrentValue, m_ModifiedConvars[i].pszOrgValue ); + return; + } + } + + // We didn't find it in our list, so add it + modifiedconvars_t newConvar; + Q_strncpy( newConvar.pszConvar, var.GetName(), MAX_MODIFIED_CONVAR_STRING ); + Q_strncpy( newConvar.pszCurrentValue, var.GetString(), MAX_MODIFIED_CONVAR_STRING ); + Q_strncpy( newConvar.pszOrgValue, pOldString, MAX_MODIFIED_CONVAR_STRING ); + m_ModifiedConvars.AddToTail( newConvar ); + + /* + Msg(" Commentary changed '%s' to '%s' (was '%s')\n", var->GetName(), var->GetString(), pOldString ); + Msg(" Convars stored: %d\n", m_ModifiedConvars.Count() ); + for ( int i = 0; i < m_ModifiedConvars.Count(); i++ ) + { + Msg(" Convar %d: %s, value %s (org %s)\n", i, m_ModifiedConvars[i].pszConvar, m_ModifiedConvars[i].pszCurrentValue, m_ModifiedConvars[i].pszOrgValue ); + } + */ + } + + void InitCommentary( void ) + { + // Install the global cvar callback + cvar->InstallGlobalChangeCallback( CV_GlobalChange_Commentary ); + + m_flNextTeleportTime = 0; + m_iTeleportStage = TELEPORT_NONE; + m_hLastCommentaryNode = NULL; + + // If we find the commentary semaphore, the commentary entities already exist. + // This occurs when you transition back to a map that has saved commentary nodes in it. + if ( gEntList.FindEntityByName( NULL, COMMENTARY_SPAWNED_SEMAPHORE ) ) + return; + + // Spawn the commentary semaphore entity + CBaseEntity *pSemaphore = CreateEntityByName( "info_target" ); + pSemaphore->SetName( MAKE_STRING(COMMENTARY_SPAWNED_SEMAPHORE) ); + + bool oldLock = engine->LockNetworkStringTables( false ); + + // Find the commentary file + char szFullName[512]; + Q_snprintf(szFullName,sizeof(szFullName), "maps/%s_commentary.txt", STRING( gpGlobals->mapname )); + KeyValues *pkvFile = new KeyValues( "Commentary" ); + if ( pkvFile->LoadFromFile( filesystem, szFullName, "MOD" ) ) + { + Msg( "Commentary: Loading commentary data from %s. \n", szFullName ); + + // Load each commentary block, and spawn the entities + KeyValues *pkvNode = pkvFile->GetFirstSubKey(); + while ( pkvNode ) + { + // Get node name + const char *pNodeName = pkvNode->GetName(); + + // Skip the trackinfo + if ( !Q_strncmp( pNodeName, "trackinfo", 9 ) ) + { + pkvNode = pkvNode->GetNextKey(); + continue; + } + + KeyValues *pClassname = pkvNode->FindKey( "classname" ); + if ( pClassname ) + { + // Use the classname instead + pNodeName = pClassname->GetString(); + } + + // Spawn the commentary entity + CBaseEntity *pNode = CreateEntityByName( pNodeName ); + if ( pNode ) + { + ParseEntKVBlock( pNode, pkvNode ); + DispatchSpawn( pNode ); + + EHANDLE hHandle; + hHandle = pNode; + m_hSpawnedEntities.AddToTail( hHandle ); + + CPointCommentaryNode *pCommNode = dynamic_cast(pNode); + if ( pCommNode ) + { + m_iCommentaryNodeCount++; + pCommNode->SetNodeNumber( m_iCommentaryNodeCount ); + } + } + else + { + Warning("Commentary: Failed to spawn commentary entity, type: '%s'\n", pNodeName ); + } + + // Move to next entity + pkvNode = pkvNode->GetNextKey(); + } + + // Then activate all the entities + for ( int i = 0; i < m_hSpawnedEntities.Count(); i++ ) + { + m_hSpawnedEntities[i]->Activate(); + } + } + else + { + Msg( "Commentary: Could not find commentary data file '%s'. \n", szFullName ); + } + + engine->LockNetworkStringTables( oldLock ); + } + + void ShutDownCommentary( void ) + { + if ( GetActiveNode() ) + { + GetActiveNode()->AbortPlaying(); + } + + // Destroy all the entities created by commentary + for ( int i = m_hSpawnedEntities.Count()-1; i >= 0; i-- ) + { + if ( m_hSpawnedEntities[i] ) + { + UTIL_Remove( m_hSpawnedEntities[i] ); + } + } + m_hSpawnedEntities.Purge(); + m_iCommentaryNodeCount = 0; + + // Remove the commentary semaphore + CBaseEntity *pSemaphore = gEntList.FindEntityByName( NULL, COMMENTARY_SPAWNED_SEMAPHORE ); + if ( pSemaphore ) + { + UTIL_Remove( pSemaphore ); + } + + // Remove our global convar callback + cvar->RemoveGlobalChangeCallback( CV_GlobalChange_Commentary ); + + // Reset any convars that have been changed by the commentary + for ( int i = 0; i < m_ModifiedConvars.Count(); i++ ) + { + ConVar *pConVar = (ConVar *)cvar->FindVar( m_ModifiedConvars[i].pszConvar ); + if ( pConVar ) + { + pConVar->SetValue( m_ModifiedConvars[i].pszOrgValue ); + } + } + m_ModifiedConvars.Purge(); + + m_hCurrentNode = NULL; + m_hActiveCommentaryNode = NULL; + m_hLastCommentaryNode = NULL; + m_flNextTeleportTime = 0; + m_iTeleportStage = TELEPORT_NONE; + } + + void SetCommentaryMode( bool bCommentaryMode ) + { + g_bInCommentaryMode = bCommentaryMode; + CalculateCommentaryState(); + + // If we're turning on commentary, create all the entities. + if ( IsInCommentaryMode() ) + { + m_bCommentaryEnabledMidGame = true; + InitCommentary(); + } + else + { + ShutDownCommentary(); + } + } + + void OnRestore( void ) + { + cvar->RemoveGlobalChangeCallback( CV_GlobalChange_Commentary ); + + if ( !IsInCommentaryMode() ) + return; + + // Set any convars that have already been changed by the commentary before the save + for ( int i = 0; i < m_ModifiedConvars.Count(); i++ ) + { + ConVar *pConVar = (ConVar *)cvar->FindVar( m_ModifiedConvars[i].pszConvar ); + if ( pConVar ) + { + //Msg(" Restoring Convar %s: value %s (org %s)\n", m_ModifiedConvars[i].pszConvar, m_ModifiedConvars[i].pszCurrentValue, m_ModifiedConvars[i].pszOrgValue ); + pConVar->SetValue( m_ModifiedConvars[i].pszCurrentValue ); + } + } + + // Install the global cvar callback + cvar->InstallGlobalChangeCallback( CV_GlobalChange_Commentary ); + } + + bool CommentaryWasEnabledMidGame( void ) + { + return m_bCommentaryEnabledMidGame; + } + + void JumpToNextNode( CBasePlayer *pPlayer ) + { + if ( m_flNextTeleportTime > gpGlobals->curtime || m_iTeleportStage != TELEPORT_NONE ) + return; + + CBaseEntity *pEnt = m_hLastCommentaryNode; + while ( ( pEnt = gEntList.FindEntityByClassname( pEnt, "point_commentary_node" ) ) != m_hLastCommentaryNode ) + { + CPointCommentaryNode *pNode = dynamic_cast( pEnt ); + if ( pNode && pNode->CanTeleportTo() ) + { + m_iTeleportStage = TELEPORT_FADEOUT; + m_hLastCommentaryNode = pNode; + m_flNextTeleportTime = gpGlobals->curtime; + + // Stop any active nodes + if ( m_hActiveCommentaryNode ) + { + m_hActiveCommentaryNode->StopPlaying(); + } + break; + } + } + } + +private: + int m_afPlayersLastButtons; + int m_iCommentaryNodeCount; + bool m_bCommentaryConvarsChanging; + int m_iClearPressedButtons; + bool m_bCommentaryEnabledMidGame; + float m_flNextTeleportTime; + int m_iTeleportStage; + + CUtlVector< modifiedconvars_t > m_ModifiedConvars; + CUtlVector m_hSpawnedEntities; + CHandle m_hCurrentNode; + CHandle m_hActiveCommentaryNode; + CHandle m_hLastCommentaryNode; +}; + +CCommentarySystem g_CommentarySystem; + +void CommentarySystem_PePlayerRunCommand( CBasePlayer *player, CUserCmd *ucmd ) +{ + g_CommentarySystem.PrePlayerRunCommand( player, ucmd ); +} + +BEGIN_DATADESC_NO_BASE( CCommentarySystem ) + //int m_afPlayersLastButtons; DON'T SAVE + //bool m_bCommentaryConvarsChanging; DON'T SAVE + //int m_iClearPressedButtons; DON'T SAVE + + DEFINE_FIELD( m_bCommentaryEnabledMidGame, FIELD_BOOLEAN ), + DEFINE_FIELD( m_flNextTeleportTime, FIELD_TIME ), + DEFINE_FIELD( m_iTeleportStage, FIELD_INTEGER ), + + DEFINE_UTLVECTOR( m_ModifiedConvars, FIELD_EMBEDDED ), + DEFINE_UTLVECTOR( m_hSpawnedEntities, FIELD_EHANDLE ), + DEFINE_FIELD( m_hCurrentNode, FIELD_EHANDLE ), + DEFINE_FIELD( m_hActiveCommentaryNode, FIELD_EHANDLE ), + DEFINE_FIELD( m_hLastCommentaryNode, FIELD_EHANDLE ), + DEFINE_FIELD( m_iCommentaryNodeCount, FIELD_INTEGER ), +END_DATADESC() + +BEGIN_SIMPLE_DATADESC( modifiedconvars_t ) + DEFINE_ARRAY( pszConvar, FIELD_CHARACTER, MAX_MODIFIED_CONVAR_STRING ), + DEFINE_ARRAY( pszCurrentValue, FIELD_CHARACTER, MAX_MODIFIED_CONVAR_STRING ), + DEFINE_ARRAY( pszOrgValue, FIELD_CHARACTER, MAX_MODIFIED_CONVAR_STRING ), +END_DATADESC() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CC_CommentaryChanged( IConVar *pConVar, const char *pOldString, float flOldValue ) +{ + ConVarRef var( pConVar ); + if ( var.GetBool() != g_bInCommentaryMode ) + { + g_CommentarySystem.SetCommentaryMode( var.GetBool() ); + } +} +ConVar commentary("commentary", "0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "Desired commentary mode state.", CC_CommentaryChanged ); + +//----------------------------------------------------------------------------- +// Purpose: We need to revert back any convar changes that are made by the +// commentary system during commentary. This code stores convar changes +// made by the commentary system, and reverts them when finished. +//----------------------------------------------------------------------------- +void CV_GlobalChange_Commentary( IConVar *var, const char *pOldString, float flOldValue ) +{ + if ( !g_CommentarySystem.CommentaryConvarsChanging() ) + { + // A convar has changed, but not due to commentary nodes. Ignore it. + return; + } + + g_CommentarySystem.ConvarChanged( var, pOldString, flOldValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CC_CommentaryNotChanging( void ) +{ + g_CommentarySystem.SetCommentaryConvarsChanging( false ); +} +static ConCommand commentary_cvarsnotchanging("commentary_cvarsnotchanging", CC_CommentaryNotChanging, 0 ); + +bool IsListeningToCommentary( void ) +{ + return ( g_CommentarySystem.GetActiveNode() != NULL ); +} + +//=========================================================================================================== +// COMMENTARY NODES +//=========================================================================================================== + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::Spawn( void ) +{ + // No model specified? + char *szModel = (char *)STRING( GetModelName() ); + if (!szModel || !*szModel) + { + szModel = "models/extras/info_speech.mdl"; + SetModelName( AllocPooledString(szModel) ); + } + + Precache(); + SetModel( szModel ); + UTIL_SetSize( this, -Vector(16,16,16), Vector(16,16,16) ); + SetSolid( SOLID_BBOX ); + AddSolidFlags( FSOLID_CUSTOMRAYTEST | FSOLID_CUSTOMBOXTEST ); + AddEffects( EF_NOSHADOW ); + + // Setup for animation + ResetSequence( LookupSequence("idle") ); + SetThink( &CPointCommentaryNode::SpinThink ); + SetNextThink( gpGlobals->curtime + 0.1f ); + + m_iNodeNumber = 0; + m_iNodeNumberMax = 0; + + SetDisabled( m_bDisabled ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::Activate( void ) +{ + m_iNodeNumberMax = g_CommentarySystem.GetCommentaryNodeCount(); + + if ( m_iszViewTarget != NULL_STRING ) + { + m_hViewTarget = gEntList.FindEntityByName( NULL, m_iszViewTarget ); + if ( !m_hViewTarget ) + { + Warning("%s: %s could not find viewtarget %s.\n", GetClassname(), GetDebugName(), STRING(m_iszViewTarget) ); + } + } + + if ( m_iszViewPosition != NULL_STRING ) + { + m_hViewPosition = gEntList.FindEntityByName( NULL, m_iszViewPosition ); + if ( !m_hViewPosition.Get() ) + { + Warning("%s: %s could not find viewposition %s.\n", GetClassname(), GetDebugName(), STRING(m_iszViewPosition) ); + } + } + + BaseClass::Activate(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::Precache() +{ + PrecacheModel( STRING( GetModelName() ) ); + + if ( m_iszCommentaryFile.Get() != NULL_STRING ) + { + PrecacheScriptSound( STRING( m_iszCommentaryFile.Get() ) ); + } + else + { + Warning("%s: %s has no commentary file.\n", GetClassname(), GetDebugName() ); + } + + if ( m_iszCommentaryFileNoHDR.Get() != NULL_STRING ) + { + PrecacheScriptSound( STRING( m_iszCommentaryFileNoHDR.Get() ) ); + } + + BaseClass::Precache(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called to tell the node when it's moved under/not-under the player's crosshair +//----------------------------------------------------------------------------- +void CPointCommentaryNode::SetUnderCrosshair( bool bUnderCrosshair ) +{ + if ( bUnderCrosshair ) + { + // Start animating + m_bUnderCrosshair = true; + + if ( !m_bActive ) + { + m_flAnimTime = gpGlobals->curtime; + } + } + else + { + // Stop animating + m_bUnderCrosshair = false; + } +} + +//------------------------------------------------------------------------------ +// Purpose: Prevent collisions of everything except the trace from the commentary system +//------------------------------------------------------------------------------ +bool CPointCommentaryNode::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace ) +{ + if ( !g_bTracingVsCommentaryNodes ) + return false; + if ( m_bDisabled ) + return false; + + return BaseClass::TestCollision( ray, mask, trace ); +} + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CPointCommentaryNode::SpinThink( void ) +{ + // Rotate if we're active, or under the crosshair. Don't rotate if we're + // under the crosshair, but we've already been listened to. + if ( m_bActive || (m_bUnderCrosshair && m_nSkin == 0) ) + { + if ( m_bActive ) + { + m_flPlaybackRate = 3.0; + } + else + { + m_flPlaybackRate = 1.0; + } + StudioFrameAdvance(); + DispatchAnimEvents(this); + } + + SetNextThink( gpGlobals->curtime + 0.1f ); +} + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CPointCommentaryNode::PlayerActivated( void ) +{ + gamestats->Event_Commentary(); + + if ( m_bActive ) + { + StopPlaying(); + } + else + { + StartCommentary(); + g_CommentarySystem.SetActiveNode( this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::StopPlaying( void ) +{ + if ( m_bActive ) + { + FinishCommentary(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Stop playing the node, but snap completely out of the node. +// Used when players shut down commentary while we're in the middle +// of playing a node, so we can't smoothly blend out (since the +// commentary entities need to be removed). +//----------------------------------------------------------------------------- +void CPointCommentaryNode::AbortPlaying( void ) +{ + if ( m_bActive ) + { + FinishCommentary( false ); + } + else if ( m_bPreventChangesWhileMoving ) + { + // We're a node that's not active, but is in the process of transitioning the view. Finish movement. + CleanupPostCommentary(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CPointCommentaryNode::CanTeleportTo( void ) +{ + //return ( m_vecTeleportOrigin != vec3_origin ); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::TeleportTo( CBasePlayer *pPlayer ) +{ + Vector vecTarget = m_vecTeleportOrigin; + if ( m_vecTeleportOrigin == vec3_origin ) + { + vecTarget = GetAbsOrigin(); + } + + trace_t trace; + UTIL_TraceHull( vecTarget, vecTarget + Vector( 0, 0, -500 ), pPlayer->WorldAlignMins(), pPlayer->WorldAlignMaxs(), MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &trace ); + + pPlayer->Teleport( &trace.endpos, NULL, &vec3_origin ); + + Vector vecToNode = GetAbsOrigin() - pPlayer->EyePosition(); + VectorNormalize( vecToNode ); + QAngle vecAngle; + VectorAngles( vecToNode, Vector(0,0,1), vecAngle ); + + pPlayer->SnapEyeAngles( vecAngle ); +} + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CPointCommentaryNode::StartCommentary( void ) +{ + CBasePlayer *pPlayer = GetCommentaryPlayer(); + + if ( !pPlayer ) + return; + + m_bActive = true; + + m_flAnimTime = gpGlobals->curtime; + m_flPrevAnimTime = gpGlobals->curtime; + + // Switch to the greyed out skin + m_nSkin = 1; + + m_pOnCommentaryStarted.FireOutput( this, this ); + + // Fire off our precommands + if ( m_iszPreCommands != NULL_STRING ) + { + g_CommentarySystem.SetCommentaryConvarsChanging( true ); + engine->ClientCommand( pPlayer->edict(), STRING(m_iszPreCommands) ); + engine->ClientCommand( pPlayer->edict(), "commentary_cvarsnotchanging\n" ); + } + + // Start the commentary + m_flStartTime = gpGlobals->curtime; + + // If we have a view target, start blending towards it + if ( m_hViewTarget || m_hViewPosition.Get() ) + { + m_vecOriginalAngles = pPlayer->EyeAngles(); + SetContextThink( &CPointCommentaryNode::UpdateViewThink, gpGlobals->curtime, s_pCommentaryUpdateViewThink ); + } + + //SetContextThink( &CPointCommentaryNode::FinishCommentary, gpGlobals->curtime + flDuration, s_pFinishCommentaryThink ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CC_CommentaryFinishNode( void ) +{ + // We were told by the client DLL that our commentary has finished + if ( g_CommentarySystem.GetActiveNode() ) + { + g_CommentarySystem.GetActiveNode()->StopPlaying(); + } +} +static ConCommand commentary_finishnode("commentary_finishnode", CC_CommentaryFinishNode, 0 ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::UpdateViewThink( void ) +{ + if ( !m_bActive ) + return; + CBasePlayer *pPlayer = GetCommentaryPlayer(); + if ( !pPlayer ) + return; + + // Swing the view towards the target + if ( m_hViewTarget ) + { + if ( !m_hViewTargetAngles && !m_hViewPositionMover ) + { + // Make an invisible entity to attach view angles to + m_hViewTargetAngles = CreateEntityByName( "point_commentary_viewpoint" ); + m_hViewTargetAngles->SetAbsOrigin( pPlayer->EyePosition() ); + m_hViewTargetAngles->SetAbsAngles( pPlayer->EyeAngles() ); + pPlayer->SetViewEntity( m_hViewTargetAngles ); + + if ( pPlayer->GetActiveWeapon() ) + { + pPlayer->GetActiveWeapon()->Holster(); + } + } + + QAngle angGoal; + QAngle angCurrent; + if ( m_hViewPositionMover ) + { + angCurrent = m_hViewPositionMover->GetAbsAngles(); + VectorAngles( m_hViewTarget->WorldSpaceCenter() - m_hViewPositionMover->GetAbsOrigin(), angGoal ); + } + else if ( m_hViewTargetAngles ) + { + angCurrent = m_hViewTargetAngles->GetAbsAngles(); + m_hViewTargetAngles->SetAbsOrigin( pPlayer->EyePosition() ); + VectorAngles( m_hViewTarget->WorldSpaceCenter() - m_hViewTargetAngles->GetAbsOrigin(), angGoal ); + } + else + { + angCurrent = pPlayer->EyeAngles(); + VectorAngles( m_hViewTarget->WorldSpaceCenter() - pPlayer->EyePosition(), angGoal ); + } + + // Accelerate towards the target goal angles + float dx = AngleDiff( angGoal.x, angCurrent.x ); + float dy = AngleDiff( angGoal.y, angCurrent.y ); + float mod = 1.0 - ExponentialDecay( 0.5, 0.3, gpGlobals->frametime ); + float dxmod = dx * mod; + float dymod = dy * mod; + + angCurrent.x = AngleNormalize( angCurrent.x + dxmod ); + angCurrent.y = AngleNormalize( angCurrent.y + dymod ); + + if ( m_hViewPositionMover ) + { + m_hViewPositionMover->SetAbsAngles( angCurrent ); + } + else if ( m_hViewTargetAngles ) + { + m_hViewTargetAngles->SetAbsAngles( angCurrent ); + pPlayer->SnapEyeAngles( angCurrent ); + } + else + { + pPlayer->SnapEyeAngles( angCurrent ); + } + + SetNextThink( gpGlobals->curtime, s_pCommentaryUpdateViewThink ); + } + + if ( m_hViewPosition.Get() ) + { + if ( pPlayer->GetActiveWeapon() ) + { + pPlayer->GetActiveWeapon()->Holster(); + } + + if ( !m_hViewPositionMover ) + { + // Make an invisible info target entity for us to attach the view to, + // and move it to the desired view position. + m_hViewPositionMover = CreateEntityByName( "point_commentary_viewpoint" ); + m_hViewPositionMover->SetAbsAngles( pPlayer->EyeAngles() ); + pPlayer->SetViewEntity( m_hViewPositionMover ); + } + + // Blend to the target position over time. + float flCurTime = (gpGlobals->curtime - m_flStartTime); + float flBlendPerc = clamp( flCurTime * 0.5f, 0.f, 1.f ); + + // Figure out the current view position + Vector vecCurEye; + VectorLerp( pPlayer->EyePosition(), m_hViewPosition.Get()->GetAbsOrigin(), flBlendPerc, vecCurEye ); + m_hViewPositionMover->SetAbsOrigin( vecCurEye ); + + SetNextThink( gpGlobals->curtime, s_pCommentaryUpdateViewThink ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::UpdateViewPostThink( void ) +{ + CBasePlayer *pPlayer = GetCommentaryPlayer(); + if ( !pPlayer ) + return; + + if ( m_hViewPosition.Get() && m_hViewPositionMover ) + { + // Blend back to the player's position over time. + float flCurTime = (gpGlobals->curtime - m_flFinishedTime); + float flTimeToBlend = MIN( 2.0, m_flFinishedTime - m_flStartTime ); + float flBlendPerc = 1.0f - clamp( flCurTime / flTimeToBlend, 0.f, 1.f ); + + //Msg("OUT: CurTime %.2f, BlendTime: %.2f, Blend: %.3f\n", flCurTime, flTimeToBlend, flBlendPerc ); + + // Only do this while we're still moving + if ( flBlendPerc > 0 ) + { + // Figure out the current view position + Vector vecPlayerPos = pPlayer->EyePosition(); + Vector vecToPosition = (m_vecFinishOrigin - vecPlayerPos); + Vector vecCurEye = pPlayer->EyePosition() + (vecToPosition * flBlendPerc); + m_hViewPositionMover->SetAbsOrigin( vecCurEye ); + + if ( m_hViewTarget ) + { + Quaternion quatFinish; + Quaternion quatOriginal; + Quaternion quatCurrent; + AngleQuaternion( m_vecOriginalAngles, quatOriginal ); + AngleQuaternion( m_vecFinishAngles, quatFinish ); + QuaternionSlerp( quatFinish, quatOriginal, 1.0 - flBlendPerc, quatCurrent ); + QAngle angCurrent; + QuaternionAngles( quatCurrent, angCurrent ); + m_hViewPositionMover->SetAbsAngles( angCurrent ); + } + + SetNextThink( gpGlobals->curtime, s_pCommentaryUpdateViewThink ); + return; + } + + pPlayer->SnapEyeAngles( m_hViewPositionMover->GetAbsAngles() ); + } + + // We're done + CleanupPostCommentary(); + + m_bPreventChangesWhileMoving = false; +} + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CPointCommentaryNode::FinishCommentary( bool bBlendOut ) +{ + CBasePlayer *pPlayer = GetCommentaryPlayer(); + if ( !pPlayer ) + return; + + // Fire off our postcommands + if ( m_iszPostCommands != NULL_STRING ) + { + g_CommentarySystem.SetCommentaryConvarsChanging( true ); + engine->ClientCommand( pPlayer->edict(), STRING(m_iszPostCommands) ); + engine->ClientCommand( pPlayer->edict(), "commentary_cvarsnotchanging\n" ); + } + + // Stop the commentary + m_flFinishedTime = gpGlobals->curtime; + + if ( bBlendOut && m_hViewPositionMover ) + { + m_bActive = false; + m_flPlaybackRate = 1.0; + m_vecFinishOrigin = m_hViewPositionMover->GetAbsOrigin(); + m_vecFinishAngles = m_hViewPositionMover->GetAbsAngles(); + + m_bPreventChangesWhileMoving = true; + + // We've moved away from the player's position. Move back to it before ending + SetContextThink( &CPointCommentaryNode::UpdateViewPostThink, gpGlobals->curtime, s_pCommentaryUpdateViewThink ); + return; + } + + CleanupPostCommentary(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::CleanupPostCommentary( void ) +{ + CBasePlayer *pPlayer = GetCommentaryPlayer(); + if ( !pPlayer ) + return; + + if ( ( m_hViewPositionMover || m_hViewTargetAngles ) && pPlayer->GetActiveWeapon() ) + { + pPlayer->GetActiveWeapon()->Deploy(); + } + + if ( m_hViewTargetAngles && pPlayer->GetViewEntity() == m_hViewTargetAngles ) + { + pPlayer->SetViewEntity( NULL ); + } + UTIL_Remove( m_hViewTargetAngles ); + + if ( m_hViewPositionMover && pPlayer->GetViewEntity() == m_hViewPositionMover ) + { + pPlayer->SetViewEntity( NULL ); + } + UTIL_Remove( m_hViewPositionMover ); + + m_bActive = false; + m_flPlaybackRate = 1.0; + m_bUnstoppable = false; + m_flFinishedTime = 0; + SetContextThink( NULL, 0, s_pCommentaryUpdateViewThink ); + + // Clear out any events waiting on our start commentary + g_EventQueue.CancelEvents( this ); + + m_pOnCommentaryStopped.FireOutput( this, this ); + + g_CommentarySystem.SetActiveNode( NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::InputStartCommentary( inputdata_t &inputdata ) +{ + if ( !m_bActive ) + { + if ( g_CommentarySystem.GetActiveNode() ) + { + g_CommentarySystem.GetActiveNode()->StopPlaying(); + } + + PlayerActivated(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::InputStartUnstoppableCommentary( inputdata_t &inputdata ) +{ + if ( !m_bActive ) + { + m_bUnstoppable = true; + InputStartCommentary( inputdata ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::InputEnable( inputdata_t &inputdata ) +{ + SetDisabled( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::InputDisable( inputdata_t &inputdata ) +{ + SetDisabled( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::SetDisabled( bool bDisabled ) +{ + m_bDisabled = bDisabled; + + if ( m_bDisabled ) + { + AddEffects( EF_NODRAW ); + } + else + { + RemoveEffects( EF_NODRAW ); + } +} + +//----------------------------------------------------------------------------- +// Purpose Force our lighting landmark to be transmitted +//----------------------------------------------------------------------------- +int CPointCommentaryNode::UpdateTransmitState( void ) +{ + return SetTransmitState( FL_EDICT_ALWAYS ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways ) +{ + // Are we already marked for transmission? + if ( pInfo->m_pTransmitEdict->Get( entindex() ) ) + return; + + BaseClass::SetTransmit( pInfo, bAlways ); + + // Force our camera view position entity to be sent + if ( m_hViewTarget ) + { + m_hViewTarget->SetTransmit( pInfo, bAlways ); + } + if ( m_hViewTargetAngles ) + { + m_hViewTargetAngles->SetTransmit( pInfo, bAlways ); + } + if ( m_hViewPosition.Get() ) + { + m_hViewPosition.Get()->SetTransmit( pInfo, bAlways ); + } + if ( m_hViewPositionMover ) + { + m_hViewPositionMover->SetTransmit( pInfo, bAlways ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CPointCommentaryNode::PreventsMovement( void ) +{ + // If we're moving the player's view at all, prevent movement + if ( m_hViewPosition.Get() ) + return true; + + return m_bPreventMovement; +} + +//----------------------------------------------------------------------------- +// COMMENTARY SAVE / RESTORE +//----------------------------------------------------------------------------- +static short COMMENTARY_SAVE_RESTORE_VERSION = 2; + +class CCommentary_SaveRestoreBlockHandler : public CDefSaveRestoreBlockHandler +{ +public: + const char *GetBlockName() + { + return "Commentary"; + } + + //--------------------------------- + + void Save( ISave *pSave ) + { + pSave->WriteBool( &g_bInCommentaryMode ); + if ( IsInCommentaryMode() ) + { + pSave->WriteAll( &g_CommentarySystem, g_CommentarySystem.GetDataDescMap() ); + pSave->WriteInt( &CAI_BaseNPC::m_nDebugBits ); + } + } + + //--------------------------------- + + void WriteSaveHeaders( ISave *pSave ) + { + pSave->WriteShort( &COMMENTARY_SAVE_RESTORE_VERSION ); + } + + //--------------------------------- + + void ReadRestoreHeaders( IRestore *pRestore ) + { + // No reason why any future version shouldn't try to retain backward compatability. The default here is to not do so. + short version; + pRestore->ReadShort( &version ); + m_fDoLoad = ( version == COMMENTARY_SAVE_RESTORE_VERSION ); + } + + //--------------------------------- + + void Restore( IRestore *pRestore, bool createPlayers ) + { + if ( m_fDoLoad ) + { + pRestore->ReadBool( &g_bInCommentaryMode ); + if ( g_bInCommentaryMode ) + { + pRestore->ReadAll( &g_CommentarySystem, g_CommentarySystem.GetDataDescMap() ); + CAI_BaseNPC::m_nDebugBits = pRestore->ReadInt(); + } + + // Force the commentary convar to match the saved game state + commentary.SetValue( g_bInCommentaryMode ); + } + } + +private: + bool m_fDoLoad; +}; + +//----------------------------------------------------------------------------- + +CCommentary_SaveRestoreBlockHandler g_Commentary_SaveRestoreBlockHandler; + +//------------------------------------- + +ISaveRestoreBlockHandler *GetCommentarySaveRestoreBlockHandler() +{ + return &g_Commentary_SaveRestoreBlockHandler; +} + +//----------------------------------------------------------------------------- +// Purpose: Commentary specific logic_auto replacement. +// Fires outputs based upon how commentary mode has been activated. +//----------------------------------------------------------------------------- +class CCommentaryAuto : public CBaseEntity +{ + DECLARE_CLASS( CCommentaryAuto, CBaseEntity ); +public: + DECLARE_DATADESC(); + + void Spawn(void); + void Think(void); + + void InputMultiplayerSpawned( inputdata_t &inputdata ); + +private: + // fired if commentary started due to new map + COutputEvent m_OnCommentaryNewGame; + + // fired if commentary was turned on in the middle of a map + COutputEvent m_OnCommentaryMidGame; + + // fired when the player spawns in a multiplayer game + COutputEvent m_OnCommentaryMultiplayerSpawn; +}; + +LINK_ENTITY_TO_CLASS(commentary_auto, CCommentaryAuto); + +BEGIN_DATADESC( CCommentaryAuto ) + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "MultiplayerSpawned", InputMultiplayerSpawned ), + + // Outputs + DEFINE_OUTPUT(m_OnCommentaryNewGame, "OnCommentaryNewGame"), + DEFINE_OUTPUT(m_OnCommentaryMidGame, "OnCommentaryMidGame"), + DEFINE_OUTPUT(m_OnCommentaryMultiplayerSpawn, "OnCommentaryMultiplayerSpawn"), +END_DATADESC() + +//------------------------------------------------------------------------------ +// Purpose : Fire my outputs here if I fire on map reload +//------------------------------------------------------------------------------ +void CCommentaryAuto::Spawn(void) +{ + BaseClass::Spawn(); + SetNextThink( gpGlobals->curtime + 0.1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCommentaryAuto::Think(void) +{ + if ( g_CommentarySystem.CommentaryWasEnabledMidGame() ) + { + m_OnCommentaryMidGame.FireOutput(NULL, this); + } + else + { + m_OnCommentaryNewGame.FireOutput(NULL, this); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCommentaryAuto::InputMultiplayerSpawned( inputdata_t &inputdata ) +{ + m_OnCommentaryMultiplayerSpawn.FireOutput( NULL, this ); +} + +#else + +bool IsInCommentaryMode( void ) +{ + return false; +} + +#endif -- cgit v1.2.3