Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1908 lines
57 KiB

//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: The system for handling director's commentary style production info in-game.
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "tier0/icommandline.h"
#include "igamesystem.h"
#include "filesystem.h"
#include <keyvalues.h>
#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"
#include "point_template.h"
#include "mapentities.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." );
extern ConVar rr_remarkables_enabled;
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;
}
PRECACHE_REGISTER_BEGIN( GLOBAL, PrecachePointCommentaryNode )
PRECACHE( MODEL, "models/extras/info_speech.mdl" )
PRECACHE_REGISTER_END()
//-----------------------------------------------------------------------------
// 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();
CPointCommentaryNode();
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;
m_pkvSavedModifications = NULL;
}
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 )
{
// Previously, we would bail out immediately. However if info_remarkables are enabled,
// there is the possiblity that they are defined in the commentary file, because it was a
// more convenient markup tool for the writers than Hammer. Therefore we must load it.
if ( !IsInCommentaryMode() && !rr_remarkables_enabled.GetBool() )
return;
// Don't spawn commentary entities when loading a savegame
if ( gpGlobals->eLoadType == MapLoad_LoadGame || gpGlobals->eLoadType == MapLoad_Background )
return;
m_bCommentaryEnabledMidGame = false;
InitCommentary();
if ( IsInCommentaryMode() )
{
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<CPointCommentaryNode*>(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)
{
#ifdef PORTAL2
if ( buttonsPressed & IN_REMOTE_VIEW && (V_stristr( MapName(), "coop" ) != NULL) )
#else
if ( buttonsPressed & IN_ATTACK2 )
#endif
{
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 )
{
MEM_ALLOC_CREDIT();
// 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
static CGameString infoTargetStr( "info_target" );
static CGameString commentarySpawnedSemaphoreName( COMMENTARY_SPAWNED_SEMAPHORE );
CBaseEntity *pSemaphore = CreateEntityByName( infoTargetStr );
pSemaphore->SetName( commentarySpawnedSemaphoreName );
bool oldLock = engine->LockNetworkStringTables( false );
if ( m_pkvSavedModifications != NULL )
{
m_pkvSavedModifications->deleteThis();
m_pkvSavedModifications = NULL;
}
// 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 );
unsigned int iFileSize = filesystem->Size( szFullName, "MOD" );
Assert( iFileSize > 0 );
CPointTemplate *PointTemplates[MAX_EDICTS];
int iPointTemplateCount = 0;
HierarchicalSpawnMapData_t SpawnData[MAX_EDICTS]; //for point templates
//to avoid rewriting a bunch of code, I'm leaving the keyvalue parsing as is. But to get point_templates to work, I need the keyvalues as strings. So we go file->KeyValues->String.
char *pKVToStringBuffer = (char *)stackalloc( sizeof(char *) * iFileSize );
char TempKeyBuffer[20 * 1024];
CUtlBuffer SpawnKeyValuesBackToString( TempKeyBuffer, 20 * 1024 );
int iKVStringBufferIndex = 0;
// 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 ( StringHasPrefixCaseSensitive( pNodeName, "trackinfo" ) )
{
pkvNode = pkvNode->GetNextKey();
continue;
}
KeyValues *pClassname = pkvNode->FindKey( "classname" );
if ( pClassname )
{
// Use the classname instead
pNodeName = pClassname->GetString();
}
if ( rr_remarkables_enabled.GetBool() )
{
bool bModifyBlock = !Q_strcmp( pkvNode->GetName(), "modify_entity" );
// in L4D, we hijack the commentary system to create
// info_remarkable nodes. If we are here with commentary
// disabled, load only those nodes.
// We also hijack it for map updates (exploits/etc.) on 360
KeyValues *pUpdateFlag = pkvNode->FindKey( "mapupdate", false );
if ( !IsInCommentaryMode() &&
!StringHasPrefixCaseSensitive( pNodeName, "info_remarkable" ) &&
!pUpdateFlag && !bModifyBlock )
{
// Move to next entity
pkvNode = pkvNode->GetNextKey();
continue;
}
if ( bModifyBlock )
{
if ( m_pkvSavedModifications == NULL )
{
m_pkvSavedModifications = new KeyValues("Entities");
}
m_pkvSavedModifications->AddSubKey( pkvNode->MakeCopy() );
}
}
// Spawn the commentary entity
CBaseEntity *pNode = CreateEntityByName( pNodeName );
if ( pNode )
{
ParseEntKVBlock( pNode, pkvNode );
EHANDLE hHandle;
hHandle = pNode;
int iAddIndex = m_hSpawnedEntities.AddToTail( hHandle );
SpawnKeyValuesBackToString.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
pkvNode->RecursiveSaveToFile( SpawnKeyValuesBackToString, 0 );
int iLength = SpawnKeyValuesBackToString.TellPut() - 1;
//trim off the start and end that we don't care about
while( TempKeyBuffer[iLength] != '}' )
{
--iLength;
}
--iLength;
char *pStart = TempKeyBuffer;
while( *pStart != '{' )
{
++pStart;
}
pStart += 2; //RecursiveSaveToFile saves "name"\n{\n. We're skipping past that final \n
iLength -= (pStart - TempKeyBuffer);
//don't need to null terminate or space at all
memcpy( &pKVToStringBuffer[iKVStringBufferIndex], pStart, iLength );
SpawnData[iAddIndex].m_pMapData = &pKVToStringBuffer[iKVStringBufferIndex];
SpawnData[iAddIndex].m_iMapDataLength = iLength;
iKVStringBufferIndex += iLength;
CPointCommentaryNode *pCommNode = dynamic_cast<CPointCommentaryNode*>(pNode);
if ( pCommNode )
{
m_iCommentaryNodeCount++;
pCommNode->SetNodeNumber( m_iCommentaryNodeCount );
}
CPointTemplate *pPointTemplate = dynamic_cast<CPointTemplate*>(pNode);
if ( pPointTemplate )
{
PointTemplates[iPointTemplateCount] = pPointTemplate;
++iPointTemplateCount;
}
}
else
{
Warning("Commentary: Failed to spawn commentary entity, type: '%s'\n", pNodeName );
}
// Move to next entity
pkvNode = pkvNode->GetNextKey();
}
ApplyCommentaryModifications();
if( iPointTemplateCount != 0 )
{
CBaseEntity **pSpawnedEntities = NULL;
int iSpawnedEntityCount = m_hSpawnedEntities.Count();
if( iSpawnedEntityCount != 0 )
{
pSpawnedEntities = (CBaseEntity **)stackalloc( sizeof(CBaseEntity *) * m_hSpawnedEntities.Count() );
for( int i = 0; i != iSpawnedEntityCount; ++i )
{
pSpawnedEntities[i] = m_hSpawnedEntities[i].Get();
}
}
MapEntity_ParseAllEntites_SpawnTemplates( PointTemplates, iPointTemplateCount, pSpawnedEntities, SpawnData, iSpawnedEntityCount );
for( int i = iSpawnedEntityCount; --i >= 0; )
{
//remove any entities that were nullified in the point template parsing process
if( pSpawnedEntities[i] == NULL )
m_hSpawnedEntities.Remove(i);
}
}
for( int i = 0; i != m_hSpawnedEntities.Count(); ++i )
{
DispatchSpawn( m_hSpawnedEntities[i] );
}
PrecachePointTemplates();
// 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 );
}
pkvFile->deleteThis();
engine->LockNetworkStringTables( oldLock );
}
void ApplyCommentaryModifications()
{
KeyValues *pkvNode = m_pkvSavedModifications ? m_pkvSavedModifications->GetFirstSubKey() : NULL;
while ( pkvNode )
{
// See if this section has a modify sub group in it.
KeyValues *pModifyBlock = pkvNode->FindKey( "modify" );
if ( pModifyBlock )
{
// find entities with model name
KeyValues *pFindByModelname = pkvNode->FindKey( "find_by_modelname" );
if ( pFindByModelname )
{
CBaseEntity *pEnt = gEntList.FindEntityByModel( NULL, pFindByModelname->GetString() );
while ( pEnt )
{
KeyValues *pKey = pModifyBlock->GetFirstSubKey();
while( pKey )
{
pEnt->KeyValue( pKey->GetName(), pKey->GetString() );
pKey = pKey->GetNextKey();
}
pEnt = gEntList.FindEntityByModel( pEnt, pFindByModelname->GetString() );
}
}
KeyValues *pFindByTargetname = pkvNode->FindKey( "find_by_targetname" );
if ( pFindByTargetname )
{
CBaseEntity *pTarget = gEntList.FindEntityByTarget( NULL, pFindByTargetname->GetString() );
while ( pTarget )
{
KeyValues *pKey = pModifyBlock->GetFirstSubKey();
while( pKey )
{
pTarget->KeyValue( pKey->GetName(), pKey->GetString() );
pKey = pKey->GetNextKey();
}
pTarget = gEntList.FindEntityByTarget( pTarget, pFindByTargetname->GetString() );
}
}
KeyValues *pFindByClassname = pkvNode->FindKey( "find_by_classname" );
if ( pFindByClassname )
{
const char *className = pFindByClassname->GetString();
KeyValues *pCurrKey = pkvNode->GetFirstSubKey();
CBaseEntity* ent = NULL;
// Walk through and select as many entities of this class as number of modify groups in this modify_entity block.
while ( ( ent = gEntList.FindEntityByClassname( ent, className ) ) != NULL && pCurrKey )
{
// We're on the next entity in the game that is of this class.
// Find the next modify block in this group.
while ( pCurrKey && V_stricmp( pCurrKey->GetName(), "modify" ) )
{
pCurrKey = pCurrKey->GetNextKey();
}
if ( NULL != pCurrKey )
{
// If we have a valid modify block, copy the values from the block into this entity.
KeyValues *pKey = pCurrKey->GetFirstSubKey();
while( pKey )
{
ent->KeyValue( pKey->GetName(), pKey->GetString() );
pKey = pKey->GetNextKey();
}
// Advance to the next key to see if it's a "modify" group in the next loop iteration.
pCurrKey = pCurrKey->GetNextKey();
}
}
}
}
// Move to next entity
pkvNode = pkvNode->GetNextKey();
}
}
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();
if ( m_pkvSavedModifications )
{
m_pkvSavedModifications->deleteThis();
}
m_pkvSavedModifications = NULL;
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<CPointCommentaryNode *>( 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;
KeyValues *m_pkvSavedModifications;
CUtlVector< modifiedconvars_t > m_ModifiedConvars;
CUtlVector<EHANDLE> m_hSpawnedEntities;
CHandle<CPointCommentaryNode> m_hCurrentNode;
CHandle<CPointCommentaryNode> m_hActiveCommentaryNode;
CHandle<CPointCommentaryNode> m_hLastCommentaryNode;
friend bool Commentary_IsCommentaryEntity( CBaseEntity *pEntity );
};
CCommentarySystem g_CommentarySystem;
void ApplyCommentaryModifications()
{
g_CommentarySystem.ApplyCommentaryModifications();
}
bool Commentary_IsCommentaryEntity( CBaseEntity *pEntity )
{
for( int i = 0; i != g_CommentarySystem.m_hSpawnedEntities.Count(); ++i )
{
if( pEntity == g_CommentarySystem.m_hSpawnedEntities[i].Get() )
return true;
}
if( pEntity->GetEntityName() == MAKE_STRING( COMMENTARY_SPAWNED_SEMAPHORE ) )
{
return true;
}
return false;
}
void Commentary_AbortCurrentNode( void )
{
if ( g_CommentarySystem.GetActiveNode() )
{
g_CommentarySystem.GetActiveNode()->AbortPlaying();
}
}
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", 0, "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
//===========================================================================================================
CPointCommentaryNode::CPointCommentaryNode()
: m_iNodeNumber( 0 )
{
}
//-----------------------------------------------------------------------------
// 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_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 )
{
#ifndef _GAMECONSOLE
gamestats->Event_Commentary();
#endif
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(), "%s", 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 / 2.0, 0, 1 );
// 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.0 - clamp( flCurTime / flTimeToBlend, 0, 1 );
//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(), "%s", 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 );
}