//========= 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"
# ifndef _XBOX
# 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 ( ) ;
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 )
{
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
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 ( ! Q_strncmp ( pNodeName , " trackinfo " , 9 ) )
{
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 ( ) & &
( Q_strncmp ( pNodeName , " info_remarkable " , sizeof ( " info_remarkable " ) ) ! = 0 ) & &
! 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 ) ;
}
engine - > LockNetworkStringTables ( oldLock ) ;
}
void ApplyCommentaryModifications ( )
{
KeyValues * pkvNode = m_pkvSavedModifications ? m_pkvSavedModifications - > GetFirstSubKey ( ) : NULL ;
while ( pkvNode )
{
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 ( ) ) ;
}
}
}
// 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 " , 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 / 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 ( ) , 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