Merge branch 'develop' into feature/vscript/hook-handler-prototype-1

# Conflicts:
#	sp/src/vscript/vscript_squirrel.nut
This commit is contained in:
Blixibon 2021-05-27 12:07:43 -05:00
commit 0d9a5349b0
180 changed files with 15896 additions and 897 deletions

View File

@ -34,6 +34,13 @@ All contributions must follow the following rules:
use the custom "Mapbase - Source 2013" header used in other Mapbase files as of Mapbase v5.0.
You are encouraged to append an "Author(s)" part to that header in your file in order to clarify who wrote it.
Contributions which do not follow these guidelines cannot be accepted into Mapbase.
Attempting to contribute content which seriously violates the rules above can lead to being blocked from contributing,
especially if done repeatedly.
---
If your contribution is accepted, you may be listed in Mapbase's credits and the README's external content list:
https://github.com/mapbase-source/source-sdk-2013/wiki/Mapbase-Credits#Contributors
https://github.com/mapbase-source/source-sdk-2013/blob/master/README

17
README
View File

@ -20,6 +20,9 @@ Mapbase's main content in this repository may include:
- View rendering changes for drawing 3D skyboxes and RT-based entities
- Countless other fixes and improvements
For more information, view this page:
https://github.com/mapbase-source/source-sdk-2013/wiki/Introduction-to-Mapbase
//===================================================================================================================================================
Mapbase is an open-source project and its contents can be distributed and used at the discretion of its users. However, this project represents many parts of
@ -37,7 +40,7 @@ and repositories (especially ones which are specifically published as free sourc
or complicated code changes accessible and easy to use for level designers and other kinds of Source modders who would otherwise have no idea how to implement them.
*** DISCLAIMER: Mapbase has a strict no-leak-content policy and only allows content created directly by contributors or content originating from open-source repositories.
If you believe any content in Mapbase originates from any leak or unauthorized source (from Valve or otherwise), please contact Blixibon immediately.
If you believe any content in Mapbase originates from any leak or unauthorized source (Valve or otherwise), please contact Blixibon immediately.
Mapbase is intended to be usable by everyone, including licensed Source projects and Steam mods. ***
-- The Alien Swarm SDK was used to backport features and code from newer branches of Source into a Source 2013/Half-Life 2 environment.
@ -91,9 +94,9 @@ Direct contributions:
- https://github.com/mapbase-source/source-sdk-2013/pull/5 (Custom VScript implementation by ReDucTor; was placed into feature branch before being merged in a subsequent PR)
- https://github.com/mapbase-source/source-sdk-2013/pull/3 ("playvideo" command playback fix from Avantate)
- https://github.com/mapbase-source/source-sdk-2013/pull/21 (Various GCC/Linux fixes from z33ky)
- https://github.com/mapbase-source/source-sdk-2013/pull/60 (Adjustment by RoyaleNoir to one of Saul's VDC changes)
- https://github.com/mapbase-source/source-sdk-2013/pull/84 (CS:S viewmodel chirality from 1upD)
- https://github.com/mapbase-source/source-sdk-2013/pull/116 (vgui_movie_display mute keyvalue from Alivebyte/rzkid)
- Demo autorecord code provided by Klems
- cc_emit crash fix provided by 1upD
- Custom HL2 ammo crate models created by Rara (Textures created by Blixibon; This is asset-based and, aside from the SLAM crate, not reflected in the code)
@ -107,6 +110,12 @@ Direct contributions:
=-- https://github.com/mapbase-source/source-sdk-2013/pull/59 (New VScript functions and singletons based on API documentation in later Source/Source 2 games)
=-- https://github.com/mapbase-source/source-sdk-2013/pull/80 (More VScript changes, including support for extremely flexible client/server messaging)
=-- https://github.com/mapbase-source/source-sdk-2013/pull/105 (VScript fixes and optimizations, Vector class extensions, custom convars/commands)
=-- https://github.com/mapbase-source/source-sdk-2013/pull/114 (VScript fixes and extensions)
== Contributions from z33ky:
=-- https://github.com/mapbase-source/source-sdk-2013/pull/21 (Various GCC/Linux compilation fixes)
=-- https://github.com/mapbase-source/source-sdk-2013/pull/95 (Additional GCC/Linux compilation fixes)
=-- https://github.com/mapbase-source/source-sdk-2013/pull/117 (Additional GCC/Linux compilation fixes)
//---------------------------------------------------------------------------------------------------------------------------------------------------
@ -135,6 +144,10 @@ If there is anything missing from this list, please contact Blixibon.
Aside from the content list above, Mapbase has more descriptive and up-to-date credits on this wiki article:
https://github.com/mapbase-source/source-sdk-2013/wiki/Mapbase-Credits
Other relevant articles:
* https://github.com/mapbase-source/source-sdk-2013/wiki/Mapbase-Disclaimers
* https://github.com/mapbase-source/source-sdk-2013/wiki/Frequently-Asked-Questions-(FAQ)
//===================================================================================================================================================
Please see the Source SDK 2013 license below:

View File

@ -6,10 +6,11 @@ MAKEFILE_LINK:=$(THISFILE).link
-include $(MAKEFILE_LINK)
$(MAKEFILE_LINK): $(shell which $(CC)) $(THISFILE)
if [ "$(shell printf "$(shell $(CC) -dumpversion)\n8" | sort -Vr | head -1)" = 8 ]; then \
$(COMPILE.cpp) -o gcc9+support.o gcc9+support.c ;\
# depend on CXX so the correct makefile can be selected when the system is updated
$(MAKEFILE_LINK): $(shell which $(CXX)) $(THISFILE) $(SRCROOT)/devtools/gcc9+support.cpp
@ if [ "$(shell printf "$(shell $(CXX) -dumpversion)\n8" | sort -Vr | head -1)" = 8 ]; then \
ln -sf $(MAKEFILE_BASE).default $@ ;\
else \
$(COMPILE.cpp) -o $(SRCROOT)/devtools/gcc9+support.o $(SRCROOT)/devtools/gcc9+support.cpp &&\
ln -sf $(MAKEFILE_BASE).gcc8 $@ ;\
fi

View File

@ -294,6 +294,7 @@ END_SCRIPTDESC();
ScriptHook_t C_BaseAnimating::g_Hook_OnClientRagdoll;
ScriptHook_t C_BaseAnimating::g_Hook_FireEvent;
ScriptHook_t C_BaseAnimating::g_Hook_BuildTransformations;
#endif
BEGIN_ENT_SCRIPTDESC( C_BaseAnimating, C_BaseEntity, "Animating models client-side" )
@ -310,6 +311,14 @@ BEGIN_ENT_SCRIPTDESC( C_BaseAnimating, C_BaseEntity, "Animating models client-si
DEFINE_SCRIPTFUNC( LookupBone, "Get the named bone id" )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetBoneTransform, "GetBoneTransform", "Get the transform for the specified bone" )
DEFINE_SCRIPTFUNC_NAMED( ScriptSetBoneTransform, "SetBoneTransform", "Set the transform for the specified bone" )
DEFINE_SCRIPTFUNC_NAMED( ScriptAttachEntityToBone, "AttachEntityToBone", "Attaches this entity to the specified target and bone. Also allows for optional local position offset" )
DEFINE_SCRIPTFUNC_NAMED( ScriptRemoveBoneAttachment, "RemoveBoneAttachment", "Removes the specified bone attachment" )
//DEFINE_SCRIPTFUNC( RemoveBoneAttachments, "Removes all bone attachments" )
DEFINE_SCRIPTFUNC( DestroyBoneAttachments, "Destroys all bone attachments" )
DEFINE_SCRIPTFUNC( GetNumBoneAttachments, "Gets the number of bone attachments" )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetBoneAttachment, "GetBoneAttachment", "Gets the specified bone attachment" )
DEFINE_SCRIPTFUNC( SetBodygroup, "Sets a bodygroup")
DEFINE_SCRIPTFUNC( GetBodygroup, "Gets a bodygroup" )
@ -354,6 +363,9 @@ BEGIN_ENT_SCRIPTDESC( C_BaseAnimating, C_BaseEntity, "Animating models client-si
DEFINE_SCRIPTHOOK_PARAM( "event", FIELD_INTEGER )
DEFINE_SCRIPTHOOK_PARAM( "options", FIELD_CSTRING )
END_SCRIPTHOOK()
BEGIN_SCRIPTHOOK( C_BaseAnimating::g_Hook_BuildTransformations, "BuildTransformations", FIELD_VOID, "Called when building bone transformations. Allows VScript to read/write any bone with Get/SetBoneTransform." )
END_SCRIPTHOOK()
#endif
END_SCRIPTDESC();
@ -1538,10 +1550,43 @@ HSCRIPT C_BaseAnimating::ScriptGetAttachmentMatrix( int iAttachment )
void C_BaseAnimating::ScriptGetBoneTransform( int iBone, HSCRIPT hTransform )
{
if (hTransform == NULL)
matrix3x4_t *matTransform = HScriptToClass<matrix3x4_t>( hTransform );
if (matTransform == NULL)
return;
GetBoneTransform( iBone, *HScriptToClass<matrix3x4_t>( hTransform ) );
GetBoneTransform( iBone, *matTransform );
}
void C_BaseAnimating::ScriptSetBoneTransform( int iBone, HSCRIPT hTransform )
{
matrix3x4_t *matTransform = HScriptToClass<matrix3x4_t>( hTransform );
if (matTransform == NULL)
return;
MatrixCopy( *matTransform, GetBoneForWrite( iBone ) );
}
void C_BaseAnimating::ScriptAttachEntityToBone( HSCRIPT attachTarget, int boneIndexAttached, const Vector &bonePosition, const QAngle &boneAngles )
{
C_BaseEntity *pTarget = ToEnt( attachTarget );
if (pTarget == NULL)
return;
AttachEntityToBone( pTarget->GetBaseAnimating(), boneIndexAttached, bonePosition, boneAngles );
}
void C_BaseAnimating::ScriptRemoveBoneAttachment( HSCRIPT boneAttachment )
{
C_BaseEntity *pTarget = ToEnt( boneAttachment );
if (pTarget == NULL)
return;
RemoveBoneAttachment( pTarget->GetBaseAnimating() );
}
HSCRIPT C_BaseAnimating::ScriptGetBoneAttachment( int i )
{
return ToHScript( GetBoneAttachment( i ) );
}
HSCRIPT C_BaseAnimating::ScriptBecomeRagdollOnClient()
@ -1719,7 +1764,23 @@ void C_BaseAnimating::BuildTransformations( CStudioHdr *hdr, Vector *pos, Quater
}
}
#ifdef MAPBASE_VSCRIPT
if (m_ScriptScope.IsInitialized() && g_Hook_BuildTransformations.CanRunInScope(m_ScriptScope))
{
int oldWritableBones = m_BoneAccessor.GetWritableBones();
int oldReadableBones = m_BoneAccessor.GetReadableBones();
m_BoneAccessor.SetWritableBones( BONE_USED_BY_ANYTHING );
m_BoneAccessor.SetReadableBones( BONE_USED_BY_ANYTHING );
// No parameters
//ScriptVariant_t args[] = {};
//ScriptVariant_t returnValue;
g_Hook_BuildTransformations.Call( m_ScriptScope, NULL, NULL /*&returnValue, args*/ );
m_BoneAccessor.SetWritableBones( oldWritableBones );
m_BoneAccessor.SetReadableBones( oldReadableBones );
}
#endif
}
//-----------------------------------------------------------------------------
@ -4808,12 +4869,18 @@ void C_BaseAnimating::GetRagdollInitBoneArrays( matrix3x4_t *pDeltaBones0, matri
}
}
C_ClientRagdoll *C_BaseAnimating::CreateClientRagdoll( bool bRestoring )
{
//DevMsg( "Creating ragdoll at tick %d\n", gpGlobals->tickcount );
return new C_ClientRagdoll( bRestoring );
}
C_BaseAnimating *C_BaseAnimating::CreateRagdollCopy()
{
//Adrian: We now create a separate entity that becomes this entity's ragdoll.
//That way the server side version of this entity can go away.
//Plus we can hook save/restore code to these ragdolls so they don't fall on restore anymore.
C_ClientRagdoll *pRagdoll = new C_ClientRagdoll( false );
C_ClientRagdoll *pRagdoll = CreateClientRagdoll( false );
if ( pRagdoll == NULL )
return NULL;
@ -5366,6 +5433,11 @@ void C_BaseAnimating::StudioFrameAdvance()
if ( flNewCycle < 0.0f || flNewCycle >= 1.0f )
{
if (flNewCycle >= 1.0f)
{
ReachedEndOfSequence();
}
if ( IsSequenceLooping( hdr, GetSequence() ) )
{
flNewCycle -= (int)(flNewCycle);

View File

@ -38,6 +38,7 @@ class C_BaseClientShader
*/
class IRagdoll;
class C_ClientRagdoll;
class CIKContext;
class CIKState;
class ConVar;
@ -301,6 +302,7 @@ public:
bool IsRagdoll() const;
bool IsAboutToRagdoll() const;
virtual C_BaseAnimating *BecomeRagdollOnClient();
virtual C_ClientRagdoll *CreateClientRagdoll( bool bRestoring = false );
C_BaseAnimating *CreateRagdollCopy();
bool InitAsClientRagdoll( const matrix3x4_t *pDeltaBones0, const matrix3x4_t *pDeltaBones1, const matrix3x4_t *pCurrentBonePosition, float boneDt, bool bFixedConstraints=false );
void IgniteRagdoll( C_BaseAnimating *pSource );
@ -354,6 +356,8 @@ public:
void ClientSideAnimationChanged();
virtual unsigned int ComputeClientSideAnimationFlags();
virtual void ReachedEndOfSequence() { return; }
virtual void ResetClientsideFrame( void ) { SetCycle( 0 ); }
void SetCycle( float flCycle );
@ -464,6 +468,11 @@ public:
HSCRIPT ScriptGetAttachmentMatrix(int iAttachment);
void ScriptGetBoneTransform( int iBone, HSCRIPT hTransform );
void ScriptSetBoneTransform( int iBone, HSCRIPT hTransform );
void ScriptAttachEntityToBone( HSCRIPT attachTarget, int boneIndexAttached, const Vector &bonePosition, const QAngle &boneAngles );
void ScriptRemoveBoneAttachment( HSCRIPT boneAttachment );
HSCRIPT ScriptGetBoneAttachment( int i );
int ScriptGetSequenceActivity( int iSequence ) { return GetSequenceActivity( iSequence ); }
float ScriptGetSequenceMoveDist( int iSequence ) { return GetSequenceMoveDist( GetModelPtr(), iSequence ); }
@ -482,6 +491,7 @@ public:
static ScriptHook_t g_Hook_OnClientRagdoll;
static ScriptHook_t g_Hook_FireEvent;
static ScriptHook_t g_Hook_BuildTransformations;
float ScriptGetPoseParameter(const char* szName);
#endif

View File

@ -428,6 +428,10 @@ BEGIN_RECV_TABLE_NOBASE( C_BaseEntity, DT_AnimTimeMustBeFirst )
RecvPropInt( RECVINFO(m_flAnimTime), 0, RecvProxy_AnimTime ),
END_RECV_TABLE()
#ifdef MAPBASE_VSCRIPT
ScriptHook_t CBaseEntity::g_Hook_UpdateOnRemove;
#endif
BEGIN_ENT_SCRIPTDESC_ROOT( C_BaseEntity, "Root class of all client-side entities" )
DEFINE_SCRIPT_INSTANCE_HELPER( &g_BaseEntityScriptInstanceHelper )
DEFINE_SCRIPTFUNC_NAMED( GetAbsOrigin, "GetOrigin", "" )
@ -550,7 +554,10 @@ BEGIN_ENT_SCRIPTDESC_ROOT( C_BaseEntity, "Root class of all client-side entities
DEFINE_SCRIPTFUNC_NAMED( ScriptSetContextThink, "SetContextThink", "Set a think function on this entity." )
#endif
DEFINE_SIMPLE_SCRIPTHOOK( CBaseEntity::g_Hook_UpdateOnRemove, "UpdateOnRemove", FIELD_VOID, "Called when the entity is being removed." )
#endif // MAPBASE_VSCRIPT
END_SCRIPTDESC();
@ -1340,6 +1347,12 @@ void C_BaseEntity::Term()
if ( m_hScriptInstance )
{
#ifdef MAPBASE_VSCRIPT
if ( m_ScriptScope.IsInitialized() )
{
g_Hook_UpdateOnRemove.Call( m_ScriptScope, NULL, NULL );
}
#endif
g_pScriptVM->RemoveInstance( m_hScriptInstance );
m_hScriptInstance = NULL;

View File

@ -164,10 +164,9 @@ struct thinkfunc_t
#ifdef MAPBASE_VSCRIPT
struct scriptthinkfunc_t
{
int m_nNextThinkTick;
HSCRIPT m_hfnThink;
unsigned short m_iContextHash;
bool m_bNoParam;
float m_flNextThink;
HSCRIPT m_hfnThink;
unsigned m_iContextHash;
};
#endif
@ -295,6 +294,8 @@ public:
string_t m_iszScriptId;
#ifdef MAPBASE_VSCRIPT
CScriptScope m_ScriptScope;
static ScriptHook_t g_Hook_UpdateOnRemove;
#endif
// IClientUnknown overrides.
@ -400,7 +401,7 @@ public:
#ifdef MAPBASE_VSCRIPT
// "I don't know why but wrapping entindex() works, while calling it directly crashes."
inline int C_BaseEntity::GetEntityIndex() const { return entindex(); }
inline int GetEntityIndex() const { return entindex(); }
#endif
// This works for client-only entities and returns the GetEntryIndex() of the entity's handle,

View File

@ -15,7 +15,7 @@
#include "ammodef.h"
#include "vprof.h"
#include "view.h"
#include "vstdlib/ikeyvaluessystem.h"
#include "vstdlib/IKeyValuesSystem.h"
#ifdef MAPBASE
#include "usermessages.h"
#endif
@ -666,7 +666,8 @@ void CIconLesson::UpdateInactive()
CUtlBuffer msg_data;
msg_data.PutChar( 1 );
msg_data.PutString( m_szHudHint.String() );
usermessages->DispatchUserMessage( usermessages->LookupUserMessage( "KeyHintText" ), bf_read( msg_data.Base(), msg_data.TellPut() ) );
bf_read msg( msg_data.Base(), msg_data.TellPut() );
usermessages->DispatchUserMessage( usermessages->LookupUserMessage( "KeyHintText" ), msg );
}
#endif
@ -1039,40 +1040,40 @@ Vector CIconLesson::GetIconTargetPosition( C_BaseEntity *pIconTarget )
#define LESSON_VARIABLE_INIT_SYMBOL( _varEnum, _varName, _varType ) g_n##_varEnum##Symbol = KeyValuesSystem()->GetSymbolForString( #_varEnum );
#define LESSON_SCRIPT_STRING_ADD_TO_MAP( _varEnum, _varName, _varType ) g_NameToTypeMap.Insert( #_varEnum, LESSON_VARIABLE_##_varEnum## );
#define LESSON_SCRIPT_STRING_ADD_TO_MAP( _varEnum, _varName, _varType ) g_NameToTypeMap.Insert( #_varEnum, LESSON_VARIABLE_##_varEnum );
// Create enum value
#define LESSON_VARIABLE_ENUM( _varEnum, _varName, _varType ) LESSON_VARIABLE_##_varEnum##,
#define LESSON_VARIABLE_ENUM( _varEnum, _varName, _varType ) LESSON_VARIABLE_##_varEnum,
// Init info call
#define LESSON_VARIABLE_INIT_INFO_CALL( _varEnum, _varName, _varType ) g_pLessonVariableInfo[ LESSON_VARIABLE_##_varEnum## ].Init_##_varEnum##();
#define LESSON_VARIABLE_INIT_INFO_CALL( _varEnum, _varName, _varType ) g_pLessonVariableInfo[ LESSON_VARIABLE_##_varEnum ].Init_##_varEnum();
// Init info
#define LESSON_VARIABLE_INIT_INFO( _varEnum, _varName, _varType ) \
void Init_##_varEnum##() \
void Init_##_varEnum() \
{ \
iOffset = offsetof( CScriptedIconLesson, CScriptedIconLesson::##_varName## ); \
iOffset = offsetof( CScriptedIconLesson, CScriptedIconLesson::_varName ); \
varType = LessonParamTypeFromString( #_varType ); \
}
#define LESSON_VARIABLE_INIT_INFO_BOOL( _varEnum, _varName, _varType ) \
void Init_##_varEnum##() \
void Init_##_varEnum() \
{ \
iOffset = offsetof( CScriptedIconLesson, CScriptedIconLesson::##_varName## ); \
iOffset = offsetof( CScriptedIconLesson, CScriptedIconLesson::_varName ); \
varType = FIELD_BOOLEAN; \
}
#define LESSON_VARIABLE_INIT_INFO_EHANDLE( _varEnum, _varName, _varType ) \
void Init_##_varEnum##() \
void Init_##_varEnum() \
{ \
iOffset = offsetof( CScriptedIconLesson, CScriptedIconLesson::##_varName## ); \
iOffset = offsetof( CScriptedIconLesson, CScriptedIconLesson::_varName ); \
varType = FIELD_EHANDLE; \
}
#define LESSON_VARIABLE_INIT_INFO_STRING( _varEnum, _varName, _varType ) \
void Init_##_varEnum##() \
void Init_##_varEnum() \
{ \
iOffset = offsetof( CScriptedIconLesson, CScriptedIconLesson::##_varName## ); \
iOffset = offsetof( CScriptedIconLesson, CScriptedIconLesson::_varName ); \
varType = FIELD_STRING; \
}
@ -1094,15 +1095,15 @@ Vector CIconLesson::GetIconTargetPosition( C_BaseEntity *pIconTarget )
// Process the element action on this variable
#define PROCESS_LESSON_ACTION( _varEnum, _varName, _varType ) \
case LESSON_VARIABLE_##_varEnum##:\
case LESSON_VARIABLE_##_varEnum:\
return ProcessElementAction( pLessonElement->iAction, pLessonElement->bNot, #_varName, _varName, &pLessonElement->szParam, eventParam_float );
#define PROCESS_LESSON_ACTION_EHANDLE( _varEnum, _varName, _varType ) \
case LESSON_VARIABLE_##_varEnum##:\
case LESSON_VARIABLE_##_varEnum:\
return ProcessElementAction( pLessonElement->iAction, pLessonElement->bNot, #_varName, _varName, &pLessonElement->szParam, eventParam_float, eventParam_BaseEntity, eventParam_string );
#define PROCESS_LESSON_ACTION_STRING( _varEnum, _varName, _varType ) \
case LESSON_VARIABLE_##_varEnum##:\
case LESSON_VARIABLE_##_varEnum:\
return ProcessElementAction( pLessonElement->iAction, pLessonElement->bNot, #_varName, &_varName, &pLessonElement->szParam, eventParam_string );
// Init the variable from the script (or a convar)
@ -2957,7 +2958,7 @@ bool CScriptedIconLesson::ProcessElementAction( int iAction, bool bNot, const ch
{
if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
{
ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->HealthFraction() ", pchVarName, pchVarName );
ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->HealthFraction() ", pchVarName );
ConColorMsg( CBaseLesson::m_rgbaVerboseName, "... " );
ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ">= [%s] " ) : ( "< [%s] " ), pchParamName->String() );
ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", fParam );
@ -2969,7 +2970,7 @@ bool CScriptedIconLesson::ProcessElementAction( int iAction, bool bNot, const ch
if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() )
{
ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->HealthFraction() ", pchVarName, pchVarName );
ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->HealthFraction() ", pchVarName );
ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f ", pVar->HealthFraction() );
ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ">= [%s] " ) : ( "< [%s] " ), pchParamName->String() );
ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", fParam );

View File

@ -426,7 +426,7 @@ private:
LessonEvent_t * AddUpdateEvent( void );
private:
static CUtlDict< int, int > CScriptedIconLesson::LessonActionMap;
static CUtlDict< int, int > LessonActionMap;
EHANDLE m_hLocalPlayer;
float m_fOutput;

View File

@ -25,6 +25,11 @@ ConVar cl_globallight_freeze( "cl_globallight_freeze", "0" );
// You can set these as KV anyway.
ConVar cl_globallight_xoffset( "cl_globallight_xoffset", "0" );
ConVar cl_globallight_yoffset( "cl_globallight_yoffset", "0" );
static ConVar cl_globallight_slopescaledepthbias_shadowmap( "cl_globallight_slopescaledepthbias_shadowmap", "16", FCVAR_CHEAT );
static ConVar cl_globallight_shadowfiltersize( "cl_globallight_shadowfiltersize", "0.1", FCVAR_CHEAT );
static ConVar cl_globallight_depthbias_shadowmap( "cl_globallight_depthbias_shadowmap", "0.00001", FCVAR_CHEAT );
static ConVar cl_globallight_depthres( "cl_globallight_depthres", "8192", FCVAR_CHEAT );
#else
ConVar cl_globallight_xoffset( "cl_globallight_xoffset", "-800" );
ConVar cl_globallight_yoffset( "cl_globallight_yoffset", "1600" );
@ -286,16 +291,21 @@ void C_GlobalLight::ClientThink()
state.m_bOrtho = false;
}
#ifndef MAPBASE // Don't draw that huge debug thing
#ifdef MAPBASE
//state.m_bDrawShadowFrustum = true; // Don't draw that huge debug thing
state.m_flShadowMapResolution = cl_globallight_depthres.GetFloat();
state.m_flShadowFilterSize = cl_globallight_shadowfiltersize.GetFloat();
state.m_flShadowSlopeScaleDepthBias = cl_globallight_slopescaledepthbias_shadowmap.GetFloat();
state.m_flShadowDepthBias = cl_globallight_depthbias_shadowmap.GetFloat();
state.m_bEnableShadows = m_bEnableShadows;
state.m_pSpotlightTexture = m_SpotlightTexture;
state.m_nSpotlightTextureFrame = m_nSpotlightTextureFrame;
#else
state.m_bDrawShadowFrustum = true;
#endif
/*state.m_flShadowSlopeScaleDepthBias = g_pMaterialSystemHardwareConfig->GetShadowSlopeScaleDepthBias();;
state.m_flShadowDepthBias = g_pMaterialSystemHardwareConfig->GetShadowDepthBias();*/
state.m_bEnableShadows = m_bEnableShadows;
state.m_pSpotlightTexture = m_SpotlightTexture;
#ifdef MAPBASE
state.m_nSpotlightTextureFrame = m_nSpotlightTextureFrame;
#else
state.m_nSpotlightTextureFrame = 0;
#endif

View File

@ -25,8 +25,8 @@
#include "tier0/memdbgon.h"
#ifdef ASW_PROJECTED_TEXTURES
static ConVar mat_slopescaledepthbias_shadowmap( "mat_slopescaledepthbias_shadowmap", "16", FCVAR_CHEAT );
static ConVar mat_depthbias_shadowmap( "mat_depthbias_shadowmap", "0.00001", FCVAR_CHEAT );
extern ConVarRef mat_slopescaledepthbias_shadowmap;
extern ConVarRef mat_depthbias_shadowmap;
float C_EnvProjectedTexture::m_flVisibleBBoxMinHeight = -FLT_MAX;

View File

@ -9,7 +9,7 @@
#include "GameEventListener.h"
#include "vgui_controls/phandle.h"
#include "vgui_controls/PHandle.h"
class CBaseLesson;

View File

@ -13,6 +13,7 @@
IMPLEMENT_CLIENTCLASS_DT( C_MovieDisplay, DT_MovieDisplay, CMovieDisplay )
RecvPropBool( RECVINFO( m_bEnabled ) ),
RecvPropBool( RECVINFO( m_bLooping ) ),
RecvPropBool( RECVINFO( m_bMuted ) ),
RecvPropString( RECVINFO( m_szMovieFilename ) ),
RecvPropString( RECVINFO( m_szGroupName ) ),
END_RECV_TABLE()

View File

@ -20,6 +20,7 @@ public:
bool IsEnabled( void ) const { return m_bEnabled; }
bool IsLooping( void ) const { return m_bLooping; }
bool IsMuted(void) const { return m_bMuted; }
const char *GetMovieFilename( void ) const { return m_szMovieFilename; }
const char *GetGroupName( void ) const { return m_szGroupName; }
@ -27,6 +28,7 @@ public:
private:
bool m_bEnabled;
bool m_bLooping;
bool m_bMuted;
char m_szMovieFilename[128];
char m_szGroupName[128];
};

View File

@ -73,6 +73,27 @@ IMPLEMENT_CLIENTCLASS_DT_NOBASE( C_RopeKeyframe, DT_RopeKeyframe, CRopeKeyframe
RecvPropInt( RECVINFO( m_iParentAttachment ) ),
END_RECV_TABLE()
#ifdef MAPBASE_VSCRIPT
BEGIN_ENT_SCRIPTDESC( C_RopeKeyframe, C_BaseEntity, "The clientside class of move_rope and keyframe_rope" )
DEFINE_SCRIPTFUNC( GetNodePosition, "Gets the position of the specified node index" )
DEFINE_SCRIPTFUNC( GetNumNodes, "Gets the number of nodes available" )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetStartEntity, "GetStartEntity", "Gets the rope's start entity" )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetEndEntity, "GetEndEntity", "Gets the rope's end entity" )
DEFINE_SCRIPTFUNC( SetupHangDistance, "Sets the rope's hang distance" )
DEFINE_SCRIPTFUNC( SetSlack, "Sets the rope's slack value (extra length)" )
DEFINE_SCRIPTFUNC( GetRopeFlags, "Gets the rope's flags" )
DEFINE_SCRIPTFUNC( SetRopeFlags, "Sets the rope's flags" )
DEFINE_SCRIPTFUNC( SetColorMod, "Sets the rope's color mod value" )
DEFINE_SCRIPTFUNC( ShakeRope, "Shakes the rope with the specified center, radius, and magnitude" )
DEFINE_SCRIPTFUNC( AnyPointsMoved, "Returns true if any points have moved recently" )
END_SCRIPTDESC();
#endif
#define ROPE_IMPULSE_SCALE 20
#define ROPE_IMPULSE_DECAY 0.95
@ -2022,6 +2043,25 @@ bool C_RopeKeyframe::GetAttachment( int number, Vector &origin, QAngle &angles )
return false;
}
#ifdef MAPBASE
const Vector &C_RopeKeyframe::GetNodePosition( int index )
{
int nNodes = m_RopePhysics.NumNodes();
if ( index >= nNodes || nNodes < 2 )
{
Warning( "C_RopeKeyframe::GetNodePosition(): Invalid node index %i (number of nodes is %i)\n", index, nNodes );
return vec3_origin;
}
return m_RopePhysics.GetNode( index )->m_vPredicted;
}
int C_RopeKeyframe::GetNumNodes()
{
return m_RopePhysics.NumNodes();
}
#endif
bool C_RopeKeyframe::AnyPointsMoved()
{
#ifdef MAPBASE

View File

@ -33,6 +33,9 @@ public:
DECLARE_CLASS( C_RopeKeyframe, C_BaseEntity );
DECLARE_CLIENTCLASS();
#ifdef MAPBASE_VSCRIPT
DECLARE_ENT_SCRIPTDESC();
#endif
private:
@ -142,6 +145,11 @@ public:
virtual bool GetAttachment( int number, Vector &origin );
virtual bool GetAttachmentVelocity( int number, Vector &originVel, Quaternion &angleVel );
#ifdef MAPBASE
const Vector &GetNodePosition( int index );
int GetNumNodes();
#endif
private:
void FinishInit( const char *pMaterialName );
@ -166,6 +174,11 @@ private:
void ReceiveMessage( int classID, bf_read &msg );
bool CalculateEndPointAttachment( C_BaseEntity *pEnt, int iAttachment, Vector &vPos, QAngle *pAngles );
#ifdef MAPBASE_VSCRIPT
HSCRIPT ScriptGetStartEntity() { return ToHScript( GetStartEntity() ); }
HSCRIPT ScriptGetEndEntity() { return ToHScript( GetEndEntity() ); }
#endif
private:
// Track which links touched something last frame. Used to prevent wind from gusting on them.

View File

@ -166,7 +166,6 @@ inline bool FStrEq(const char *sz1, const char *sz2)
{
#ifdef MAPBASE
// V_stricmp() already checks if the pointers are equal, so having a comparison here is pointless.
// I had few reasons to do this, but maybe you'll thank me later.
return ( V_stricmp(sz1, sz2) == 0 );
#else
return ( sz1 == sz2 || V_stricmp(sz1, sz2) == 0 );

View File

@ -536,7 +536,6 @@ $Project
"$SRCDIR\public\dt_utlvector_recv.cpp" \
"$SRCDIR\public\filesystem_helpers.cpp" \
"$SRCDIR\public\interpolatortypes.cpp" \
"$SRCDIR\game\shared\interval.cpp" \
"$SRCDIR\common\language.cpp" \
"$SRCDIR\public\networkvar.cpp" \
"$SRCDIR\common\randoverride.cpp" \
@ -1107,6 +1106,7 @@ $Project
$File "$SRCDIR\public\vgui_controls\WizardSubPanel.h"
$File "$SRCDIR\public\worldsize.h"
$File "$SRCDIR\public\zip_uncompressed.h"
$File "$SRCDIR\public\tier1\interval.h"
//Haptics
$File "$SRCDIR\public\haptics\ihaptics.h" [$WIN32]
$File "$SRCDIR\public\haptics\haptic_utils.h" [$WIN32]
@ -1163,7 +1163,6 @@ $Project
$File "$SRCDIR\game\shared\igamesystem.h"
$File "$SRCDIR\game\shared\imovehelper.h"
$File "$SRCDIR\game\shared\in_buttons.h"
$File "$SRCDIR\game\shared\interval.h"
$File "$SRCDIR\game\shared\iplayeranimstate.h"
$File "$SRCDIR\game\shared\ipredictionsystem.h"
$File "$SRCDIR\game\shared\itempents.h"

View File

@ -12,6 +12,7 @@ $Configuration
$PreprocessorDefinitions "$BASE;MAPBASE_RPC;DISCORD_RPC;STEAM_RPC" [$MAPBASE_RPC]
$PreprocessorDefinitions "$BASE;MAPBASE_VSCRIPT" [$MAPBASE_VSCRIPT]
$PreprocessorDefinitions "$BASE;NEW_RESPONSE_SYSTEM" [$NEW_RESPONSE_SYSTEM]
}
}
@ -34,6 +35,7 @@ $Project
$File "c_movie_display.cpp"
$File "c_movie_display.h"
$File "vgui_movie_display.cpp"
$File "convarproxy.cpp"
$Folder "Mapbase"
{

View File

@ -1259,7 +1259,7 @@ void ClientModeShared::FireGameEvent( IGameEvent *event )
}
}
if ( team == 0 && GetLocalTeam() > 0 )
if ( team == 0 && GetLocalTeam() )
{
bValidTeam = false;
}

View File

@ -125,6 +125,11 @@ ConVar r_threaded_client_shadow_manager( "r_threaded_client_shadow_manager", "1"
ConVar r_threaded_client_shadow_manager( "r_threaded_client_shadow_manager", "0" );
#endif
#ifdef MAPBASE
ConVarRef mat_slopescaledepthbias_shadowmap( "mat_slopescaledepthbias_shadowmap" );
ConVarRef mat_depthbias_shadowmap( "mat_depthbias_shadowmap" );
#endif
#ifdef _WIN32
#pragma warning( disable: 4701 )
#endif
@ -1424,6 +1429,15 @@ bool CClientShadowMgr::Init()
materials->AddRestoreFunc( ShadowRestoreFunc );
#ifdef MAPBASE
// These need to be referenced here since the cvars don't exist in the initial declaration
mat_slopescaledepthbias_shadowmap = ConVarRef( "mat_slopescaledepthbias_shadowmap" );
mat_depthbias_shadowmap = ConVarRef( "mat_depthbias_shadowmap" );
mat_slopescaledepthbias_shadowmap.SetValue( "16" ); // Would do something like 2 here, but it causes citizens to look weird under flashlights
mat_depthbias_shadowmap.SetValue( "0.00005" );
#endif
return true;
}

View File

@ -0,0 +1,113 @@
//========= Copyright © 1996-2008, Valve Corporation, All rights reserved. ============//
//
// Material proxy to stuff a convar into a material var.
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
// identifier was truncated to '255' characters in the debug information
//#pragma warning(disable: 4786)
#include "convar.h"
#include "MaterialSystem/imaterialproxy.h"
#include "materialsystem/IMaterialVar.h"
//#include "imaterialproxydict.h"
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
class CConVarMaterialProxy: public IMaterialProxy
{
public:
CConVarMaterialProxy()
: m_pResult( NULL ),
m_conVarRef( "", true )
{
}
virtual ~CConVarMaterialProxy()
{
}
virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues )
{
const char *pResult = pKeyValues->GetString( "resultVar" );
if ( !pResult )
return false;
bool found;
m_pResult = pMaterial->FindVar( pResult, &found );
if ( !found )
{
m_pResult = NULL;
return false;
}
/*
if ( !Q_stricmp( pResult, "$alpha" ) )
{
pMaterial->SetMaterialVarFlag( MATERIAL_VAR_ALPHA_MODIFIED_BY_PROXY, true );
}
*/
pResult = pKeyValues->GetString( "convar" );
if( !pResult )
{
return false;
}
m_conVarRef.Init( pResult, false );
if ( !m_conVarRef.IsValid() )
{
return false;
}
return true;
}
virtual void OnBind( void* )
{
switch( m_pResult->GetType() )
{
case MATERIAL_VAR_TYPE_VECTOR:
{
float f = m_conVarRef.GetFloat();
Vector4D vec( f, f, f, f );
m_pResult->SetVecValue( vec.Base(), m_pResult->VectorSize() );
}
break;
#ifdef MAPBASE
case MATERIAL_VAR_TYPE_STRING:
m_pResult->SetStringValue( m_conVarRef.GetString() );
break;
#endif
case MATERIAL_VAR_TYPE_INT:
m_pResult->SetIntValue( m_conVarRef.GetInt() );
break;
case MATERIAL_VAR_TYPE_FLOAT:
default:
m_pResult->SetFloatValue( m_conVarRef.GetFloat() );
break;
}
}
virtual IMaterial *GetMaterial()
{
return m_pResult->GetOwningMaterial();
}
virtual void Release()
{
}
protected:
IMaterialVar *m_pResult;
ConVarRef m_conVarRef;
};
EXPOSE_INTERFACE( CConVarMaterialProxy, IMaterialProxy, "ConVar" IMATERIAL_PROXY_INTERFACE_VERSION );

View File

@ -5,6 +5,10 @@
//=============================================================================
#include "cbase.h"
#ifdef MAPBASE
#include "proxyentity.h"
#include "materialsystem/imaterialvar.h"
#endif
class C_PropScalable : public C_BaseAnimating
{
@ -194,3 +198,56 @@ void C_PropScalable::GetRenderBounds( Vector &theMins, Vector &theMaxs )
Assert( theMins.IsValid() && theMaxs.IsValid() );
}
#ifdef MAPBASE
ConVar r_coreball_update_sphere_center( "r_coreball_update_sphere_center", "1", FCVAR_NONE, "Allows prop_coreball to update its center to the entity's origin" );
class CCoreBallUpdateMaterialProxy : public CEntityMaterialProxy
{
public:
CCoreBallUpdateMaterialProxy()
{
m_pMaterial = NULL;
m_pSphereCenter = NULL;
}
virtual ~CCoreBallUpdateMaterialProxy()
{
}
virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues )
{
m_pMaterial = pMaterial;
bool found;
m_pSphereCenter = m_pMaterial->FindVar( "$spherecenter", &found );
if( !found )
{
m_pSphereCenter = NULL;
return false;
}
return true;
}
virtual void OnBind( C_BaseEntity *pC_BaseEntity )
{
if (r_coreball_update_sphere_center.GetBool())
{
const Vector &origin = pC_BaseEntity->GetAbsOrigin();
m_pSphereCenter->SetVecValue( origin.x, origin.y, origin.z );
}
else
{
// Just continuously bind the old hacked value (TODO: Optimize so it's not just assigning the same value constantly?)
m_pSphereCenter->SetVecValue( 2688.0, 12139.0, 5170.0 );
}
}
virtual IMaterial *GetMaterial()
{
return m_pMaterial;
}
protected:
IMaterial *m_pMaterial;
IMaterialVar *m_pSphereCenter;
};
EXPOSE_INTERFACE( CCoreBallUpdateMaterialProxy, IMaterialProxy, "CoreBallUpdate" IMATERIAL_PROXY_INTERFACE_VERSION );
#endif

View File

@ -52,8 +52,8 @@ static ConVar r_flashlightladderdist( "r_flashlightladderdist", "40.0", FCVAR_CH
static ConVar mat_slopescaledepthbias_shadowmap( "mat_slopescaledepthbias_shadowmap", "16", FCVAR_CHEAT );
static ConVar mat_depthbias_shadowmap( "mat_depthbias_shadowmap", "0.0005", FCVAR_CHEAT );
#else
static ConVar mat_slopescaledepthbias_shadowmap( "mat_slopescaledepthbias_shadowmap", "4", FCVAR_CHEAT );
static ConVar mat_depthbias_shadowmap( "mat_depthbias_shadowmap", "0.00001", FCVAR_CHEAT );
extern ConVarRef mat_slopescaledepthbias_shadowmap;
extern ConVarRef mat_depthbias_shadowmap;
#endif
#ifdef MAPBASE
static ConVar r_flashlighttextureoverride( "r_flashlighttextureoverride", "", FCVAR_CHEAT );

View File

@ -150,6 +150,10 @@ private:
static const int ENTRY_IN_USE = -2;
};
#ifdef MAPBASE_VSCRIPT
// For unregistration boundary check
public:
#endif
CUtlVector< GlowObjectDefinition_t > m_GlowObjectDefinitions;
int m_nFirstFreeSlot;
};

View File

@ -10,7 +10,7 @@
#include "iclientmode.h"
#include <vgui/ILocalize.h>
#include <vgui/ISurface.h>
#include <vgui/IVGUI.h>
#include <vgui/IVGui.h>
#include <vgui_controls/EditablePanel.h>
#include <vgui_controls/Controls.h>
#include <vgui_controls/Label.h>

View File

@ -34,7 +34,7 @@
#define LOCATOR_ICON_FX_FADE_OUT 0x00000800 // Set when deactivated so it can smoothly vanish
#define LOCATOR_ICON_FX_FADE_IN 0x00001000 // Set when activated so it can smoothly appear
#include "tier1/UtlSymbol.h"
#include "tier1/utlsymbol.h"
// See comments in UtlSymbol on why this is useful
DECLARE_PRIVATE_SYMBOLTYPE( CGameInstructorSymbol );

View File

@ -69,6 +69,16 @@ BEGIN_SIMPLE_DATADESC( CRagdoll )
DEFINE_RAGDOLL_ELEMENT( 21 ),
DEFINE_RAGDOLL_ELEMENT( 22 ),
DEFINE_RAGDOLL_ELEMENT( 23 ),
#ifdef MAPBASE
DEFINE_RAGDOLL_ELEMENT( 24 ),
DEFINE_RAGDOLL_ELEMENT( 25 ),
DEFINE_RAGDOLL_ELEMENT( 26 ),
DEFINE_RAGDOLL_ELEMENT( 27 ),
DEFINE_RAGDOLL_ELEMENT( 28 ),
DEFINE_RAGDOLL_ELEMENT( 29 ),
DEFINE_RAGDOLL_ELEMENT( 30 ),
DEFINE_RAGDOLL_ELEMENT( 31 ),
#endif
END_DATADESC()

View File

@ -7,16 +7,16 @@
#include "cbase.h"
#include "c_vguiscreen.h"
#include "vgui_controls/Label.h"
#include "vgui_BitmapPanel.h"
#include <vgui/IVGUI.h>
#include "vgui_bitmappanel.h"
#include <vgui/IVGui.h>
#include "c_slideshow_display.h"
#include "ienginevgui.h"
#include "fmtstr.h"
#include "vgui_controls/ImagePanel.h"
#include <vgui/ISurface.h>
#include "video/ivideoservices.h"
#include "engine/ienginesound.h"
#include "VGUIMatSurface/IMatSystemSurface.h"
#include "engine/IEngineSound.h"
#include "VGuiMatSurface/IMatSystemSurface.h"
#include "c_movie_display.h"
// NOTE: This has to be the last file included!
@ -81,7 +81,6 @@ private:
bool m_bBlackBackground;
bool m_bSlaved;
bool m_bInitialized;
bool m_bLastActiveState; // HACK: I'd rather get a real callback...
// VGUI specifics
@ -110,10 +109,9 @@ CMovieDisplayScreen::CMovieDisplayScreen( vgui::Panel *parent, const char *panel
m_bBlackBackground = true;
m_bSlaved = false;
m_bInitialized = false;
// Add ourselves to the global list of movie displays
g_MovieDisplays.AddToTail( this );
//m_VideoMaterial->SetMuted(true);
m_bLastActiveState = IsActive();
}
@ -295,6 +293,11 @@ void CMovieDisplayScreen::UpdateMovie( void )
// OnVideoOver();
// StopPlayback();
}
if (!m_hScreenEntity->IsMuted())
{
m_VideoMaterial->SetMuted(false);
}
}
}
@ -368,7 +371,7 @@ bool CMovieDisplayScreen::BeginPlayback( const char *pFilename )
Q_strncpy( szMaterialName, pFilename, sizeof(szMaterialName) );
}
const char *pszMaterialName = CFmtStrN<128>( "VideoMaterial_", m_hScreenEntity->GetEntityName() );
const char *pszMaterialName = CFmtStrN<128>( "VideoMaterial_%s", m_hScreenEntity->GetEntityName() );
m_VideoMaterial = g_pVideo->CreateVideoMaterial( pszMaterialName, pFilename, "GAME",
VideoPlaybackFlags::DEFAULT_MATERIAL_OPTIONS,
VideoSystem::DETERMINE_FROM_FILE_EXTENSION/*, m_bAllowAlternateMedia*/ );
@ -376,14 +379,17 @@ bool CMovieDisplayScreen::BeginPlayback( const char *pFilename )
if ( m_VideoMaterial == NULL )
return false;
m_VideoMaterial->SetMuted( true ); // FIXME: Allow?
m_VideoMaterial->SetMuted(true); // FIXME: Allow?
if ( m_hScreenEntity->IsLooping() )
{
m_VideoMaterial->SetLooping( true );
}
if ( m_VideoMaterial->HasAudio() )
if ( m_VideoMaterial->HasAudio())
{
// We want to be the sole audio source
enginesound->NotifyBeginMoviePlayback();

View File

@ -165,6 +165,8 @@ public:
void SetVarFloat( int i, float value );
void SetVarVector( int i, const Vector &value );
const char *GetVarName( int i );
private:
IMaterialVar *m_MaterialVars[SCRIPT_MAT_PROXY_MAX_VARS];
@ -194,12 +196,19 @@ BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptMaterialProxy, "CScriptMaterialProxy", "Mate
DEFINE_SCRIPTFUNC( SetVarInt, "Sets a material var's int value" )
DEFINE_SCRIPTFUNC( SetVarFloat, "Sets a material var's float value" )
DEFINE_SCRIPTFUNC( SetVarVector, "Sets a material var's vector value" )
DEFINE_SCRIPTFUNC( GetVarName, "Gets a material var's name" )
END_SCRIPTDESC();
CScriptMaterialProxy::CScriptMaterialProxy()
{
m_hScriptInstance = NULL;
m_hFuncOnBind = NULL;
for (int i = 0; i < SCRIPT_MAT_PROXY_MAX_VARS; i++)
{
m_MaterialVars[i] = NULL;
}
}
CScriptMaterialProxy::~CScriptMaterialProxy()
@ -316,18 +325,20 @@ void CScriptMaterialProxy::TermScript()
void CScriptMaterialProxy::OnBind( void *pRenderable )
{
if( !pRenderable )
return;
if (m_hFuncOnBind != NULL)
{
IClientRenderable *pRend = ( IClientRenderable* )pRenderable;
C_BaseEntity *pEnt = pRend->GetIClientUnknown()->GetBaseEntity();
if ( pEnt )
C_BaseEntity *pEnt = NULL;
if (pRenderable)
{
g_pScriptVM->SetValue( m_ScriptScope, "entity", pEnt->GetScriptInstance() );
IClientRenderable *pRend = (IClientRenderable*)pRenderable;
pEnt = pRend->GetIClientUnknown()->GetBaseEntity();
if ( pEnt )
{
g_pScriptVM->SetValue( m_ScriptScope, "entity", pEnt->GetScriptInstance() );
}
}
else
if (!pEnt)
{
// Needs to register as a null value so the script doesn't break if it looks for an entity
g_pScriptVM->SetValue( m_ScriptScope, "entity", SCRIPT_VARIANT_NULL );
@ -414,6 +425,14 @@ void CScriptMaterialProxy::SetVarVector( int i, const Vector &value )
return m_MaterialVars[i]->SetVecValue( value.Base(), 3 );
}
const char *CScriptMaterialProxy::GetVarName( int i )
{
if (!ValidateIndex( i ) || !m_MaterialVars[i])
return NULL;
return m_MaterialVars[i]->GetName();
}
EXPOSE_INTERFACE( CScriptMaterialProxy, IMaterialProxy, "VScriptProxy" IMATERIAL_PROXY_INTERFACE_VERSION );
bool CMaterialProxyScriptInstanceHelper::ToString( void *p, char *pBuf, int bufSize )

View File

@ -24,7 +24,13 @@ function IncludeScript( name, scope = null )
function DispatchParticleEffect( particleName, origin, angles, entity = null )
{
DoDispatchParticleEffect( particleName, origin, angles, entity );
return DoDispatchParticleEffect( particleName, origin, angles, entity );
}
function ImpulseScale( flTargetMass, flDesiredSpeed )
{
return flTargetMass * flDesiredSpeed;
}
__Documentation.RegisterHelp( "ImpulseScale", "float ImpulseScale(float, float)", "Returns an impulse scale required to push an object." );
)vscript";

View File

@ -27,6 +27,10 @@
static IVEngineServer *g_pEngineServer = NULL;
#ifdef MAPBASE
ConVar cl_worldlight_use_new_method("cl_worldlight_use_new_method", "1", FCVAR_NONE, "Uses the new world light iteration method which splits lights into multiple lists for each cluster.");
#endif
//-----------------------------------------------------------------------------
// Singleton exposure
//-----------------------------------------------------------------------------
@ -192,6 +196,83 @@ void CWorldLights::LevelInitPreEntity()
g_pFullFileSystem->Close(hFile);
DevMsg("CWorldLights: load successful (%d lights at 0x%p)\n", m_nWorldLights, m_pWorldLights);
#ifdef MAPBASE
// Now that the lights have been gathered, begin separating them into lists for each PVS cluster.
// This code is adapted from the soundscape cluster list code (see soundscape_system.cpp) and is intended to
// reduce frame drops in large maps which use dynamic RTT shadow angles.
CUtlVector<bbox_t> clusterbounds;
int clusterCount = g_pEngineServer->GetClusterCount();
clusterbounds.SetCount( clusterCount );
g_pEngineServer->GetAllClusterBounds( clusterbounds.Base(), clusterCount );
m_WorldLightsInCluster.SetCount(clusterCount);
for ( int i = 0; i < clusterCount; i++ )
{
m_WorldLightsInCluster[i].lightCount = 0;
m_WorldLightsInCluster[i].firstLight = 0;
}
unsigned char myPVS[16 * 1024];
CUtlVector<short> clusterIndexList;
CUtlVector<short> lightIndexList;
// Find the clusters visible from each light, then add it to those clusters' light lists
// (Also try to clip for radius if possible)
for (int i = 0; i < m_nWorldLights; ++i)
{
dworldlight_t *light = &m_pWorldLights[i];
// Assign the sun to its own pointer
if (light->type == emit_skylight)
{
m_iSunIndex = i;
continue;
}
float radiusSq = light->radius * light->radius;
if (radiusSq == 0.0f)
{
// TODO: Use intensity instead?
radiusSq = FLT_MAX;
}
g_pEngineServer->GetPVSForCluster( light->cluster, sizeof( myPVS ), myPVS );
for ( int j = 0; j < clusterCount; j++ )
{
if ( myPVS[ j >> 3 ] & (1<<(j&7)) )
{
float distSq = CalcSqrDistanceToAABB( clusterbounds[j].mins, clusterbounds[j].maxs, light->origin );
if ( distSq < radiusSq )
{
m_WorldLightsInCluster[j].lightCount++;
clusterIndexList.AddToTail(j);
lightIndexList.AddToTail(i);
}
}
}
}
m_WorldLightsIndexList.SetCount(lightIndexList.Count());
// Compute the starting index of each cluster
int firstLight = 0;
for ( int i = 0; i < clusterCount; i++ )
{
m_WorldLightsInCluster[i].firstLight = firstLight;
firstLight += m_WorldLightsInCluster[i].lightCount;
m_WorldLightsInCluster[i].lightCount = 0;
}
// Now add each light index to the appropriate cluster's list
for ( int i = 0; i < lightIndexList.Count(); i++ )
{
int cluster = clusterIndexList[i];
int outIndex = m_WorldLightsInCluster[cluster].lightCount + m_WorldLightsInCluster[cluster].firstLight;
m_WorldLightsInCluster[cluster].lightCount++;
m_WorldLightsIndexList[outIndex] = lightIndexList[i];
}
//DevMsg( "CWorldLights: Light clusters list has %i elements; Light index list has %i\n", m_WorldLightsInCluster.Count(), m_WorldLightsIndexList.Count() );
#endif
}
//-----------------------------------------------------------------------------
@ -208,6 +289,25 @@ bool CWorldLights::GetBrightestLightSource(const Vector &vecPosition, Vector &ve
// Find the size of the PVS for our current position
int nCluster = g_pEngineServer->GetClusterForOrigin(vecPosition);
#ifdef MAPBASE
if (cl_worldlight_use_new_method.GetBool())
{
FindBrightestLightSourceNew( vecPosition, vecLightPos, vecLightBrightness, nCluster );
}
else
#endif
{
FindBrightestLightSourceOld( vecPosition, vecLightPos, vecLightBrightness, nCluster );
}
//engine->Con_NPrintf(m_nWorldLights, "result: %d", !vecLightBrightness.IsZero());
return !vecLightBrightness.IsZero();
}
void CWorldLights::FindBrightestLightSourceOld( const Vector &vecPosition, Vector &vecLightPos, Vector &vecLightBrightness, int nCluster )
{
// Find the size of the PVS for our current position
int nPVSSize = g_pEngineServer->GetPVSForCluster(nCluster, 0, NULL);
// Get the PVS at our position
@ -257,7 +357,7 @@ bool CWorldLights::GetBrightestLightSource(const Vector &vecPosition, Vector &ve
delete[] pvs;
return false;
return;
}
// Calculate square distance to this worldlight
@ -308,7 +408,87 @@ bool CWorldLights::GetBrightestLightSource(const Vector &vecPosition, Vector &ve
}
delete[] pvs;
}
//engine->Con_NPrintf(m_nWorldLights, "result: %d", !vecLightBrightness.IsZero());
return !vecLightBrightness.IsZero();
}
#ifdef MAPBASE
void CWorldLights::FindBrightestLightSourceNew( const Vector &vecPosition, Vector &vecLightPos, Vector &vecLightBrightness, int nCluster )
{
// Handle sun
if (m_iSunIndex != -1)
{
dworldlight_t *light = &m_pWorldLights[m_iSunIndex];
// Calculate sun position
Vector vecAbsStart = vecPosition + Vector(0,0,30);
Vector vecAbsEnd = vecAbsStart - (light->normal * MAX_TRACE_LENGTH);
trace_t tr;
UTIL_TraceLine(vecPosition, vecAbsEnd, MASK_OPAQUE, NULL, COLLISION_GROUP_NONE, &tr);
// If we didn't hit anything then we have a problem
if(tr.DidHit())
{
// If we did hit something, and it wasn't the skybox, then skip
// this worldlight
if((tr.surface.flags & SURF_SKY) && (tr.surface.flags & SURF_SKY2D))
{
// Act like we didn't find any valid worldlights, so the shadow
// manager uses the default shadow direction instead (should be the
// sun direction)
return;
}
}
}
// Iterate through all the worldlights
if ( nCluster >= 0 && nCluster < m_WorldLightsInCluster.Count() )
{
// find all soundscapes that could possibly attach to this player and update them
for ( int j = 0; j < m_WorldLightsInCluster[nCluster].lightCount; j++ )
{
int ssIndex = m_WorldLightsIndexList[m_WorldLightsInCluster[nCluster].firstLight + j];
dworldlight_t *light = &m_pWorldLights[ssIndex];
// Calculate square distance to this worldlight
Vector vecDelta = light->origin - vecPosition;
float flDistSqr = vecDelta.LengthSqr();
float flRadiusSqr = light->radius * light->radius;
// Skip lights that are out of our radius
if(flRadiusSqr > 0 && flDistSqr >= flRadiusSqr)
{
//engine->Con_NPrintf(i, "%d: out-of-radius (dist: %d, radius: %d)", i, sqrt(flDistSqr), light->radius);
continue;
}
// Calculate intensity at our position
float flRatio = Engine_WorldLightDistanceFalloff(light, vecDelta);
Vector vecIntensity = light->intensity * flRatio;
// Is this light more intense than the one we already found?
if(vecIntensity.LengthSqr() <= vecLightBrightness.LengthSqr())
{
//engine->Con_NPrintf(i, "%d: too dim", i);
continue;
}
// Can we see the light?
trace_t tr;
Vector vecAbsStart = vecPosition + Vector(0,0,30);
UTIL_TraceLine(vecAbsStart, light->origin, MASK_OPAQUE, NULL, COLLISION_GROUP_NONE, &tr);
if(tr.DidHit())
{
//engine->Con_NPrintf(i, "%d: trace failed", i);
continue;
}
vecLightPos = light->origin;
vecLightBrightness = vecIntensity;
//engine->Con_NPrintf(i, "%d: set (%.2f)", i, vecIntensity.Length());
}
}
}
#endif

View File

@ -27,7 +27,9 @@ public:
// Find the brightest light source at a point
//-------------------------------------------------------------------------
bool GetBrightestLightSource(const Vector &vecPosition, Vector &vecLightPos, Vector &vecLightBrightness);
void FindBrightestLightSourceOld( const Vector &vecPosition, Vector &vecLightPos, Vector &vecLightBrightness, int nCluster );
#ifdef MAPBASE
void FindBrightestLightSourceNew(const Vector &vecPosition, Vector &vecLightPos, Vector &vecLightBrightness, int nCluster);
bool GetCumulativeLightSource(const Vector &vecPosition, Vector &vecLightPos, float flMinBrightnessSqr);
#endif
@ -42,6 +44,19 @@ private:
int m_nWorldLights;
dworldlight_t *m_pWorldLights;
#ifdef MAPBASE
int m_iSunIndex = -1; // The sun's personal index
struct clusterLightList_t
{
unsigned short lightCount;
unsigned short firstLight;
};
CUtlVector<clusterLightList_t> m_WorldLightsInCluster;
CUtlVector<unsigned short> m_WorldLightsIndexList;
#endif
};
//-----------------------------------------------------------------------------

View File

@ -4,6 +4,9 @@
//
//=============================================================================//
#ifdef NEW_RESPONSE_SYSTEM
#include "ai_criteria_new.h"
#else
#ifndef AI_CRITERIA_H
#define AI_CRITERIA_H
#ifdef _WIN32
@ -276,3 +279,4 @@ private:
};
#endif // AI_CRITERIA_H
#endif

View File

@ -4,6 +4,9 @@
//
//=============================================================================//
#ifdef NEW_RESPONSE_SYSTEM
#include "ai_responsesystem_new.h"
#else
#ifndef AI_RESPONSESYSTEM_H
#define AI_RESPONSESYSTEM_H
@ -39,3 +42,4 @@ class ISaveRestoreBlockHandler *GetDefaultResponseSystemSaveRestoreBlockHandler(
class ISaveRestoreOps *GetResponseSystemSaveRestoreOps();
#endif // AI_RESPONSESYSTEM_H
#endif

View File

@ -242,7 +242,12 @@ void CEntityFlame::FlameThink( void )
}
CAI_BaseNPC *pNPC = m_hEntAttached->MyNPCPointer();
#ifdef MAPBASE
// Don't extingish if the NPC is still dying
if ( pNPC && !pNPC->IsAlive() && pNPC->m_lifeState != LIFE_DYING )
#else
if ( pNPC && !pNPC->IsAlive() )
#endif
{
UTIL_Remove( this );
// Notify the NPC that it's no longer burning!

View File

@ -2033,7 +2033,11 @@ bool CAI_BaseActor::UseSemaphore( void )
CAI_Expresser *CAI_BaseActor::CreateExpresser()
{
#ifdef NEW_RESPONSE_SYSTEM
m_pExpresser = new CAI_ExpresserWithFollowup(this);
#else
m_pExpresser = new CAI_Expresser(this);
#endif
return m_pExpresser;
}

View File

@ -97,6 +97,7 @@
#ifdef MAPBASE
#include "mapbase/matchers.h"
#include "items.h"
#endif
#include "env_debughistory.h"
@ -11509,17 +11510,9 @@ void CAI_BaseNPC::PickupItem( CBaseEntity *pItem )
m_OnItemPickup.Set( pItem, pItem, this );
Assert( pItem != NULL );
if( FClassnameIs( pItem, "item_healthkit" ) )
if( FClassnameIs( pItem, "item_health*" ) ) // item_healthkit, item_healthvial, item_healthkit_custom, etc.
{
if ( TakeHealth( sk_healthkit.GetFloat(), DMG_GENERIC ) )
{
RemoveAllDecals();
UTIL_Remove( pItem );
}
}
else if( FClassnameIs( pItem, "item_healthvial" ) )
{
if ( TakeHealth( sk_healthvial.GetFloat(), DMG_GENERIC ) )
if ( TakeHealth( static_cast<CItem*>(pItem)->GetItemAmount(), DMG_GENERIC ) )
{
RemoveAllDecals();
UTIL_Remove( pItem );
@ -15251,7 +15244,11 @@ void CAI_BaseNPC::CalculateValidEnemyInteractions( void )
const char *p = STRING(pInteraction->MiscCriteria);
while ( p )
{
#ifdef NEW_RESPONSE_SYSTEM
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), NULL, STRING(pInteraction->MiscCriteria) );
#else
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), NULL );
#endif
index = set.FindCriterionIndex(key);
if (index != -1)

View File

@ -64,7 +64,9 @@ class CBaseGrenade;
class CBaseDoor;
class CBasePropDoor;
struct AI_Waypoint_t;
#ifndef NEW_RESPONSE_SYSTEM
class AI_Response;
#endif
class CBaseFilter;
typedef CBitVec<MAX_CONDITIONS> CAI_ScheduleBits;
@ -98,11 +100,6 @@ extern bool AIStrongOpt( void );
#ifdef MAPBASE
// Defines Mapbase's extended NPC response system usage.
#define EXPANDED_RESPONSE_SYSTEM_USAGE
// Use the model keyvalue if it is defined
#define DefaultOrCustomModel(defaultModel) GetModelName() != NULL_STRING ? STRING(GetModelName()) : defaultModel
#else
#define DefaultOrCustomModel() defaultModel
#endif
#ifdef EXPANDED_RESPONSE_SYSTEM_USAGE
@ -670,6 +667,7 @@ public:
virtual bool ShouldAlwaysThink();
void ForceGatherConditions() { m_bForceConditionsGather = true; SetEfficiency( AIE_NORMAL ); } // Force an NPC out of PVS to call GatherConditions on next think
bool IsForceGatherConditionsSet() { return m_bForceConditionsGather; }
virtual float LineOfSightDist( const Vector &vecDir = vec3_invalid, float zEye = FLT_MAX );
@ -963,7 +961,7 @@ public:
void RemoveSleepFlags( int flags ) { m_SleepFlags &= ~flags; }
bool HasSleepFlags( int flags ) { return (m_SleepFlags & flags) == flags; }
void UpdateSleepState( bool bInPVS );
virtual void UpdateSleepState( bool bInPVS );
virtual void Wake( bool bFireOutput = true );
#ifdef MAPBASE
// A version of Wake() that takes an activator

View File

@ -3979,10 +3979,16 @@ void CAI_BaseNPC::RunTask( const Task_t *pTask )
// If we have an entry, we have to play it first
if ( m_hCine->m_iszEntry != NULL_STRING )
{
#ifdef MAPBASE
m_hCine->OnEntrySequence( this );
#endif
m_hCine->StartSequence( (CAI_BaseNPC *)this, m_hCine->m_iszEntry, true );
}
else
{
#ifdef MAPBASE
m_hCine->OnActionSequence( this );
#endif
m_hCine->StartSequence( (CAI_BaseNPC *)this, m_hCine->m_iszPlay, true );
}

View File

@ -9,12 +9,19 @@
#include "simtimer.h"
#include "ai_behavior.h"
#ifdef NEW_RESPONSE_SYSTEM
#include "ai_speechconcept.h"
#endif
#if defined( _WIN32 )
#pragma once
#endif
#ifdef NEW_RESPONSE_SYSTEM
typedef CAI_Concept AIConcept_t;
#else
typedef const char *AIConcept_t;
#endif
// Speak concepts
#define TLK_LEAD_START "TLK_LEAD_START"

View File

@ -0,0 +1,509 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "ai_speech.h"
#include "game.h"
#include "eventqueue.h"
#include "ai_basenpc.h"
#include "basemultiplayerplayer.h"
#include "ai_baseactor.h"
#include "sceneentity.h"
//#include "flex_expresser.h"
/*
#include "engine/ienginesound.h"
#include "keyvalues.h"
#include "ai_criteria.h"
#include "isaverestore.h"
#include "sceneentity.h"
*/
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
static const char *GetResponseName( CBaseEntity *pEnt )
{
Assert( pEnt );
if ( pEnt == NULL )
return "";
return STRING( pEnt->GetEntityName() );
}
// This is a tiny helper function for below -- what I'd use a lambda for, usually
static void DispatchComeback( CAI_ExpresserWithFollowup *pExpress, CBaseEntity *pSpeaker, CBaseEntity *pRespondent, AI_ResponseFollowup &followup )
{
AssertMsg(pSpeaker != NULL, "Response expressor somehow got called with a NULL Outer.\n");
if ( !pRespondent )
{
return;
}
float delay = followup.followup_delay;
if (pSpeaker == pRespondent && delay < 0)
{
Warning("Response rule with a 'self' target specified negative delay, which isn't legal because that would make someone talk over himself.");
delay = 0;
}
// Msg( "%s: Dispatch comeback about %s to %s\n", pSpeaker->GetBotString(), g_pConceptManager->GetTopicName( handle ), pRespondent->GetBotString() );
// build an input event that we will use to force the bot to talk through the IO system
variant_t value;
// Don't send along null contexts
if (followup.followup_contexts && followup.followup_contexts[0] != '\0')
{
value.SetString( MAKE_STRING( followup.followup_contexts ) );
g_EventQueue.AddEvent( pRespondent, "AddContext", value, delay - 0.01, pSpeaker, pSpeaker );
}
/*
value.SetString(MAKE_STRING(followup.followup_concept));
g_EventQueue.AddEvent( pRespondent, "SpeakResponseConcept", value, delay , pSpeaker, pSpeaker );
*/
AI_CriteriaSet criteria;
// add in the FROM context so dispatchee knows was from me
const char * RESTRICT pszSpeakerName = GetResponseName( pSpeaker );
criteria.AppendCriteria( "From", pszSpeakerName );
#ifdef MAPBASE
// See DispatchFollowupThroughQueue()
criteria.AppendCriteria( "From_idx", CNumStr( pSpeaker->entindex() ) );
criteria.AppendCriteria( "From_class", pSpeaker->GetClassname() );
#endif
// if a SUBJECT criteria is missing, put it back in.
if ( criteria.FindCriterionIndex( "Subject" ) == -1 )
{
criteria.AppendCriteria( "Subject", pszSpeakerName );
}
// add in any provided contexts from the parameters onto the ones stored in the followup
criteria.Merge( followup.followup_contexts );
// This is kludgy and needs to be fixed in class hierarchy, but for now, try to guess at the most likely
// kinds of targets and dispatch to them.
if (CBaseMultiplayerPlayer *pPlayer = dynamic_cast<CBaseMultiplayerPlayer *>(pRespondent))
{
pPlayer->Speak( followup.followup_concept, &criteria );
}
else if (CAI_BaseActor *pActor = dynamic_cast<CAI_BaseActor *>(pRespondent))
{
pActor->Speak( followup.followup_concept, &criteria );
}
}
#if 0
//-----------------------------------------------------------------------------
// Purpose: Placeholder for rules based response system
// Input : concept -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_ExpresserWithFollowup::Speak( AIConcept_t &concept, const char *modifiers /*= NULL*/, char *pszOutResponseChosen /* = NULL*/, size_t bufsize /* = 0 */, IRecipientFilter *filter /* = NULL */ )
{
AI_Response *result = SpeakFindResponse( concept, modifiers );
if ( !result )
{
return false;
}
CNPC_CompanionBot *pBot = dynamic_cast<CNPC_CompanionBot *>(GetOuter());
if ( pBot )
{
pBot->SetConversationTopic( g_pConceptManager->GetTopic( handle ) );
pBot->SetLastSpeaker( g_pConceptManager->GetSpeaker( handle ) );
// Msg( "%s: Conversing about %s\n", pBot->GetBotString(), g_pConceptManager->GetTopicName( handle ) );
}
SpeechMsg( GetOuter(), "%s (%x) spoke %s (%f)\n", STRING(GetOuter()->GetEntityName()), GetOuter(), g_pConceptManager->GetConcept( handle ), gpGlobals->curtime );
bool spoke = SpeakDispatchResponse( handle, result, filter );
if ( pszOutResponseChosen )
{
result->GetResponse( pszOutResponseChosen, bufsize );
}
return spoke;
}
#endif
// Work out the character from the "subject" context.
// Right now, this is a simple find by entity name search.
// But you can define arbitrary subject names, like L4D does
// for "biker", "manager", etc.
static CBaseEntity *AscertainSpeechSubjectFromContext( AI_Response *response, AI_CriteriaSet &criteria, const char *pContextName )
{
const char *subject = criteria.GetValue( criteria.FindCriterionIndex( pContextName ) );
if (subject)
{
CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, subject );
#ifdef MAPBASE
// Allow entity indices to be used (see DispatchFollowupThroughQueue() for one particular use case)
if (!pEnt && atoi(subject))
{
pEnt = CBaseEntity::Instance( atoi( subject ) );
}
#endif
return pEnt;
}
else
{
return NULL;
}
}
// TODO: Currently uses awful stricmp. Use symbols! Once I know which ones we want, that is.
static CResponseQueue::CFollowupTargetSpec_t ResolveFollowupTargetToEntity( AIConcept_t &concept, AI_CriteriaSet &criteria, const char * RESTRICT szTarget, AI_Response * RESTRICT response = NULL )
{
if ( Q_stricmp(szTarget, "self") == 0 )
{
return CResponseQueue::CFollowupTargetSpec_t( kDRT_SPECIFIC, concept.GetSpeaker() );
}
else if ( Q_stricmp(szTarget, "subject") == 0 )
{
return CResponseQueue::CFollowupTargetSpec_t( AscertainSpeechSubjectFromContext( response, criteria, "Subject" ) );
}
else if ( Q_stricmp(szTarget, "from") == 0 )
{
#ifdef MAPBASE
// See DispatchFollowupThroughQueue()
return CResponseQueue::CFollowupTargetSpec_t( AscertainSpeechSubjectFromContext( response, criteria, "From_idx" ) );
#else
return CResponseQueue::CFollowupTargetSpec_t( AscertainSpeechSubjectFromContext( response, criteria, "From" ) );
#endif
}
else if ( Q_stricmp(szTarget, "any") == 0 )
{
return CResponseQueue::CFollowupTargetSpec_t( kDRT_ANY, concept.GetSpeaker() );
}
else if ( Q_stricmp(szTarget, "all") == 0 )
{
return CResponseQueue::CFollowupTargetSpec_t( kDRT_ALL );
}
// last resort, try a named lookup
#ifdef MAPBASE
else if ( CBaseEntity *pSpecific = gEntList.FindEntityByName(NULL, szTarget, concept.GetSpeaker()) ) // it could be anything
#else
else if ( CBaseEntity *pSpecific = gEntList.FindEntityByName(NULL, szTarget) ) // it could be anything
#endif
{
return CResponseQueue::CFollowupTargetSpec_t( pSpecific );
}
Warning("Couldn't resolve response target %s\n", szTarget );
return CResponseQueue::CFollowupTargetSpec_t(); // couldn't resolve.
}
// TODO: Currently uses awful stricmp. Use symbols! Once I know which ones we want, that is.
static CResponseQueue::CFollowupTargetSpec_t ResolveFollowupTargetToEntity( AIConcept_t &concept, AI_CriteriaSet &criteria, AI_Response * RESTRICT response, AI_ResponseFollowup * RESTRICT followup )
{
const char * RESTRICT szTarget = followup->followup_target;
const CResponseQueue::CFollowupTargetSpec_t INVALID; // default: invalid result
if ( szTarget == NULL )
return INVALID;
else
return ResolveFollowupTargetToEntity( concept, criteria, szTarget, response );
}
ConVar chet_debug_idle( "chet_debug_idle", "0", FCVAR_ARCHIVE, "If set one, many debug prints to help track down the TLK_IDLE issue. Set two for super verbose info" );
// extern ConVar chet_debug_idle;
bool CAI_ExpresserWithFollowup::Speak( AIConcept_t concept, const char *modifiers /*= NULL*/, char *pszOutResponseChosen /* = NULL*/, size_t bufsize /* = 0 */, IRecipientFilter *filter /* = NULL */ )
{
VPROF("CAI_Expresser::Speak");
if ( IsSpeechGloballySuppressed() )
{
return false;
}
concept.SetSpeaker(GetOuter());
AI_CriteriaSet criteria;
GatherCriteria(&criteria, concept, modifiers);
GetOuter()->ModifyOrAppendDerivedCriteria(criteria);
AI_Response result;
if ( !FindResponse( result, concept, &criteria ) )
{
if (chet_debug_idle.GetBool())
{
const char *name = GetOuter()->GetDebugName();
Msg( "TLK_IDLE: %s did not FindResponse\n", name );
}
return false;
}
else
{
if (chet_debug_idle.GetBool())
{
const char *name = GetOuter()->GetDebugName();
Msg( "TLK_IDLE: %s SUCCESSFUL FindResponse\n", name );
}
}
SpeechMsg( GetOuter(), "%s (%p) spoke %s (%f)", STRING(GetOuter()->GetEntityName()), GetOuter(), (const char*)concept, gpGlobals->curtime );
// Msg( "%s:%s to %s:%s\n", GetOuter()->GetDebugName(), concept.GetStringConcept(), criteria.GetValue(criteria.FindCriterionIndex("Subject")), pTarget ? pTarget->GetDebugName() : "none" );
bool spoke = SpeakDispatchResponse( concept, &result, &criteria, filter );
if ( pszOutResponseChosen )
{
result.GetResponse( pszOutResponseChosen, bufsize );
}
return spoke;
}
extern ISoundEmitterSystemBase* soundemitterbase;
static float GetSpeechDurationForResponse( const AI_Response * RESTRICT response, const char *szActorModel)
{
switch (response->GetType())
{
case ResponseRules::RESPONSE_SCENE:
{
char szScene[MAX_PATH];
soundemitterbase->GenderExpandString(szActorModel, response->GetResponsePtr(), szScene, MAX_PATH);
return GetSceneSpeechDuration(szScene);
}
break;
default:
break;
}
return 0.f;
}
//-----------------------------------------------------------------------------
// Purpose: Dispatches the result
// Input : *response -
//-----------------------------------------------------------------------------
bool CAI_ExpresserWithFollowup::SpeakDispatchResponse( AIConcept_t concept, AI_Response *response, AI_CriteriaSet *criteria, IRecipientFilter *filter )
{
// This gives the chance for the other bot to respond.
if ( !concept.GetSpeaker().IsValid() )
{
concept.SetSpeaker(GetOuter());
}
bool bInterrupted = IsSpeaking();
bool bSuc = CAI_Expresser::SpeakDispatchResponse( concept, response, criteria, filter );
if (!bSuc)
{
return false;
}
if ( bInterrupted )
{
g_ResponseQueueManager.GetQueue()->RemoveSpeechQueuedFor( GetOuter() );
}
// Record my followup details so that I may defer its use til end of the speech
AI_ResponseFollowup * RESTRICT followup = response->GetParams()->m_pFollowup;
if ( followup )
{
if ( followup->followup_entityiotarget && followup->followup_entityioinput )
{
#ifdef MAPBASE
CBaseEntity * RESTRICT pTarget = ResolveFollowupTargetToEntity( concept, *criteria, followup->followup_entityiotarget, response ).m_hHandle;
#else
CBaseEntity * RESTRICT pTarget = gEntList.FindEntityByName( NULL, followup->followup_entityiotarget );
#endif
if ( pTarget )
{
g_EventQueue.AddEvent( pTarget, followup->followup_entityioinput, variant_t(), followup->followup_entityiodelay, GetOuter(), GetOuter() );
}
}
if ( followup->IsValid() )
{
// 11th hour change: rather than trigger followups from the end of a VCD,
// instead fire it from the end of the last speech event in the VCD, because
// there's a multisecond facial relax delay built into the scene.
// The speech length is stored in the cache, so we can post the followup now.
if ( response->GetType() == ResponseRules::RESPONSE_SCENE &&
followup->followup_delay >= 0 )
{
float fTimeToLastSpeech = GetSpeechDurationForResponse( response, STRING(GetOuter()->GetModelName()) );
// failsafe
if ( fTimeToLastSpeech > 0 )
{
DispatchFollowupThroughQueue( followup->followup_concept, followup->followup_contexts,
ResolveFollowupTargetToEntity( concept, *criteria, response, followup ),
fTimeToLastSpeech + followup->followup_delay, GetOuter() );
}
else // error
{
// old way, copied from "else" below
m_pPostponedFollowup = followup;
if ( criteria )
m_followupTarget = ResolveFollowupTargetToEntity( concept, *criteria, response, m_pPostponedFollowup );
else
{
AI_CriteriaSet tmpCriteria;
m_followupTarget = ResolveFollowupTargetToEntity( concept, tmpCriteria, response, m_pPostponedFollowup );
}
}
}
else if ( followup->followup_delay < 0 )
{
// a negative delay has a special meaning. Usually the comeback dispatches after
// the currently said line is finished; the delay is added to that, to provide a
// pause between when character A finishes speaking and B begins.
// A negative delay (-n) actually means "dispatch the comeback n seconds
// after I start talking".
// In this case we do not need to postpone the followup; we just throw it directly
// into the queue.
DispatchFollowupThroughQueue( followup->followup_concept, followup->followup_contexts,
ResolveFollowupTargetToEntity( concept, *criteria, response, followup ),
-followup->followup_delay, GetOuter() );
}
#ifndef MAPBASE // RESPONSE_PRINT now notes speaking time
else if ( response->GetType() == ResponseRules::RESPONSE_PRINT )
{ // zero-duration responses dispatch immediately via the queue (must be the queue bec.
// the m_pPostponedFollowup will never trigger)
DispatchFollowupThroughQueue( followup->followup_concept, followup->followup_contexts,
ResolveFollowupTargetToEntity( concept, *criteria, response, followup ),
followup->followup_delay, GetOuter() );
}
#endif
else
{
// this is kind of a quick patch to immediately deal with the issue of null criteria
// (arose while branching to main) without replumbing a bunch of stuff -- to be fixed
// 5.13.08 egr
m_pPostponedFollowup = followup;
if ( criteria )
m_followupTarget = ResolveFollowupTargetToEntity( concept, *criteria, response, m_pPostponedFollowup );
else
{
AI_CriteriaSet tmpCriteria;
m_followupTarget = ResolveFollowupTargetToEntity( concept, tmpCriteria, response, m_pPostponedFollowup );
}
}
}
}
return bSuc;
}
// This is a gimmick used when a negative delay is specified in a followup, which is a shorthand
// for "this many seconds after the beginning of the line" rather than "this may seconds after the end
// of the line", eg to create a THEN rule when two characters talk over each other.
// It's static to avoid accidental use of the postponed followup/target members.
void CAI_ExpresserWithFollowup::DispatchFollowupThroughQueue( const AIConcept_t &concept,
const char * RESTRICT criteriaStr,
const CResponseQueue::CFollowupTargetSpec_t &target,
float delay,
CBaseEntity * RESTRICT pOuter
)
{
AI_CriteriaSet criteria;
// Don't add my own criteria! GatherCriteria( &criteria, followup.followup_concept, followup.followup_contexts );
criteria.AppendCriteria( "From", STRING( pOuter->GetEntityName() ) );
#ifdef MAPBASE
// The index of the "From" entity.
// In HL2 mods, many followup users would be generic NPCs (e.g. citizens) who might not have any particular significance.
// Those generic NPCs are quite likely to have no name or have a name in common with other entities. As a result, Mapbase
// changes internal operations of the "From" context to search for an entity index. This won't be 100% reliable if the source
// talker dies and another entity is created immediately afterwards, but it's a lot more reliable than a simple entity name search.
criteria.AppendCriteria( "From_idx", CNumStr( pOuter->entindex() ) );
// Generic NPCs should also be attributable by classname
criteria.AppendCriteria( "From_class", pOuter->GetClassname() );
#endif
criteria.Merge( criteriaStr );
g_ResponseQueueManager.GetQueue()->Add( concept, &criteria, gpGlobals->curtime + delay, target, pOuter );
}
//-----------------------------------------------------------------------------
// Purpose: Handles the new concept objects
//-----------------------------------------------------------------------------
void CAI_ExpresserWithFollowup::SpeakDispatchFollowup( AI_ResponseFollowup &followup )
{
if ( !m_followupTarget.IsValid() )
return;
// If a specific entity target is given, use the old pathway for now
if ( m_followupTarget.m_iTargetType == kDRT_SPECIFIC && followup.followup_delay == 0 )
{
CBaseEntity *pTarget = m_followupTarget.m_hHandle.Get();
if (!pTarget)
{
return;
}
DispatchComeback( this, GetOuter(), pTarget, followup );
}
else
{
DispatchFollowupThroughQueue( followup.followup_concept, followup.followup_contexts, m_followupTarget, followup.followup_delay, GetOuter() );
}
// clear out the followup member just in case.
m_pPostponedFollowup = NULL;
m_followupTarget.m_iTargetType = kDRT_MAX;
}
void CAI_ExpresserWithFollowup::OnSpeechFinished()
{
if (m_pPostponedFollowup && m_pPostponedFollowup->IsValid())
{
#ifdef MAPBASE
// HACKHACK: Non-scene speech (e.g. noscene speak/sentence) fire OnSpeechFinished() immediately,
// so add the actual speech time to the followup delay
if (GetTimeSpeechCompleteWithoutDelay() > gpGlobals->curtime)
m_pPostponedFollowup->followup_delay += GetTimeSpeechCompleteWithoutDelay() - gpGlobals->curtime;
#endif
return SpeakDispatchFollowup(*m_pPostponedFollowup);
}
}
void CC_RR_ForceConcept_f( const CCommand &args )
{
if ( args.ArgC() < 3 )
{
Msg("USAGE: rr_forceconcept <target> <concept> \"criteria1:value1,criteria2:value2,...\"\n");
return;
}
AI_CriteriaSet criteria;
if ( args.ArgC() >= 3 )
{
const char *criteriastring = args[3];
criteria.Merge( criteriastring );
}
AIConcept_t concept( args[2] );
QueueSpeak( concept, ResolveFollowupTargetToEntity( concept, criteria, args[1] ), criteria );
}
static ConCommand rr_forceconcept( "rr_forceconcept", CC_RR_ForceConcept_f,
"fire a response concept directly at a given character.\n"
"USAGE: rr_forceconcept <target> <concept> \"criteria1:value1,criteria2:value2,...\"\n"
"criteria values are optional.\n"
, FCVAR_CHEAT );

View File

@ -289,6 +289,11 @@ public:
void SetHintType( int hintType, bool force = false );
string_t HintActivityName( void ) const { return m_NodeData.iszActivityName; }
int GetTargetNode( void ) const { return m_nTargetNodeID; }
#ifdef MAPBASE
// HACKHACK: This is for when target nodes need to be accessed before being sorted into engine IDs
int GetTargetWCNodeID( void ) const { return m_NodeData.nTargetWCNodeID; }
int GetWCNodeID( void ) const { return m_NodeData.nWCNodeID; }
#endif
bool IsDisabled( void ) const { return (m_NodeData.iDisabled != 0); }
void SetDisabled( bool bDisabled ) { m_NodeData.iDisabled = bDisabled; }
void DisableForSeconds( float flSeconds );
@ -319,7 +324,9 @@ public:
const char* ScriptGetHintActivity() { return STRING( HintActivityName() ); }
#endif
#ifndef MAPBASE
private:
#endif
void Spawn( void );
virtual void Activate();
virtual void UpdateOnRemove( void );

View File

@ -227,7 +227,11 @@ int CNodeEnt::Spawn( const char *pMapData )
// ---------------------------------------------------------------------------------
CAI_Hint *pHint = NULL;
if ( ClassMatches( "info_node_hint" ) || ClassMatches( "info_node_air_hint" ) )
if ( ClassMatches( "info_node_hint" ) || ClassMatches( "info_node_air_hint" )
#ifdef MAPBASE
|| ClassMatches( "info_node_climb" ) // Climb nodes contain hint data in the FGD
#endif
)
{
if ( m_NodeData.nHintType || m_NodeData.strGroup != NULL_STRING || m_NodeData.strEntityName != NULL_STRING )
{

View File

@ -3059,6 +3059,16 @@ int CAI_NetworkBuilder::ComputeConnection( CAI_Node *pSrcNode, CAI_Node *pDestNo
}
else
{
#ifdef MAPBASE
// This is kind of a hack since target node IDs are designed to be used *after* the nodegraph is generated.
// However, for the purposes of forcing a climb connection outside of regular lineup bounds, it seems to be a reasonable solution.
if (pSrcNode->GetHint() && pDestNode->GetHint() &&
(pSrcNode->GetHint()->GetTargetWCNodeID() == pDestNode->GetHint()->GetWCId() || pDestNode->GetHint()->GetTargetWCNodeID() == pSrcNode->GetHint()->GetWCId()))
{
DebugConnectMsg( srcId, destId, " Ignoring climbing lineup due to manual target ID linkage\n" );
}
else
#endif
if ( !IsInLineForClimb(srcPos, UTIL_YawToVector( pSrcNode->m_flYaw ), destPos, UTIL_YawToVector( pDestNode->m_flYaw ) ) )
{
Assert( !IsInLineForClimb(destPos, UTIL_YawToVector( pDestNode->m_flYaw ), srcPos, UTIL_YawToVector( pSrcNode->m_flYaw ) ) );

View File

@ -137,12 +137,23 @@ bool ConceptStringLessFunc( const string_t &lhs, const string_t &rhs )
return CaselessStringLessThan( STRING(lhs), STRING(rhs) );
}
#ifdef NEW_RESPONSE_SYSTEM
bool ConceptInfoStringLessFunc( const AIConcept_t& lhs, const AIConcept_t& rhs )
{
return CaselessStringLessThan( lhs.GetStringConcept(), rhs.GetStringConcept() );
}
#endif
//-----------------------------------------------------------------------------
class CConceptInfoMap : public CUtlMap<AIConcept_t, ConceptInfo_t *> {
public:
CConceptInfoMap() :
#ifdef NEW_RESPONSE_SYSTEM
CUtlMap<AIConcept_t, ConceptInfo_t *>( ConceptInfoStringLessFunc )
#else
CUtlMap<AIConcept_t, ConceptInfo_t *>( CaselessStringLessThan )
#endif
{
for ( int i = 0; i < ARRAYSIZE(g_ConceptInfos); i++ )
{
@ -557,7 +568,11 @@ void CAI_PlayerAlly::PrescheduleThink( void )
if ( SelectNonCombatSpeech( &selection ) )
{
SetSpeechTarget( selection.hSpeechTarget );
#ifdef NEW_RESPONSE_SYSTEM
SpeakDispatchResponse( selection.concept.c_str(), &selection.Response );
#else
SpeakDispatchResponse( selection.concept.c_str(), selection.pResponse );
#endif
m_flNextIdleSpeechTime = gpGlobals->curtime + RandomFloat( 20,30 );
}
else
@ -599,12 +614,22 @@ bool CAI_PlayerAlly::SelectSpeechResponse( AIConcept_t concept, const char *pszM
{
if ( IsAllowedToSpeak( concept ) )
{
#ifdef NEW_RESPONSE_SYSTEM
bool result = SpeakFindResponse( pSelection->Response, concept, pszModifiers );
if ( result )
{
pSelection->concept = concept;
pSelection->hSpeechTarget = pTarget;
return true;
}
#else
AI_Response *pResponse = SpeakFindResponse( concept, pszModifiers );
if ( pResponse )
{
pSelection->Set( concept, pResponse, pTarget );
return true;
}
#endif
}
return false;
}
@ -614,7 +639,9 @@ bool CAI_PlayerAlly::SelectSpeechResponse( AIConcept_t concept, const char *pszM
void CAI_PlayerAlly::SetPendingSpeech( AIConcept_t concept, AI_Response *pResponse )
{
m_PendingResponse = *pResponse;
#ifndef NEW_RESPONSE_SYSTEM
pResponse->Release();
#endif
m_PendingConcept = concept;
m_TimePendingSet = gpGlobals->curtime;
}
@ -696,7 +723,11 @@ bool CAI_PlayerAlly::SelectInterjection()
if ( SelectIdleSpeech( &selection ) )
{
SetSpeechTarget( selection.hSpeechTarget );
#ifdef NEW_RESPONSE_SYSTEM
SpeakDispatchResponse( selection.concept.c_str(), &selection.Response );
#else
SpeakDispatchResponse( selection.concept.c_str(), selection.pResponse );
#endif
return true;
}
}
@ -761,11 +792,11 @@ void CAI_PlayerAlly::PostSpeakDispatchResponse( AIConcept_t concept, AI_Response
{
if ( bSaidHelloToNPC )
{
Warning("Q&A: '%s' said Hello to '%s' (concept %s)\n", GetDebugName(), GetSpeechTarget()->GetDebugName(), concept );
Warning("Q&A: '%s' said Hello to '%s' (concept %s)\n", GetDebugName(), GetSpeechTarget()->GetDebugName(), (const char*)concept );
}
else
{
Warning("Q&A: '%s' questioned '%s' (concept %s)\n", GetDebugName(), GetSpeechTarget()->GetDebugName(), concept );
Warning("Q&A: '%s' questioned '%s' (concept %s)\n", GetDebugName(), GetSpeechTarget()->GetDebugName(), (const char*)concept );
}
NDebugOverlay::HorzArrow( GetAbsOrigin(), GetSpeechTarget()->GetAbsOrigin(), 8, 0, 255, 0, 64, true, duration );
}
@ -881,6 +912,18 @@ bool CAI_PlayerAlly::AskQuestionNow( CBaseEntity *pSpeechTarget, int iQARandomNu
m_iQARandomNumber = RandomInt(0, 100);
AISpeechSelection_t selection;
#ifdef NEW_RESPONSE_SYSTEM
if (SelectSpeechResponse( concept, NULL, m_hPotentialSpeechTarget.Get(), &selection ))
{
SetSpeechTarget( selection.hSpeechTarget );
ClearPendingSpeech();
// Speak immediately
return SpeakDispatchResponse( selection.concept.c_str(), &selection.Response );
}
return false;
#else
SelectSpeechResponse( concept, NULL, m_hPotentialSpeechTarget.Get(), &selection );
SetSpeechTarget( selection.hSpeechTarget );
@ -891,6 +934,7 @@ bool CAI_PlayerAlly::AskQuestionNow( CBaseEntity *pSpeechTarget, int iQARandomNu
// Speak immediately
return SpeakDispatchResponse( selection.concept.c_str(), selection.pResponse );
#endif
}
//-----------------------------------------------------------------------------
@ -978,9 +1022,13 @@ void CAI_PlayerAlly::AnswerQuestion( CAI_PlayerAlly *pQuestioner, int iQARandomN
}
}
Assert( selection.pResponse );
SetSpeechTarget( selection.hSpeechTarget );
#ifdef NEW_RESPONSE_SYSTEM
SpeakDispatchResponse( selection.concept.c_str(), &selection.Response );
#else
Assert( selection.pResponse );
SpeakDispatchResponse( selection.concept.c_str(), selection.pResponse );
#endif
// Prevent idle speech for a while
DeferAllIdleSpeech( random->RandomFloat( TALKER_DEFER_IDLE_SPEAK_MIN, TALKER_DEFER_IDLE_SPEAK_MAX ), GetSpeechTarget()->MyNPCPointer() );
@ -1030,9 +1078,13 @@ int CAI_PlayerAlly::SelectNonCombatSpeechSchedule()
AISpeechSelection_t selection;
if ( SelectNonCombatSpeech( &selection ) )
{
Assert( selection.pResponse );
SetSpeechTarget( selection.hSpeechTarget );
#ifdef NEW_RESPONSE_SYSTEM
SetPendingSpeech( selection.concept.c_str(), &selection.Response );
#else
Assert( selection.pResponse );
SetPendingSpeech( selection.concept.c_str(), selection.pResponse );
#endif
}
}
@ -1107,9 +1159,13 @@ void CAI_PlayerAlly::StartTask( const Task_t *pTask )
case TASK_TALKER_SPEAK_PENDING:
if ( !m_PendingConcept.empty() )
{
#ifdef NEW_RESPONSE_SYSTEM
SpeakDispatchResponse( m_PendingConcept.c_str(), &m_PendingResponse );
#else
AI_Response *pResponse = new AI_Response;
*pResponse = m_PendingResponse;
SpeakDispatchResponse( m_PendingConcept.c_str(), pResponse );
#endif
m_PendingConcept.erase();
TaskComplete();
}
@ -1844,6 +1900,18 @@ bool CAI_PlayerAlly::RespondedTo( const char *ResponseConcept, bool bForce, bool
{
// We're being forced to respond to the event, probably because it's the
// player dying or something equally important.
#ifdef NEW_RESPONSE_SYSTEM
AI_Response response;
bool result = SpeakFindResponse( response, ResponseConcept, NULL );
if ( result )
{
// We've got something to say. Stop any scenes we're in, and speak the response.
if ( bCancelScene )
RemoveActorFromScriptedScenes( this, false );
return SpeakDispatchResponse( ResponseConcept, &response );
}
#else
AI_Response *result = SpeakFindResponse( ResponseConcept, NULL );
if ( result )
{
@ -1854,6 +1922,7 @@ bool CAI_PlayerAlly::RespondedTo( const char *ResponseConcept, bool bForce, bool
bool spoke = SpeakDispatchResponse( ResponseConcept, result );
return spoke;
}
#endif
return false;
}

View File

@ -252,6 +252,11 @@ enum AISpeechTargetSearchFlags_t
struct AISpeechSelection_t
{
#ifdef NEW_RESPONSE_SYSTEM
std::string concept;
AI_Response Response;
EHANDLE hSpeechTarget;
#else
AISpeechSelection_t()
: pResponse(NULL)
{
@ -267,6 +272,7 @@ struct AISpeechSelection_t
std::string concept;
AI_Response * pResponse;
EHANDLE hSpeechTarget;
#endif
};
//-------------------------------------

View File

@ -1240,7 +1240,7 @@ char *CAI_Expresser::ParseApplyContext( const char *szContext )
{
// If it's really 0, then this is a waste of time
Warning("\"%s\" was detected by applyContext operators as an operable number, but it's not.\n", szValue);
return strdup(szContext);
return szContext;
}
// This is the existing value; will be operated upon and become the final assignment

View File

@ -5,6 +5,9 @@
// $NoKeywords: $
//=============================================================================//
#ifdef NEW_RESPONSE_SYSTEM
#include "ai_speech_new.h"
#else
#ifndef AI_SPEECH_H
#define AI_SPEECH_H
@ -452,3 +455,4 @@ inline void CAI_ExpresserHost<BASE_NPC>::DispatchResponse( const char *conceptNa
//-----------------------------------------------------------------------------
#endif // AI_SPEECH_H
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,705 @@
//========= Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef AI_SPEECH_H
#define AI_SPEECH_H
#include "utlmap.h"
#include "soundflags.h"
#include "AI_Criteria.h"
#include "AI_ResponseSystem.h"
#include "utldict.h"
#include "ai_speechconcept.h"
#if defined( _WIN32 )
#pragma once
#endif
class KeyValues;
using ResponseRules::ResponseType_t;
using ResponseRules::AI_ResponseFollowup;
//-----------------------------------------------------------------------------
// Purpose: Used to share a global resource or prevent a system stepping on
// own toes.
//-----------------------------------------------------------------------------
class CAI_TimedSemaphore
{
public:
CAI_TimedSemaphore()
: m_ReleaseTime( 0 )
{
m_hCurrentTalker = NULL;
}
void Acquire( float time, CBaseEntity *pTalker ) { m_ReleaseTime = gpGlobals->curtime + time; m_hCurrentTalker = pTalker; }
void Release() { m_ReleaseTime = 0; m_hCurrentTalker = NULL; }
// Current owner of the semaphore is always allowed to talk
bool IsAvailable( CBaseEntity *pTalker ) const { return ((gpGlobals->curtime > m_ReleaseTime) || (m_hCurrentTalker == pTalker)); }
float GetReleaseTime() const { return m_ReleaseTime; }
CBaseEntity *GetOwner() { return m_hCurrentTalker; }
private:
float m_ReleaseTime;
EHANDLE m_hCurrentTalker;
};
//-----------------------------------------------------------------------------
extern CAI_TimedSemaphore g_AIFriendliesTalkSemaphore;
extern CAI_TimedSemaphore g_AIFoesTalkSemaphore;
#define GetSpeechSemaphore( pNpc ) (((pNpc)->IsPlayerAlly()) ? &g_AIFriendliesTalkSemaphore : &g_AIFoesTalkSemaphore )
//-----------------------------------------------------------------------------
// Basic speech system types
//-----------------------------------------------------------------------------
//-------------------------------------
// Constants
const float AIS_NO_DELAY = 0;
const soundlevel_t AIS_DEF_SNDLVL = SNDLVL_TALKING;
#define AI_NULL_CONCEPT NULL
#define AI_NULL_SENTENCE NULL
// Sentence prefix constants
#define AI_SP_SPECIFIC_SENTENCE '!'
#define AI_SP_WAVFILE '^'
#define AI_SP_SCENE_GROUP '='
#define AI_SP_SPECIFIC_SCENE '?'
#define AI_SPECIFIC_SENTENCE(str_constant) "!" str_constant
#define AI_WAVFILE(str_constant) "^" str_constant
// @Note (toml 09-12-02): as scene groups are not currently implemented, the string is a semi-colon delimited list
#define AI_SCENE_GROUP(str_constant) "=" str_constant
#define AI_SPECIFIC_SCENE(str_constant) "?" str_constant
// Designer overriding modifiers
#define AI_SPECIFIC_SCENE_MODIFIER "scene:"
//-------------------------------------
//-------------------------------------
// An id that represents the core meaning of a spoken phrase,
// eventually to be mapped to a sentence group or scene
#if AI_CONCEPTS_ARE_STRINGS
typedef const char *AIConcept_t;
inline bool CompareConcepts( AIConcept_t c1, AIConcept_t c2 )
{
return ( (void *)c1 == (void *)c2 || ( c1 && c2 && Q_stricmp( c1, c2 ) == 0 ) );
}
#else
typedef CAI_Concept AIConcept_t;
inline bool CompareConcepts( AIConcept_t c1, AIConcept_t c2 )
{
return c1.m_iConcept == c2.m_iConcept;
}
#endif
//-----------------------------------------------------------------------------
// CAI_Expresser
//
// Purpose: Provides the functionality of going from abstract concept ("hello")
// to specific sentence/scene/wave
//
//-------------------------------------
// Sink supports behavior control and receives notifications of internal events
class CAI_ExpresserSink
{
public:
virtual void OnSpokeConcept( AIConcept_t concept, AI_Response *response ) {};
virtual void OnStartSpeaking() {}
virtual bool UseSemaphore() { return true; }
};
struct ConceptHistory_t
{
DECLARE_SIMPLE_DATADESC();
ConceptHistory_t(float timeSpoken = -1 )
: timeSpoken( timeSpoken ), m_response( )
{
}
ConceptHistory_t( const ConceptHistory_t& src );
ConceptHistory_t& operator = ( const ConceptHistory_t& src );
~ConceptHistory_t();
float timeSpoken;
AI_Response m_response;
};
//-------------------------------------
class CAI_Expresser : public ResponseRules::IResponseFilter
{
public:
CAI_Expresser( CBaseFlex *pOuter = NULL );
~CAI_Expresser();
// --------------------------------
bool Connect( CAI_ExpresserSink *pSink ) { m_pSink = pSink; return true; }
bool Disconnect( CAI_ExpresserSink *pSink ) { m_pSink = NULL; return true;}
void TestAllResponses();
// --------------------------------
bool Speak( AIConcept_t concept, const char *modifiers = NULL, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL );
bool Speak( const AIConcept_t &concept, AI_CriteriaSet *criteria, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL );
// Given modifiers (which are colon-delimited strings), fill out a criteria set including this
// character's contexts and the ones in the modifier. This lets us hang on to them after a call
// to SpeakFindResponse.
void GatherCriteria( AI_CriteriaSet *outputCritera, const AIConcept_t &concept, const char *modifiers );
// These two methods allow looking up a response and dispatching it to be two different steps
// AI_Response *SpeakFindResponse( AIConcept_t concept, const char *modifiers = NULL );
// AI_Response *SpeakFindResponse( AIConcept_t &concept, AI_CriteriaSet *criteria );
// Find the appropriate response for the given concept. Return false if none found.
// Fills out the response object that you provide.
bool FindResponse( AI_Response &outResponse, const AIConcept_t &concept, AI_CriteriaSet *modifiers = NULL );
virtual bool SpeakDispatchResponse( AIConcept_t concept, AI_Response *response, AI_CriteriaSet *criteria, IRecipientFilter *filter = NULL );
float GetResponseDuration( AI_Response *response );
#ifdef MAPBASE
void SetUsingProspectiveResponses( bool bToggle );
void MarkResponseAsUsed( AI_Response *response );
#endif
virtual int SpeakRawSentence( const char *pszSentence, float delay, float volume = VOL_NORM, soundlevel_t soundlevel = SNDLVL_TALKING, CBaseEntity *pListener = NULL );
bool SemaphoreIsAvailable( CBaseEntity *pTalker );
float GetSemaphoreAvailableTime( CBaseEntity *pTalker );
virtual void OnSpeechFinished() {};
// This function can be overriden by games to suppress speech altogether during glue screens, etc
static bool IsSpeechGloballySuppressed();
// --------------------------------
virtual bool IsSpeaking();
bool CanSpeak();
bool CanSpeakAfterMyself();
float GetTimeSpeechComplete() const { return m_flStopTalkTime; }
#ifdef MAPBASE
float GetTimeSpeechCompleteWithoutDelay() const { return m_flStopTalkTimeWithoutDelay; }
#endif
void BlockSpeechUntil( float time );
// --------------------------------
bool CanSpeakConcept( const AIConcept_t &concept );
bool SpokeConcept( const AIConcept_t &concept );
float GetTimeSpokeConcept( const AIConcept_t &concept ); // returns -1 if never
void SetSpokeConcept( const AIConcept_t &concept, AI_Response *response, bool bCallback = true );
void ClearSpokeConcept( const AIConcept_t &concept );
// --------------------------------
void SetVoicePitch( int voicePitch ) { m_voicePitch = voicePitch; }
int GetVoicePitch() const;
void NoteSpeaking( float duration, float delay = 0 );
// Force the NPC to release the semaphore & clear next speech time
void ForceNotSpeaking( void );
#ifdef MAPBASE_VSCRIPT
bool ScriptSpeakRawScene( char const *soundname, float delay ) { return SpeakRawScene( soundname, delay, NULL ); }
bool ScriptSpeakAutoGeneratedScene( char const *soundname, float delay ) { return SpeakAutoGeneratedScene( soundname, delay ); }
int ScriptSpeakRawSentence( char const *pszSentence, float delay ) { return SpeakRawSentence( pszSentence, delay ); }
bool ScriptSpeak( char const *concept, const char *modifiers ) { return Speak( concept, modifiers[0] != '\0' ? modifiers : NULL ); }
#endif
// helper used in dealing with RESPONSE_ENTITYIO
// response is the output of AI_Response::GetName
// note: the response string will get stomped on (by strtok)
// returns false on failure (eg, couldn't match parse contents)
static bool FireEntIOFromResponse( char *response, CBaseEntity *pInitiator );
#ifdef MAPBASE_VSCRIPT
// Used for RESPONSE_VSCRIPT(_FILE)
static bool RunScriptResponse( CBaseEntity *pTarget, const char *response, AI_CriteriaSet *criteria, bool file );
#endif
protected:
CAI_TimedSemaphore *GetMySpeechSemaphore( CBaseEntity *pNpc );
bool SpeakRawScene( const char *pszScene, float delay, AI_Response *response, IRecipientFilter *filter = NULL );
// This will create a fake .vcd/CChoreoScene to wrap the sound to be played
bool SpeakAutoGeneratedScene( char const *soundname, float delay );
void DumpHistories();
void SpeechMsg( CBaseEntity *pFlex, PRINTF_FORMAT_STRING const char *pszFormat, ... ) FMTFUNCTION(3, 4);
// --------------------------------
CAI_ExpresserSink *GetSink() { return m_pSink; }
private:
// --------------------------------
virtual bool IsValidResponse( ResponseType_t type, const char *pszValue );
// --------------------------------
CAI_ExpresserSink *m_pSink;
// --------------------------------
//
// Speech concept data structures
//
CUtlDict< ConceptHistory_t, int > m_ConceptHistories;
// --------------------------------
//
// Speaking states
//
float m_flStopTalkTime; // when in the future that I'll be done saying this sentence.
float m_flStopTalkTimeWithoutDelay; // same as the above, but minus the delay before other people can speak
float m_flBlockedTalkTime;
int m_voicePitch; // pitch of voice for this head
float m_flLastTimeAcceptedSpeak; // because speech may not be blocked until NoteSpeaking called by scene ent, this handles in-think blocking
DECLARE_SIMPLE_DATADESC();
// --------------------------------
//
public:
void SetOuter( CBaseFlex *pOuter );
CBaseFlex * GetOuter() { return m_pOuter; }
const CBaseFlex * GetOuter() const { return m_pOuter; }
private:
CHandle<CBaseFlex> m_pOuter;
};
//-----------------------------------------------------------------------------
//
// An NPC base class to assist a branch of the inheritance graph
// in utilizing CAI_Expresser
//
template <class BASE_NPC>
class CAI_ExpresserHost : public BASE_NPC, protected CAI_ExpresserSink
{
DECLARE_CLASS_NOFRIEND( CAI_ExpresserHost, BASE_NPC );
public:
virtual void NoteSpeaking( float duration, float delay );
virtual bool Speak( AIConcept_t concept, const char *modifiers = NULL, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL );
virtual bool Speak( AIConcept_t concept, AI_CriteriaSet *pCriteria, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL );
#ifdef MAPBASE
virtual bool Speak( AIConcept_t concept, AI_CriteriaSet& modifiers, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL ) { return Speak( concept, &modifiers, pszOutResponseChosen, bufsize, filter ); }
#endif
void GatherCriteria( AI_CriteriaSet *outputCritera, const AIConcept_t &concept, const char *modifiers );
// These two methods allow looking up a response and dispatching it to be two different steps
#ifdef MAPBASE
//AI_Response *SpeakFindResponse( AIConcept_t concept, const AI_CriteriaSet& modifiers );
inline bool SpeakDispatchResponse( const AIConcept_t &concept, AI_Response &response, AI_CriteriaSet *criteria = NULL ) { return SpeakDispatchResponse( concept, &response, criteria ); }
#endif
bool SpeakFindResponse( AI_Response& outResponse, const AIConcept_t &concept, const char *modifiers = NULL );
// AI_Response * SpeakFindResponse( AIConcept_t concept, const char *modifiers = NULL );
// AI_Response *SpeakFindResponse( AIConcept_t concept, AI_CriteriaSet *criteria );
// AI_Response *SpeakFindResponse( AIConcept_t concept );
// Find the appropriate response for the given concept. Return false if none found.
// Fills out the response object that you provide.
bool FindResponse( AI_Response &outResponse, const AIConcept_t &concept, AI_CriteriaSet *criteria = NULL );
bool SpeakDispatchResponse( AIConcept_t concept, AI_Response *response, AI_CriteriaSet *criteria = NULL );
virtual void PostSpeakDispatchResponse( AIConcept_t concept, AI_Response *response ) { return; }
float GetResponseDuration( AI_Response *response );
float GetTimeSpeechComplete() const { return this->GetExpresser()->GetTimeSpeechComplete(); }
bool IsSpeaking() { return this->GetExpresser()->IsSpeaking(); }
bool CanSpeak() { return this->GetExpresser()->CanSpeak(); }
bool CanSpeakAfterMyself() { return this->GetExpresser()->CanSpeakAfterMyself(); }
void SetSpokeConcept( AIConcept_t concept, AI_Response *response, bool bCallback = true ) { this->GetExpresser()->SetSpokeConcept( concept, response, bCallback ); }
float GetTimeSpokeConcept( AIConcept_t concept ) { return this->GetExpresser()->GetTimeSpokeConcept( concept ); }
bool SpokeConcept( AIConcept_t concept ) { return this->GetExpresser()->SpokeConcept( concept ); }
protected:
int PlaySentence( const char *pszSentence, float delay, float volume = VOL_NORM, soundlevel_t soundlevel = SNDLVL_TALKING, CBaseEntity *pListener = NULL );
virtual void ModifyOrAppendCriteria( AI_CriteriaSet& set );
virtual IResponseSystem *GetResponseSystem();
// Override of base entity response input handler
virtual void DispatchResponse( const char *conceptName );
};
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline void CAI_ExpresserHost<BASE_NPC>::NoteSpeaking( float duration, float delay )
{
this->GetExpresser()->NoteSpeaking( duration, delay );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline bool CAI_ExpresserHost<BASE_NPC>::Speak( AIConcept_t concept, const char *modifiers /*= NULL*/, char *pszOutResponseChosen /*=NULL*/, size_t bufsize /* = 0 */, IRecipientFilter *filter /* = NULL */ )
{
AssertOnce( this->GetExpresser()->GetOuter() == this );
return this->GetExpresser()->Speak( concept, modifiers, pszOutResponseChosen, bufsize, filter );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline bool CAI_ExpresserHost<BASE_NPC>::Speak( AIConcept_t concept, AI_CriteriaSet *pCriteria, char *pszOutResponseChosen /*=NULL*/, size_t bufsize /* = 0 */, IRecipientFilter *filter /* = NULL */ )
{
AssertOnce( this->GetExpresser()->GetOuter() == this );
CAI_Expresser * const RESTRICT pExpresser = this->GetExpresser();
concept.SetSpeaker(this);
// add in any local criteria to the one passed on the command line.
pExpresser->GatherCriteria( pCriteria, concept, NULL );
// call the "I have aleady gathered criteria" version of Expresser::Speak
return pExpresser->Speak( concept, pCriteria, pszOutResponseChosen, bufsize, filter );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline int CAI_ExpresserHost<BASE_NPC>::PlaySentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, CBaseEntity *pListener )
{
return this->GetExpresser()->SpeakRawSentence( pszSentence, delay, volume, soundlevel, pListener );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
extern void CAI_ExpresserHost_NPC_DoModifyOrAppendCriteria( CAI_BaseNPC *pSpeaker, AI_CriteriaSet& criteriaSet );
template <class BASE_NPC>
inline void CAI_ExpresserHost<BASE_NPC>::ModifyOrAppendCriteria( AI_CriteriaSet& criteriaSet )
{
BaseClass::ModifyOrAppendCriteria( criteriaSet );
if ( this->MyNPCPointer() )
{
CAI_ExpresserHost_NPC_DoModifyOrAppendCriteria( this->MyNPCPointer(), criteriaSet );
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline IResponseSystem *CAI_ExpresserHost<BASE_NPC>::GetResponseSystem()
{
extern IResponseSystem *g_pResponseSystem;
// Expressive NPC's use the general response system
return g_pResponseSystem;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline void CAI_ExpresserHost<BASE_NPC>::GatherCriteria( AI_CriteriaSet *outputCriteria, const AIConcept_t &concept, const char *modifiers )
{
return this->GetExpresser()->GatherCriteria( outputCriteria, concept, modifiers );
}
#if 1
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline bool CAI_ExpresserHost<BASE_NPC>::SpeakFindResponse(AI_Response& outResponse, const AIConcept_t &concept, const char *modifiers /*= NULL*/ )
{
AI_CriteriaSet criteria;
GatherCriteria(&criteria, concept, modifiers);
return FindResponse( outResponse, concept, &criteria );
}
#else
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline AI_Response *CAI_ExpresserHost<BASE_NPC>::SpeakFindResponse( const AIConcept_t &concept, const char *modifiers /*= NULL*/ )
{
return this->GetExpresser()->SpeakFindResponse( concept, modifiers );
}
#endif
#if 0
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline AI_Response *CAI_ExpresserHost<BASE_NPC>::SpeakFindResponse( const AIConcept_t &concept, AI_CriteriaSet *criteria /*= NULL*/ )
{
return this->GetExpresser()->SpeakFindResponse( concept, criteria );
}
//-----------------------------------------------------------------------------
// In this case we clearly don't care to hang on to the criteria, so make a convenience
// class that generates a one off.
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline AI_Response * CAI_ExpresserHost<BASE_NPC>::SpeakFindResponse( const AIConcept_t &concept )
{
AI_CriteriaSet criteria;
GatherCriteria( &criteria, concept, NULL );
return this->GetExpresser()->SpeakFindResponse( concept, &criteria );
}
#endif
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline bool CAI_ExpresserHost<BASE_NPC>::FindResponse( AI_Response &outResponse, const AIConcept_t &concept, AI_CriteriaSet *criteria )
{
return this->GetExpresser()->FindResponse( outResponse, concept, criteria );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline bool CAI_ExpresserHost<BASE_NPC>::SpeakDispatchResponse( AIConcept_t concept, AI_Response *response, AI_CriteriaSet *criteria )
{
if ( this->GetExpresser()->SpeakDispatchResponse( concept, response, criteria ) )
{
PostSpeakDispatchResponse( concept, response );
return true;
}
return false;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline float CAI_ExpresserHost<BASE_NPC>::GetResponseDuration( AI_Response *response )
{
return this->GetExpresser()->GetResponseDuration( response );
}
//-----------------------------------------------------------------------------
// Override of base entity response input handler
//-----------------------------------------------------------------------------
template <class BASE_NPC>
inline void CAI_ExpresserHost<BASE_NPC>::DispatchResponse( const char *conceptName )
{
Speak( AIConcept_t( conceptName ) );
}
//-----------------------------------------------------------------------------
/// A shim under CAI_ExpresserHost you can use when deriving a new expresser
/// host type under CAI_BaseNPC. This does the extra step of declaring an m_pExpresser
/// member and initializing it from CreateComponents(). If your BASE_NPC class isn't
/// actually an NPC, then CreateComponents() never gets called and you won't have
/// an expresser created.
/// Note: you still need to add m_pExpresser to the Datadesc for your derived type.
/// This is because I couldn't figure out how to make a templatized datadesc declaration
/// that works generically on the template type.
template <class BASE_NPC, class EXPRESSER_TYPE>
class CAI_ExpresserHostWithData : public CAI_ExpresserHost<BASE_NPC>
{
DECLARE_CLASS_NOFRIEND( CAI_ExpresserHostWithData, CAI_ExpresserHost<BASE_NPC> );
public:
CAI_ExpresserHostWithData( ) : m_pExpresser(NULL) {};
virtual CAI_Expresser *GetExpresser() { return m_pExpresser; }
const CAI_Expresser *GetExpresser() const { return m_pExpresser; }
virtual bool CreateComponents()
{
return BaseClass::CreateComponents() && ( CreateExpresser() != NULL );
}
protected:
EXPRESSER_TYPE *CreateExpresser( void )
{
AssertMsg1( m_pExpresser == NULL, "Tried to double-initialize expresser in %s\n", this->GetDebugName() );
m_pExpresser = new EXPRESSER_TYPE(this);
if ( !m_pExpresser)
{
AssertMsg1( false, "Creating an expresser failed in %s\n", this->GetDebugName() );
return NULL;
}
m_pExpresser->Connect(this);
return m_pExpresser;
}
virtual ~CAI_ExpresserHostWithData( void )
{
delete m_pExpresser;
m_pExpresser = NULL;
}
EXPRESSER_TYPE *m_pExpresser;
};
/// response rules
namespace RR
{
/// some applycontext clauses have operators preceding them,
/// like ++1 which means "take the current value and increment it
/// by one". These classes detect these cases and do the appropriate
/// thing.
class CApplyContextOperator
{
public:
inline CApplyContextOperator( int nSkipChars ) : m_nSkipChars(nSkipChars) {};
/// perform whatever this operator does upon the given context value.
/// Default op is simply to copy old to new.
/// pOldValue should be the currently set value of the context. May be NULL meaning no prior value.
/// pOperator the value that applycontext says to set
/// pNewValue a pointer to a buffer where the real new value will be writ.
/// returns true on success; false on failure (eg, tried to increment a
/// non-numeric value).
virtual bool Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize );
/// This is the function that should be called from outside,
/// fed the input string, it'll select the right operator
/// to apply.
static CApplyContextOperator *FindOperator( const char *pContextString );
protected:
int m_nSkipChars; // how many chars to "skip" in the value string to get past the op specifier to the actual value
// eg, "++3" has a m_nSkipChars of 2, because the op string "++" is two characters.
};
class CIncrementOperator : public CApplyContextOperator
{
public:
inline CIncrementOperator( int nSkipChars ) : CApplyContextOperator(nSkipChars) {};
virtual bool Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize );
};
class CDecrementOperator : public CApplyContextOperator
{
public:
inline CDecrementOperator( int nSkipChars ) : CApplyContextOperator(nSkipChars) {};
virtual bool Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize );
};
#ifdef MAPBASE
class CMultiplyOperator : public CApplyContextOperator
{
public:
inline CMultiplyOperator( int nSkipChars ) : CApplyContextOperator(nSkipChars) {};
virtual bool Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize );
};
class CDivideOperator : public CApplyContextOperator
{
public:
inline CDivideOperator( int nSkipChars ) : CApplyContextOperator(nSkipChars) {};
virtual bool Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize );
};
#endif
class CToggleOperator : public CApplyContextOperator
{
public:
inline CToggleOperator( int nSkipChars ) : CApplyContextOperator(nSkipChars) {};
virtual bool Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize );
};
// the singleton operators
extern CApplyContextOperator sm_OpCopy;
extern CIncrementOperator sm_OpIncrement;
extern CDecrementOperator sm_OpDecrement;
#ifdef MAPBASE
extern CMultiplyOperator sm_OpMultiply;
extern CDivideOperator sm_OpDivide;
#endif
extern CToggleOperator sm_OpToggle;
#ifdef MAPBASE
// LEGACY - See CApplyContextOperator::FindOperator()
extern CIncrementOperator sm_OpLegacyIncrement;
extern CDecrementOperator sm_OpLegacyDecrement;
extern CMultiplyOperator sm_OpLegacyMultiply;
extern CDivideOperator sm_OpLegacyDivide;
#endif
};
//-----------------------------------------------------------------------------
#include "ai_speechqueue.h"
//-----------------------------------------------------------------------------
// A kind of AI Expresser that can dispatch a follow-up speech event when it
// finishes speaking.
//-----------------------------------------------------------------------------
class CAI_ExpresserWithFollowup : public CAI_Expresser
{
public:
CAI_ExpresserWithFollowup( CBaseFlex *pOuter = NULL ) : CAI_Expresser(pOuter),
m_pPostponedFollowup(NULL)
{};
virtual bool Speak( AIConcept_t concept, const char *modifiers = NULL, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL );
virtual bool SpeakDispatchResponse( AIConcept_t concept, AI_Response *response, AI_CriteriaSet *criteria, IRecipientFilter *filter = NULL );
virtual void SpeakDispatchFollowup( AI_ResponseFollowup &followup );
virtual void OnSpeechFinished();
typedef CAI_Expresser BaseClass;
protected:
static void DispatchFollowupThroughQueue( const AIConcept_t &concept,
const char *criteriaStr,
const CResponseQueue::CFollowupTargetSpec_t &target,
float delay,
CBaseEntity * RESTRICT pOuter );
AI_ResponseFollowup *m_pPostponedFollowup; // TODO: save/restore
CResponseQueue::CFollowupTargetSpec_t m_followupTarget;
};
class CMultiplayer_Expresser : public CAI_ExpresserWithFollowup
{
public:
CMultiplayer_Expresser( CBaseFlex *pOuter = NULL );
//~CMultiplayer_Expresser();
virtual bool IsSpeaking();
void AllowMultipleScenes();
void DisallowMultipleScenes();
private:
bool m_bAllowMultipleScenes;
};
#endif // AI_SPEECH_H

View File

@ -0,0 +1,495 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "basemultiplayerplayer.h"
#include "ai_baseactor.h"
#include "ai_speech.h"
//#include "flex_expresser.h"
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
extern ConVar ai_debug_speech;
#define DebuggingSpeech() ai_debug_speech.GetBool()
extern ConVar rr_debugresponses;
ConVar rr_followup_maxdist( "rr_followup_maxdist", "1800", FCVAR_CHEAT, "'then ANY' or 'then ALL' response followups will be dispatched only to characters within this distance." );
///////////////////////////////////////////////////////////////////////////////
// RESPONSE QUEUE DATA STRUCTURE
///////////////////////////////////////////////////////////////////////////////
CResponseQueue::CResponseQueue( int queueSize ) : m_Queue(queueSize), m_ExpresserTargets(8,8)
{};
/// Add a deferred response.
void CResponseQueue::Add( const AIConcept_t &concept, ///< concept to dispatch
const AI_CriteriaSet * RESTRICT contexts,
float time, ///< when to dispatch it. You can specify a time of zero to mean "immediately."
const CFollowupTargetSpec_t &targetspec,
CBaseEntity *pIssuer
)
{
// Add a response.
AssertMsg( m_Queue.Count() < AI_RESPONSE_QUEUE_SIZE, "AI Response queue overfilled." );
QueueType_t::IndexLocalType_t idx = m_Queue.AddToTail();
m_Queue[idx].Init( concept, contexts, time, targetspec, pIssuer );
}
/// Remove a deferred response matching the concept and issuer.
void CResponseQueue::Remove( const AIConcept_t &concept, ///< concept to dispatch
CBaseEntity * const RESTRICT pIssuer ///< the entity issuing the response, if one exists.
) RESTRICT
{
// walk through the queue until we find a response matching the concept and issuer, then strike it.
QueueType_t::IndexLocalType_t idx = m_Queue.Head();
while (idx != m_Queue.InvalidIndex())
{
CDeferredResponse &response = m_Queue[idx];
QueueType_t::IndexLocalType_t previdx = idx; // advance the index immediately because we may be deleting the "current" element
idx = m_Queue.Next(idx); // is now the next index
if ( CompareConcepts( response.m_concept, concept ) && // if concepts match and
( !pIssuer || ( response.m_hIssuer.Get() == pIssuer ) ) // issuer is null, or matches the one in the response
)
{
m_Queue.Remove(previdx);
}
}
}
void CResponseQueue::RemoveSpeechQueuedFor( const CBaseEntity *pSpeaker )
{
// walk through the queue until we find a response matching the speaker, then strike it.
// because responses are dispatched from inside a loop that is already walking through the
// queue, it's not safe to actually remove the elements. Instead, quash it by replacing it
// with a null event.
for ( QueueType_t::IndexLocalType_t idx = m_Queue.Head() ;
idx != m_Queue.InvalidIndex() ;
idx = m_Queue.Next(idx) ) // is now the next index
{
CDeferredResponse &response = m_Queue[idx];
if ( response.m_Target.m_hHandle.Get() == pSpeaker )
{
response.Quash();
}
}
}
// TODO: use a more compact representation.
void CResponseQueue::DeferContextsFromCriteriaSet( DeferredContexts_t &contextsOut, const AI_CriteriaSet * RESTRICT criteriaIn )
{
contextsOut.Reset();
if (criteriaIn)
{
contextsOut.Merge(criteriaIn);
}
}
void CResponseQueue::PerFrameDispatch()
{
failsafe:
// Walk through the list, find any messages whose time has come, and dispatch them. Then remove them.
QueueType_t::IndexLocalType_t idx = m_Queue.Head();
while (idx != m_Queue.InvalidIndex())
{
// do we need to dispatch this concept?
CDeferredResponse &response = m_Queue[idx];
QueueType_t::IndexLocalType_t previdx = idx; // advance the index immediately because we may be deleting the "current" element
idx = m_Queue.Next(idx); // is now the next index
if ( response.IsQuashed() )
{
// we can delete this entry now
m_Queue.Remove(previdx);
}
else if ( response.m_fDispatchTime <= gpGlobals->curtime )
{
// dispatch. we've had bugs where dispatches removed things from inside the queue;
// so, as a failsafe, if the queue length changes as a result, start over.
int oldLength = m_Queue.Count();
DispatchOneResponse(response);
if ( m_Queue.Count() < oldLength )
{
AssertMsg( false, "Response queue length changed in non-reentrant way! FAILSAFE TRIGGERED" );
goto failsafe; // ick
}
// we can delete this entry now
m_Queue.Remove(previdx);
}
}
}
/// Add an expressor owner to this queue.
void CResponseQueue::AddExpresserHost(CBaseEntity *host)
{
EHANDLE ehost(host);
// see if it's in there already
if (m_ExpresserTargets.HasElement(ehost))
{
AssertMsg1(false, "Tried to add %s to response queue when it was already in there.", host->GetDebugName());
}
else
{
// zip through the queue front to back, first see if there's any invalid handles to replace
int count = m_ExpresserTargets.Count();
for (int i = 0 ; i < count ; ++i )
{
if ( !m_ExpresserTargets[i].Get() )
{
m_ExpresserTargets[i] = ehost;
return;
}
}
// if we're down here we didn't find one to replace, so append the host to the end.
m_ExpresserTargets.AddToTail(ehost);
}
}
/// Remove an expresser host from this queue.
void CResponseQueue::RemoveExpresserHost(CBaseEntity *host)
{
int idx = m_ExpresserTargets.Find(host);
if (idx == -1)
{
// AssertMsg1(false, "Tried to remove %s from response queue, but it's not in there to begin with!", host->GetDebugName() );
}
else
{
m_ExpresserTargets.FastRemove(idx);
}
}
/// Get the expresser for a base entity.
/// TODO: Kind of an ugly hack until I get the class hierarchy straightened out.
static CAI_Expresser *InferExpresserFromBaseEntity(CBaseEntity * RESTRICT pEnt)
{
#ifdef MAPBASE
if ( CBasePlayer *pPlayer = ToBasePlayer(pEnt) )
#else
if ( CBaseMultiplayerPlayer *pPlayer = dynamic_cast<CBaseMultiplayerPlayer *>(pEnt) )
#endif
{
return pPlayer->GetExpresser();
}
else if ( CAI_BaseActor *pActor = dynamic_cast<CAI_BaseActor *>(pEnt) )
{
return pActor->GetExpresser();
}
/*
else if ( CFlexExpresser *pFlex = dynamic_cast<CFlexExpresser *>(pEnt) )
{
return pFlex->GetExpresser();
}
*/
else
{
return NULL;
}
}
void CResponseQueue::CDeferredResponse::Quash()
{
m_Target = CFollowupTargetSpec_t();
m_fDispatchTime = 0;
}
bool CResponseQueue::DispatchOneResponse(CDeferredResponse &response)
{
// find the target.
CBaseEntity * RESTRICT pTarget = NULL;
AI_CriteriaSet &deferredCriteria = response.m_contexts;
CAI_Expresser * RESTRICT pEx = NULL;
CBaseEntity * RESTRICT pIssuer = response.m_hIssuer.Get(); // MAY BE NULL
float followupMaxDistSq;
{
/*
CFlexExpresser * RESTRICT pOrator = CFlexExpresser::AsFlexExpresser( pIssuer );
if ( pOrator )
{
// max dist is overridden. "0" means infinite distance (for orators only),
// anything else is a finite distance.
if ( pOrator->m_flThenAnyMaxDist > 0 )
{
followupMaxDistSq = pOrator->m_flThenAnyMaxDist * pOrator->m_flThenAnyMaxDist;
}
else
{
followupMaxDistSq = FLT_MAX;
}
}
else
*/
{
followupMaxDistSq = rr_followup_maxdist.GetFloat(); // square of max audibility distance
followupMaxDistSq *= followupMaxDistSq;
}
}
switch (response.m_Target.m_iTargetType)
{
case kDRT_SPECIFIC:
{
pTarget = response.m_Target.m_hHandle.Get();
}
break;
case kDRT_ANY:
{
return DispatchOneResponse_ThenANY( response, &deferredCriteria, pIssuer, followupMaxDistSq );
}
break;
case kDRT_ALL:
{
bool bSaidAnything = false;
Vector issuerLocation;
if ( pIssuer )
{
issuerLocation = pIssuer->GetAbsOrigin();
}
// find all characters
int numExprs = GetNumExpresserTargets();
for ( int i = 0 ; i < numExprs; ++i )
{
pTarget = GetExpresserHost(i);
float distIssuerToTargetSq = 0.0f;
if ( pIssuer )
{
distIssuerToTargetSq = (pTarget->GetAbsOrigin() - issuerLocation).LengthSqr();
if ( distIssuerToTargetSq > followupMaxDistSq )
continue; // too far
}
pEx = InferExpresserFromBaseEntity(pTarget);
if ( !pEx || pTarget == pIssuer )
continue;
AI_CriteriaSet characterCriteria;
pEx->GatherCriteria(&characterCriteria, response.m_concept, NULL);
characterCriteria.Merge(&deferredCriteria);
if ( pIssuer )
{
characterCriteria.AppendCriteria( "dist_from_issuer", UTIL_VarArgs( "%f", sqrt(distIssuerToTargetSq) ) );
}
AI_Response prospectiveResponse;
if ( pEx->FindResponse( prospectiveResponse, response.m_concept, &characterCriteria ) )
{
// dispatch it
bSaidAnything = pEx->SpeakDispatchResponse(response.m_concept, &prospectiveResponse, &deferredCriteria) || bSaidAnything ;
}
}
return bSaidAnything;
}
break;
default:
// WTF?
AssertMsg1( false, "Unknown deferred response type %d\n", response.m_Target.m_iTargetType );
return false;
}
if (!pTarget)
return false; // we're done right here.
// Get the expresser for the target.
pEx = InferExpresserFromBaseEntity(pTarget);
if (!pEx)
return false;
AI_CriteriaSet characterCriteria;
pEx->GatherCriteria(&characterCriteria, response.m_concept, NULL);
characterCriteria.Merge(&deferredCriteria);
pEx->Speak( response.m_concept, &characterCriteria );
return true;
}
//
ConVar rr_thenany_score_slop( "rr_thenany_score_slop", "0.0", FCVAR_CHEAT, "When computing respondents for a 'THEN ANY' rule, all rule-matching scores within this much of the best score will be considered." );
#define EXARRAYMAX 32 // maximum number of prospective expressers in the array (hardcoded for simplicity)
bool CResponseQueue::DispatchOneResponse_ThenANY( CDeferredResponse &response, AI_CriteriaSet * RESTRICT pDeferredCriteria, CBaseEntity * const RESTRICT pIssuer, float followupMaxDistSq )
{
CBaseEntity * RESTRICT pTarget = NULL;
CAI_Expresser * RESTRICT pEx = NULL;
float bestScore = 0;
float slop = rr_thenany_score_slop.GetFloat();
Vector issuerLocation;
if ( pIssuer )
{
issuerLocation = pIssuer->GetAbsOrigin();
}
// this is an array of prospective respondents.
CAI_Expresser * RESTRICT pBestEx[EXARRAYMAX];
AI_Response responseToSay[EXARRAYMAX];
int numExFound = 0; // and this is the high water mark for the array.
// Here's the algorithm: we're going to walk through all the characters, finding the
// highest scoring ones for this rule. Let the highest score be called k.
// Because there may be (n) many characters all scoring k, we store an array of
// all characters with score k, then choose randomly from that array at return.
// We also define an allowable error for k in the global cvar
// rr_thenany_score_slop , which may be zero.
// find all characters (except the issuer)
int numExprs = GetNumExpresserTargets();
AssertMsg1( numExprs <= EXARRAYMAX, "Response queue has %d possible expresser targets, please increase EXARRAYMAX ", numExprs );
for ( int i = 0 ; i < numExprs; ++i )
{
pTarget = GetExpresserHost(i);
if ( pTarget == pIssuer )
continue; // don't dispatch to myself
if ( !pTarget->IsAlive() )
continue; // dead men tell no tales
float distIssuerToTargetSq = 0.0f;
if ( pIssuer )
{
distIssuerToTargetSq = (pTarget->GetAbsOrigin() - issuerLocation).LengthSqr();
if ( distIssuerToTargetSq > followupMaxDistSq )
continue; // too far
}
pEx = InferExpresserFromBaseEntity(pTarget);
if ( !pEx )
continue;
AI_CriteriaSet characterCriteria;
pEx->GatherCriteria(&characterCriteria, response.m_concept, NULL);
characterCriteria.Merge( pDeferredCriteria );
pTarget->ModifyOrAppendDerivedCriteria( characterCriteria );
if ( pIssuer )
{
characterCriteria.AppendCriteria( "dist_from_issuer", UTIL_VarArgs( "%f", sqrt(distIssuerToTargetSq) ) );
}
AI_Response prospectiveResponse;
#ifdef MAPBASE
pEx->SetUsingProspectiveResponses( true );
#endif
if ( pEx->FindResponse( prospectiveResponse, response.m_concept, &characterCriteria ) )
{
float score = prospectiveResponse.GetMatchScore();
if ( score > 0 && !prospectiveResponse.IsEmpty() ) // ignore scores that are zero, regardless of slop
{
// if this score is better than all we've seen (outside the slop), then replace the array with
// an entry just to this expresser
if ( score > bestScore + slop )
{
responseToSay[0] = prospectiveResponse;
pBestEx[0] = pEx;
bestScore = score;
numExFound = 1;
}
else if ( score >= bestScore - slop ) // if this score is at least as good as the best we've seen, but not better than all
{
if ( numExFound >= EXARRAYMAX )
{
#ifdef MAPBASE
pEx->SetUsingProspectiveResponses( false );
#endif
continue; // SAFETY: don't overflow the array
}
responseToSay[numExFound] = prospectiveResponse;
pBestEx[numExFound] = pEx;
bestScore = fpmax( score, bestScore );
numExFound += 1;
}
}
}
#ifdef MAPBASE
pEx->SetUsingProspectiveResponses( false );
#endif
}
// if I have a response, dispatch it.
if ( numExFound > 0 )
{
// get a random number between 0 and the responses found
int iSelect = numExFound > 1 ? RandomInt( 0, numExFound - 1 ) : 0;
if ( pBestEx[iSelect] != NULL )
{
#ifdef MAPBASE
pBestEx[iSelect]->MarkResponseAsUsed( responseToSay + iSelect );
#endif
return pBestEx[iSelect]->SpeakDispatchResponse( response.m_concept, responseToSay + iSelect, pDeferredCriteria );
}
else
{
AssertMsg( false, "Response queue somehow found a response, but no expresser for it.\n" );
return false;
}
}
else
{ // I did not find a response.
return false;
}
return false; // just in case
}
void CResponseQueue::Evacuate()
{
m_Queue.RemoveAll();
}
#undef EXARRAYMAX
///////////////////////////////////////////////////////////////////////////////
// RESPONSE QUEUE MANAGER
///////////////////////////////////////////////////////////////////////////////
void CResponseQueueManager::LevelInitPreEntity( void )
{
if (m_pQueue == NULL)
{
m_pQueue = new CResponseQueue(AI_RESPONSE_QUEUE_SIZE);
}
}
CResponseQueueManager::~CResponseQueueManager()
{
if (m_pQueue != NULL)
{
delete m_pQueue;
m_pQueue = NULL;
}
}
void CResponseQueueManager::Shutdown()
{
if (m_pQueue != NULL)
{
delete m_pQueue;
m_pQueue = NULL;
}
}
void CResponseQueueManager::FrameUpdatePostEntityThink()
{
Assert(m_pQueue);
m_pQueue->PerFrameDispatch();
}
CResponseQueueManager g_ResponseQueueManager( "CResponseQueueManager" );

View File

@ -0,0 +1,239 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: An event queue of AI concepts that dispatches them to appropriate characters.
//
// $NoKeywords: $
//=============================================================================//
#ifndef AI_SPEECHQUEUE_H
#define AI_SPEECHQUEUE_H
#if defined( _WIN32 )
#pragma once
#endif
#include "ai_speech.h"
#define AI_RESPONSE_QUEUE_SIZE 64
enum DeferredResponseTarget_t // possible targets for a deferred response
{
kDRT_ANY, // best matching respondent within range -- except for the one in the m_hTarget handle
kDRT_ALL, // send to everyone in range -- except for the one in the m_hTarget handle
kDRT_SPECIFIC, // a specific entity is targeted
kDRT_MAX, // high water mark
};
// Allows you to postpone AI speech concepts to a later time, or to direct them to
// a specific character, or all of them.
class CResponseQueue
{
//////////////////// Local types ////////////////////
public:
// We pack up contexts to send along with the concept.
// For now I'll just copy criteria sets, but it will be better to do something
// more efficient in the future.
typedef AI_CriteriaSet DeferredContexts_t;
struct CFollowupTargetSpec_t ///< to whom a followup is directed. Can be a specific entity or something more exotic.
{
DeferredResponseTarget_t m_iTargetType; ///< ANY, ALL, or SPECIFIC. If specific, pass through a handle to:
EHANDLE m_hHandle; ///< a specific target for the message, or a specific character to OMIT.
inline bool IsValid( void ) const;
// constructors/destructors
explicit CFollowupTargetSpec_t(const DeferredResponseTarget_t &targetType, const EHANDLE &handle)
: m_iTargetType(targetType), m_hHandle(handle)
{};
explicit CFollowupTargetSpec_t(const EHANDLE &handle)
: m_iTargetType(kDRT_SPECIFIC), m_hHandle(handle)
{};
CFollowupTargetSpec_t(DeferredResponseTarget_t target) // eg, ANY, ALL, etc.
: m_iTargetType(target)
{
AssertMsg(m_iTargetType != kDRT_SPECIFIC, "Response rule followup tried to specify an entity target, but didn't provide the target.\n" );
}
CFollowupTargetSpec_t(void) // default: invalid
: m_iTargetType(kDRT_MAX)
{};
};
/// A single deferred response.
struct CDeferredResponse
{
AIConcept_t m_concept;
DeferredContexts_t m_contexts; ///< contexts to send along with the concept
float m_fDispatchTime;
EHANDLE m_hIssuer; ///< an entity, if issued by an entity
/*
DeferredResponseTarget_t m_iTargetType;
EHANDLE m_hTarget; // May be invalid.
*/
CFollowupTargetSpec_t m_Target;
inline void Init( const AIConcept_t &concept, const AI_CriteriaSet * RESTRICT contexts, float dtime, const CFollowupTargetSpec_t &target, CBaseEntity *pIssuer );
inline bool IsQuashed() { return !m_Target.IsValid(); }
void Quash(); ///< make this response invalid.
};
/// write
static void DeferContextsFromCriteriaSet( DeferredContexts_t &contextsOut, const AI_CriteriaSet *criteriaIn );
//////////////////// Methods ////////////////////
public:
CResponseQueue( int queueSize );
/// Add a deferred response.
void Add( const AIConcept_t &concept, ///< concept to dispatch
const AI_CriteriaSet * RESTRICT contexts, ///< the contexts that come with it (may be NULL)
float time, ///< when to dispatch it. You can specify a time of zero to mean "immediately."
const CFollowupTargetSpec_t &targetspec, /// All information necessary to target this response
CBaseEntity *pIssuer = NULL ///< the entity who should not respond if this is a ANY or ALL rule. (eg, don't let people talk to themselves.)
);
/// Remove all deferred responses matching the concept and issuer.
void Remove( const AIConcept_t &concept, ///< concept to dispatch
CBaseEntity * const pIssuer = NULL ///< the entity issuing the response, if one exists.
);
/// Remove all deferred responses queued to be spoken by given character
void RemoveSpeechQueuedFor( const CBaseEntity *pSpeaker );
/// Empty out all pending events
void Evacuate();
/// Go through and dispatch any deferred responses.
void PerFrameDispatch();
/// Add an expressor owner to this queue.
void AddExpresserHost(CBaseEntity *host);
/// Remove an expresser host from this queue.
void RemoveExpresserHost(CBaseEntity *host);
/// Iterate over potential expressers for this queue
inline int GetNumExpresserTargets() const;
inline CBaseEntity *GetExpresserHost(int which) const;
protected:
/// Actually send off one response to a consumer
/// Return true if dispatch succeeded
bool DispatchOneResponse( CDeferredResponse &response );
private:
/// Helper function for one case in DispatchOneResponse
/// (for better organization)
bool DispatchOneResponse_ThenANY( CDeferredResponse &response, AI_CriteriaSet * RESTRICT pDeferredCriteria, CBaseEntity * const RESTRICT pIssuer, float followupMaxDistSq );
//////////////////// Data ////////////////////
protected:
typedef CUtlFixedLinkedList< CDeferredResponse > QueueType_t;
QueueType_t m_Queue; // the queue of deferred responses, will eventually be sorted
/// Note about the queue type: if you move to replace it with a sorted priority queue,
/// make sure it is a type such that an iterator is not invalidated by inserts and deletes.
/// CResponseQueue::PerFrameDispatch() iterates over the queue calling DispatchOneResponse
/// on each in turn, and those responses may very easily add new events to the queue.
/// A crash will result if the iterator used in CResponseQueue::PerFrameDispatch()'s loop
/// becomes invalid.
CUtlVector<EHANDLE> m_ExpresserTargets; // a list of legitimate expresser targets
};
inline void CResponseQueue::CDeferredResponse::Init(const AIConcept_t &concept, const AI_CriteriaSet * RESTRICT contexts, float dtime, const CFollowupTargetSpec_t &target, CBaseEntity *pIssuer )
{
m_concept = concept;
m_fDispatchTime = dtime;
/*
m_iTargetType = targetType;
m_hTarget = handle ;
*/
m_Target = target;
m_hIssuer = pIssuer;
DeferContextsFromCriteriaSet(m_contexts, contexts);
}
int CResponseQueue::GetNumExpresserTargets() const
{
return m_ExpresserTargets.Count();
}
CBaseEntity *CResponseQueue::GetExpresserHost(int which) const
{
return m_ExpresserTargets[which];
}
// The wrapper game system that contains a response queue, and ticks it each frame.
class CResponseQueueManager : public CAutoGameSystemPerFrame
{
public:
CResponseQueueManager(char const *name) : CAutoGameSystemPerFrame( name )
{
m_pQueue = NULL;
}
virtual ~CResponseQueueManager(void);
virtual void Shutdown();
virtual void FrameUpdatePostEntityThink( void );
virtual void LevelInitPreEntity( void );
inline CResponseQueue *GetQueue(void) { Assert(m_pQueue); return m_pQueue; }
protected:
CResponseQueue *m_pQueue;
};
// Valid if the target type enum is within bounds. Furthermore if it
// specifies a specific entity, that handle must be valid.
bool CResponseQueue::CFollowupTargetSpec_t::IsValid( void ) const
{
if (m_iTargetType >= kDRT_MAX)
return false;
if (m_iTargetType < 0)
return false;
if (m_iTargetType == kDRT_SPECIFIC && !m_hHandle.IsValid())
return false;
return true;
}
extern CResponseQueueManager g_ResponseQueueManager;
// Handy global helper funcs
/// Automatically queue up speech to happen immediately -- calls straight through to response rules add
inline void QueueSpeak( const AIConcept_t &concept, ///< concept name to say
const CResponseQueue::CFollowupTargetSpec_t& targetspec, ///< kDRT_ANY, kDRT_ALL, etc
CBaseEntity *pIssuer = NULL ///< if specifying ANY or ALL, use this to specify the one you *don't* want to speak
)
{
return g_ResponseQueueManager.GetQueue()->Add( concept, NULL, 0.0f, targetspec, pIssuer );
}
/// Automatically queue up speech to happen immediately -- calls straight through to response rules add
inline void QueueSpeak( const AIConcept_t &concept, ///< concept name to say
const CResponseQueue::CFollowupTargetSpec_t& targetspec, ///< kDRT_ANY, kDRT_ALL, etc
const AI_CriteriaSet &criteria, ///< criteria to pass in
CBaseEntity *pIssuer = NULL ///< if specifying ANY or ALL, use this to specify the one you *don't* want to speak
)
{
return g_ResponseQueueManager.GetQueue()->Add( concept, &criteria, 0.0f, targetspec, pIssuer );
}
/// Automatically queue up speech to happen immediately -- calls straight through to response rules add
inline void QueueSpeak( const AIConcept_t &concept, ///< concept name to say
const EHANDLE &target, ///< which entity shall speak
float delay, ///< how far in the future to speak
const AI_CriteriaSet &criteria, ///< criteria to pass in
CBaseEntity *pIssuer = NULL )
{
return g_ResponseQueueManager.GetQueue()->Add( concept, &criteria, gpGlobals->curtime + delay,
CResponseQueue::CFollowupTargetSpec_t(target), pIssuer );
}
#endif // AI_SPEECHQUEUE_H

View File

@ -506,6 +506,11 @@ void CBaseAnimating::StudioFrameAdvanceInternal( CStudioHdr *pStudioHdr, float f
float flNewCycle = GetCycle() + flCycleDelta;
if (flNewCycle < 0.0 || flNewCycle >= 1.0)
{
if (flNewCycle >= 1.0f)
{
ReachedEndOfSequence();
}
if (m_bSequenceLoops)
{
flNewCycle -= (int)(flNewCycle);

View File

@ -84,6 +84,7 @@ public:
virtual void StudioFrameAdvance(); // advance animation frame to some time in the future
void StudioFrameAdvanceManual( float flInterval );
bool IsValidSequence( int iSequence );
virtual void ReachedEndOfSequence() { return; }
inline float GetPlaybackRate();
inline void SetPlaybackRate( float rate );

View File

@ -1725,25 +1725,6 @@ Killed
*/
void CBaseCombatCharacter::Event_Killed( const CTakeDamageInfo &info )
{
#ifdef MAPBASE_VSCRIPT
if (m_ScriptScope.IsInitialized() && g_Hook_OnDeath.CanRunInScope( m_ScriptScope ))
{
HSCRIPT hInfo = g_pScriptVM->RegisterInstance( const_cast<CTakeDamageInfo*>(&info) );
// info
ScriptVariant_t functionReturn;
ScriptVariant_t args[] = { ScriptVariant_t( hInfo ) };
if ( g_Hook_OnDeath.Call( m_ScriptScope, &functionReturn, args ) && (functionReturn.m_type == FIELD_BOOLEAN && functionReturn.m_bool == false) )
{
// Make this entity cheat death
g_pScriptVM->RemoveInstance( hInfo );
return;
}
g_pScriptVM->RemoveInstance( hInfo );
}
#endif
extern ConVar npc_vphysics;
// Advance life state to dying
@ -2893,6 +2874,12 @@ int CBaseCombatCharacter::OnTakeDamage( const CTakeDamageInfo &info )
#endif
if ( m_iHealth <= 0 )
{
#ifdef MAPBASE_VSCRIPT
// False = Cheat death
if (ScriptDeathHook( const_cast<CTakeDamageInfo*>(&info) ) == false)
return retVal;
#endif
IPhysicsObject *pPhysics = VPhysicsGetObject();
if ( pPhysics )
{

View File

@ -66,6 +66,9 @@
#include "mapbase/matchers.h"
#include "mapbase/datadesc_mod.h"
#endif
#ifdef NEW_RESPONSE_SYSTEM
#include "ai_speech.h"
#endif
#if defined( TF_DLL )
#include "tf_gamerules.h"
@ -1716,22 +1719,9 @@ int CBaseEntity::VPhysicsTakeDamage( const CTakeDamageInfo &info )
void CBaseEntity::Event_Killed( const CTakeDamageInfo &info )
{
#ifdef MAPBASE_VSCRIPT
if (m_ScriptScope.IsInitialized() && g_Hook_OnDeath.CanRunInScope( m_ScriptScope ))
{
HSCRIPT hInfo = g_pScriptVM->RegisterInstance( const_cast<CTakeDamageInfo*>(&info) );
// info
ScriptVariant_t functionReturn;
ScriptVariant_t args[] = { ScriptVariant_t( hInfo ) };
if ( g_Hook_OnDeath.Call( m_ScriptScope, &functionReturn, args ) && (functionReturn.m_type == FIELD_BOOLEAN && functionReturn.m_bool == false) )
{
// Make this entity cheat death
g_pScriptVM->RemoveInstance( hInfo );
return;
}
g_pScriptVM->RemoveInstance( hInfo );
}
// False = Cheat death
if (ScriptDeathHook( const_cast<CTakeDamageInfo*>(&info) ) == false)
return;
#endif
if( info.GetAttacker() )
@ -2311,6 +2301,8 @@ BEGIN_ENT_SCRIPTDESC_ROOT( CBaseEntity, "Root class of all server-side entities"
DEFINE_SCRIPTFUNC_NAMED( ScriptGetModelKeyValues, "GetModelKeyValues", "Get a KeyValue class instance on this entity's model")
#ifdef MAPBASE_VSCRIPT
DEFINE_SCRIPTFUNC( Activate, "" )
DEFINE_SCRIPTFUNC_NAMED( ScriptIsVisible, "IsVisible", "Check if the specified position can be visible to this entity." )
DEFINE_SCRIPTFUNC_NAMED( ScriptIsEntVisible, "IsEntVisible", "Check if the specified entity can be visible to this entity." )
DEFINE_SCRIPTFUNC_NAMED( ScriptIsVisibleWithMask, "IsVisibleWithMask", "Check if the specified position can be visible to this entity with a specific trace mask." )
@ -2408,7 +2400,10 @@ BEGIN_ENT_SCRIPTDESC_ROOT( CBaseEntity, "Root class of all server-side entities"
DEFINE_SCRIPTFUNC( SetFriction, "" )
DEFINE_SCRIPTFUNC( GetMass, "" )
DEFINE_SCRIPTFUNC( SetMass, "" )
DEFINE_SCRIPTFUNC_NAMED( ScriptGetSolid, "GetSolid", "" )
DEFINE_SCRIPTFUNC_NAMED( ScriptSetSolid, "SetSolid", "" )
DEFINE_SCRIPTFUNC( GetSolidFlags, "Get solid flags" )
DEFINE_SCRIPTFUNC( AddSolidFlags, "Add solid flags" )
DEFINE_SCRIPTFUNC( RemoveSolidFlags, "Remove solid flags" )
@ -4660,6 +4655,16 @@ bool CBaseEntity::AcceptInput( const char *szInputName, CBaseEntity *pActivator,
return false;
}
#ifdef MAPBASE_VSCRIPT
bool CBaseEntity::ScriptAcceptInput( const char *szInputName, const char *szValue, HSCRIPT hActivator, HSCRIPT hCaller )
{
variant_t value;
value.SetString( MAKE_STRING( szValue ) );
return AcceptInput( szInputName, ToEnt( hActivator ), ToEnt( hCaller ), value, 0 );
}
#endif
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
@ -4692,12 +4697,26 @@ bool CBaseEntity::ScriptInputHook( const char *szInputName, CBaseEntity *pActiva
}
#ifdef MAPBASE_VSCRIPT
bool CBaseEntity::ScriptAcceptInput( const char *szInputName, const char *szValue, HSCRIPT hActivator, HSCRIPT hCaller )
bool CBaseEntity::ScriptDeathHook( CTakeDamageInfo *info )
{
variant_t value;
value.SetString( MAKE_STRING(szValue) );
if (m_ScriptScope.IsInitialized() && g_Hook_OnDeath.CanRunInScope( m_ScriptScope ))
{
HSCRIPT hInfo = g_pScriptVM->RegisterInstance( info );
return AcceptInput( szInputName, ToEnt(hActivator), ToEnt(hCaller), value, 0 );
// info
ScriptVariant_t functionReturn;
ScriptVariant_t args[] = { ScriptVariant_t( hInfo ) };
if ( g_Hook_OnDeath.Call( m_ScriptScope, &functionReturn, args ) && (functionReturn.m_type == FIELD_BOOLEAN && functionReturn.m_bool == false) )
{
// Make this entity cheat death
g_pScriptVM->RemoveInstance( hInfo );
return false;
}
g_pScriptVM->RemoveInstance( hInfo );
}
return true;
}
#endif
@ -7657,7 +7676,11 @@ bool CBaseEntity::HasContext( const char *nameandvalue ) const
const char *p = nameandvalue;
while ( p )
{
#ifdef NEW_RESPONSE_SYSTEM
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), NULL, nameandvalue );
#else
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), NULL );
#endif
return HasContext( key, value );
}
@ -7704,7 +7727,11 @@ void CBaseEntity::RemoveContext( const char *contextName )
while ( p )
{
duration = 0.0f;
#ifdef NEW_RESPONSE_SYSTEM
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration, contextName );
#else
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration );
#endif
if ( duration )
{
duration += gpGlobals->curtime;
@ -8775,56 +8802,76 @@ void CBaseEntity::AddContext( const char *contextName )
{
char key[ 128 ];
char value[ 128 ];
float duration;
float duration = 0.0f;
const char *p = contextName;
while ( p )
{
duration = 0.0f;
#ifdef NEW_RESPONSE_SYSTEM
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration, contextName );
#else
p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration );
#endif
if ( duration )
{
duration += gpGlobals->curtime;
}
int iIndex = FindContextByName( key );
if ( iIndex != -1 )
{
// Set the existing context to the new value
m_ResponseContexts[iIndex].m_iszValue = AllocPooledString( value );
m_ResponseContexts[iIndex].m_fExpirationTime = duration;
continue;
}
ResponseContext_t newContext;
newContext.m_iszName = AllocPooledString( key );
newContext.m_iszValue = AllocPooledString( value );
newContext.m_fExpirationTime = duration;
m_ResponseContexts.AddToTail( newContext );
AddContext( key, value, duration );
}
}
#ifdef MAPBASE
void CBaseEntity::AddContext( const char *name, const char *value, float duration )
{
int iIndex = FindContextByName( name );
if ( iIndex != -1 )
{
// Set the existing context to the new value
#ifdef NEW_RESPONSE_SYSTEM
char buf[64];
if ( RR::CApplyContextOperator::FindOperator( value )->Apply(
m_ResponseContexts[iIndex].m_iszValue.ToCStr(), value, buf, sizeof(buf) ) )
{
m_ResponseContexts[iIndex].m_iszValue = AllocPooledString( buf );
}
else
{
Warning( "RR: could not apply operator %s to prior value %s\n",
value, m_ResponseContexts[iIndex].m_iszValue.ToCStr() );
m_ResponseContexts[iIndex].m_iszValue = AllocPooledString( value );
}
#else
m_ResponseContexts[iIndex].m_iszValue = AllocPooledString( value );
m_ResponseContexts[iIndex].m_fExpirationTime = duration;
return;
}
ResponseContext_t newContext;
newContext.m_iszName = AllocPooledString( name );
newContext.m_iszValue = AllocPooledString( value );
newContext.m_fExpirationTime = duration;
m_ResponseContexts.AddToTail( newContext );
}
#endif
m_ResponseContexts[iIndex].m_fExpirationTime = duration;
}
else
{
ResponseContext_t newContext;
newContext.m_iszName = AllocPooledString( name );
#ifdef NEW_RESPONSE_SYSTEM
char buf[64];
if ( RR::CApplyContextOperator::FindOperator( value )->Apply(
NULL, value, buf, sizeof(buf) ) )
{
newContext.m_iszValue = AllocPooledString( buf );
}
else
{
newContext.m_iszValue = AllocPooledString( value );
}
#else
newContext.m_iszValue = AllocPooledString( value );
#endif
newContext.m_fExpirationTime = duration;
m_ResponseContexts.AddToTail( newContext );
}
}
//-----------------------------------------------------------------------------
// Purpose:
@ -8976,6 +9023,11 @@ void CBaseEntity::InputChangeVariable( inputdata_t &inputdata )
//-----------------------------------------------------------------------------
void CBaseEntity::DispatchResponse( const char *conceptName )
{
#ifdef NEW_RESPONSE_SYSTEM
#undef IResponseSystem
using namespace ResponseRules;
#endif
IResponseSystem *rs = GetResponseSystem();
if ( !rs )
return;
@ -9006,6 +9058,61 @@ void CBaseEntity::DispatchResponse( const char *conceptName )
// Handle the response here...
char response[ 256 ];
result.GetResponse( response, sizeof( response ) );
#ifdef NEW_RESPONSE_SYSTEM
switch (result.GetType())
{
case ResponseRules::RESPONSE_SPEAK:
{
EmitSound(response);
}
break;
case ResponseRules::RESPONSE_SENTENCE:
{
int sentenceIndex = SENTENCEG_Lookup(response);
if (sentenceIndex == -1)
{
// sentence not found
break;
}
// FIXME: Get pitch from npc?
CPASAttenuationFilter filter(this);
CBaseEntity::EmitSentenceByIndex(filter, entindex(), CHAN_VOICE, sentenceIndex, 1, result.GetSoundLevel(), 0, PITCH_NORM);
}
break;
case ResponseRules::RESPONSE_SCENE:
{
// Try to fire scene w/o an actor
InstancedScriptedScene(NULL, response);
}
break;
case ResponseRules::RESPONSE_PRINT:
{
}
break;
case ResponseRules::RESPONSE_ENTITYIO:
{
CAI_Expresser::FireEntIOFromResponse(response, this);
break;
}
#ifdef MAPBASE_VSCRIPT
case ResponseRules::RESPONSE_VSCRIPT:
{
CAI_Expresser::RunScriptResponse( this, response, &set, false );
break;
}
case ResponseRules::RESPONSE_VSCRIPT_FILE:
{
CAI_Expresser::RunScriptResponse( this, response, &set, true );
break;
}
#endif
default:
// Don't know how to handle .vcds!!!
break;
}
#else
#ifdef MAPBASE
if (response[0] == '$')
{
@ -9100,6 +9207,7 @@ void CBaseEntity::DispatchResponse( const char *conceptName )
// Don't know how to handle .vcds!!!
break;
}
#endif
}
//-----------------------------------------------------------------------------
@ -9426,6 +9534,7 @@ void CBaseEntity::RemoveRecipientsIfNotCloseCaptioning( CRecipientFilter& filter
}
}
#ifndef MAPBASE // Moved to SoundEmitterSystem.cpp
//-----------------------------------------------------------------------------
// Purpose: Wrapper to emit a sentence and also a close caption token for the sentence as appropriate.
// Input : filter -
@ -9448,6 +9557,7 @@ void CBaseEntity::EmitSentenceByIndex( IRecipientFilter& filter, int iEntIndex,
enginesound->EmitSentenceByIndex( filter, iEntIndex, iChannel, iSentenceIndex,
flVolume, iSoundlevel, iFlags, iPitch, 0, pOrigin, pDirection, &dummy, bUpdatePositions, soundtime );
}
#endif
void CBaseEntity::SetRefEHandle( const CBaseHandle &handle )

View File

@ -20,6 +20,10 @@
#include "ServerNetworkProperty.h"
#include "shareddefs.h"
#include "engine/ivmodelinfo.h"
#ifdef NEW_RESPONSE_SYSTEM
#include "AI_Criteria.h"
#include "AI_ResponseSystem.h"
#endif
#include "vscript/ivscript.h"
#include "vscript_server.h"
@ -29,8 +33,10 @@ class CDmgAccumulator;
struct CSoundParameters;
#ifndef NEW_RESPONSE_SYSTEM
class AI_CriteriaSet;
class IResponseSystem;
#endif
class IEntitySaveUtils;
class CRecipientFilter;
class CStudioHdr;
@ -39,6 +45,11 @@ class CStudioHdr;
// FIXME: Could do this in the script file by making it required and bumping up weighting there instead...
#define CONCEPT_WEIGHT 5.0f
#ifdef NEW_RESPONSE_SYSTEM
// Relax the namespace standard a bit so that less code has to be changed
#define IResponseSystem ResponseRules::IResponseSystem
#endif
typedef CHandle<CBaseEntity> EHANDLE;
#define MANUALMODE_GETSET_PROP(type, accessorName, varName) \
@ -341,10 +352,10 @@ struct thinkfunc_t
#ifdef MAPBASE_VSCRIPT
struct scriptthinkfunc_t
{
int m_nNextThinkTick;
HSCRIPT m_hfnThink;
unsigned short m_iContextHash;
bool m_bNoParam;
float m_flNextThink;
HSCRIPT m_hfnThink;
unsigned m_iContextHash;
bool m_bNoParam;
};
#endif
@ -682,6 +693,9 @@ public:
#endif
bool ScriptInputHook( const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t Value, ScriptVariant_t &functionReturn );
#ifdef MAPBASE_VSCRIPT
bool ScriptDeathHook( CTakeDamageInfo *info );
#endif
//
// Input handlers.
@ -1001,10 +1015,10 @@ public:
const char *GetContextValue( const char *contextName ) const;
float GetContextExpireTime( const char *name );
void RemoveContext( const char *nameandvalue );
void AddContext( const char *name, const char *value, float duration = 0.0f );
#endif
void AddContext( const char *nameandvalue );
void AddContext( const char *name, const char *value, float duration = 0.0f );
protected:
CUtlVector< ResponseContext_t > m_ResponseContexts;
@ -1294,6 +1308,12 @@ public:
#endif
virtual void ModifyOrAppendCriteria( AI_CriteriaSet& set );
#ifdef NEW_RESPONSE_SYSTEM
// this computes criteria that depend on the other criteria having been set.
// needs to be done in a second pass because we may have multiple overrids for
// a context before it all settles out.
virtual void ModifyOrAppendDerivedCriteria( AI_CriteriaSet& set ) {};
#endif
void AppendContextToCriteria( AI_CriteriaSet& set, const char *prefix = "" );
#ifdef MAPBASE
void ReAppendContextCriteria( AI_CriteriaSet& set );
@ -1546,7 +1566,11 @@ public:
static void EmitCloseCaption( IRecipientFilter& filter, int entindex, char const *token, CUtlVector< Vector >& soundorigins, float duration, bool warnifmissing = false );
static void EmitSentenceByIndex( IRecipientFilter& filter, int iEntIndex, int iChannel, int iSentenceIndex,
float flVolume, soundlevel_t iSoundlevel, int iFlags = 0, int iPitch = PITCH_NORM,
const Vector *pOrigin = NULL, const Vector *pDirection = NULL, bool bUpdatePositions = true, float soundtime = 0.0f );
const Vector *pOrigin = NULL, const Vector *pDirection = NULL, bool bUpdatePositions = true, float soundtime = 0.0f
#ifdef MAPBASE
, int iSpecialDSP = 0, int iSpeakerIndex = 0 // Needed for env_microphone
#endif
);
static bool IsPrecacheAllowed();
static void SetAllowPrecache( bool allow );
@ -2115,6 +2139,9 @@ public:
int ScriptGetMoveType() { return GetMoveType(); }
void ScriptSetMoveType( int iMoveType ) { SetMoveType( (MoveType_t)iMoveType ); }
int ScriptGetSolid() { return GetSolid(); }
void ScriptSetSolid( int i ) { SetSolid( (SolidType_t)i ); }
bool ScriptDispatchInteraction( int interactionType, HSCRIPT data, HSCRIPT sourceEnt );
int ScriptGetTakeDamage() { return m_takedamage; }

View File

@ -799,6 +799,15 @@ bool CBaseFlex::StartSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CCh
textParams.fadeinTime = 0.5f;
textParams.fadeoutTime = 0.5f;
textParams.channel = 3;
textParams.x = -1;
textParams.y = 0.6;
textParams.effect = 0;
textParams.r1 = 255;
textParams.g1 = 255;
textParams.b1 = 255;
if ( GetGameTextSpeechParams( textParams ) )
{
CRecipientFilter filter;
@ -2091,15 +2100,6 @@ float CBaseFlex::PlayAutoGeneratedSoundScene( const char *soundname )
//-----------------------------------------------------------------------------
bool CBaseFlex::GetGameTextSpeechParams( hudtextparms_t &params )
{
params.channel = 3;
params.x = -1;
params.y = 0.6;
params.effect = 0;
params.r1 = 255;
params.g1 = 255;
params.b1 = 255;
ScriptVariant_t varTable;
if (g_pScriptVM->GetValue(m_ScriptScope, "m_GameTextSpeechParams", &varTable) && varTable.m_type == FIELD_HSCRIPT)
{

View File

@ -19,7 +19,9 @@
struct flexsettinghdr_t;
struct flexsetting_t;
#ifndef NEW_RESPONSE_SYSTEM
class AI_Response;
#endif
//-----------------------------------------------------------------------------
// Purpose: A .vfe referenced by a scene during .vcd playback

View File

@ -28,6 +28,7 @@ CBaseMultiplayerPlayer::CBaseMultiplayerPlayer()
CBaseMultiplayerPlayer::~CBaseMultiplayerPlayer()
{
m_pAchievementKV->deleteThis();
delete m_pExpresser;
}
//-----------------------------------------------------------------------------
@ -87,10 +88,19 @@ IResponseSystem *CBaseMultiplayerPlayer::GetResponseSystem()
//-----------------------------------------------------------------------------
// Purpose: Doesn't actually speak the concept. Just finds a response in the system. You then have to play it yourself.
//-----------------------------------------------------------------------------
AI_Response *CBaseMultiplayerPlayer::SpeakConcept( int iConcept )
bool CBaseMultiplayerPlayer::SpeakConcept( AI_Response &response, int iConcept )
{
m_iCurrentConcept = iConcept;
return SpeakFindResponse( g_pszMPConcepts[iConcept] );
#ifdef NEW_RESPONSE_SYSTEM
CAI_Concept concept(g_pszMPConcepts[iConcept]);
concept.SetSpeaker(this);
return FindResponse( response, concept );
#else
AI_Response *pResponse = SpeakFindResponse( g_pszMPConcepts[iConcept] );
if (pResponse)
response = *pResponse;
return pResponse != NULL;
#endif
}
//-----------------------------------------------------------------------------

View File

@ -28,7 +28,7 @@ public:
virtual bool SpeakIfAllowed( AIConcept_t concept, const char *modifiers = NULL, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL );
virtual IResponseSystem *GetResponseSystem();
AI_Response *SpeakConcept( int iConcept );
bool SpeakConcept( AI_Response &response, int iConcept );
virtual bool SpeakConceptIfAllowed( int iConcept, const char *modifiers = NULL, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL );
virtual bool CanHearAndReadChatFrom( CBasePlayer *pPlayer );

View File

@ -104,6 +104,13 @@ extern void FireTargets( const char *targetName, CBaseEntity *pActivator, CBaseE
#define MAX_OLD_ENEMIES 4 // how many old enemies to remember
#ifdef MAPBASE
// Use the model keyvalue if it is defined
#define DefaultOrCustomModel(defaultModel) GetModelName() != NULL_STRING ? STRING(GetModelName()) : defaultModel
#else
#define DefaultOrCustomModel() defaultModel
#endif
// used by suit voice to indicate damage sustained and repaired type to player
enum

View File

@ -2511,7 +2511,7 @@ class CBreakableGibShooter : public CBaseEntity
DECLARE_DATADESC();
public:
const char *GetRandomTemplateModel( CPointTemplate *pTemplate );
int GetRandomTemplateModelIndex( CPointTemplate *pTemplate );
void Precache( void );
@ -2560,19 +2560,16 @@ END_DATADESC()
LINK_ENTITY_TO_CLASS( env_break_shooter, CBreakableGibShooter );
const char *CBreakableGibShooter::GetRandomTemplateModel( CPointTemplate *pTemplate )
int CBreakableGibShooter::GetRandomTemplateModelIndex( CPointTemplate *pTemplate )
{
int iIndex = RandomInt( 0, pTemplate->GetNumTemplates() );
char *iszTemplate = (char*)(STRING(Templates_FindByIndex(pTemplate->GetTemplateIndexForTemplate(iIndex))));
CEntityMapData entData( iszTemplate );
const char *szTemplate = STRING(Templates_FindByIndex(pTemplate->GetTemplateIndexForTemplate(iIndex)));
// This might seem a little messy, but I think it's cheaper than creating the entity.
char szModel[MAPKEY_MAXLENGTH];
if (!entData.ExtractValue("model", szModel))
return NULL;
bool modelExtracted = MapEntity_ExtractValue(szTemplate, "model", szModel);
return strdup(szModel);
return modelinfo->GetModelIndex( modelExtracted ? szModel : NULL );
}
void CBreakableGibShooter::Precache( void )
@ -2604,7 +2601,7 @@ void CBreakableGibShooter::Shoot( void )
if (m_iModelType == MODELTYPE_BREAKABLECHUNKS)
iModelIndex = modelinfo->GetModelIndex( g_PropDataSystem.GetRandomChunkModel( STRING( GetModelName() ) ) );
else if (m_iModelType == MODELTYPE_TEMPLATE)
iModelIndex = modelinfo->GetModelIndex( GetRandomTemplateModel(pTemplate) );
iModelIndex = GetRandomTemplateModelIndex( pTemplate );
// All objects except the first one in this run are marked as slaves...
int slaveFlag = 0;

View File

@ -189,7 +189,7 @@ void CEnvTonemapController::InputBlendTonemapScale( inputdata_t &inputdata )
void CEnvTonemapController::InputSetBloomScaleRange( inputdata_t &inputdata )
{
float bloom_max=1, bloom_min=1;
int nargs=sscanf("%f %f",inputdata.value.String(), bloom_max, bloom_min );
int nargs=sscanf( inputdata.value.String(), "%f %f", &bloom_max, &bloom_min );
if (nargs != 2)
{
Warning("%s (%s) received SetBloomScaleRange input without 2 arguments. Syntax: <max bloom> <min bloom>\n", GetClassname(), GetDebugName() );
@ -197,6 +197,9 @@ void CEnvTonemapController::InputSetBloomScaleRange( inputdata_t &inputdata )
}
m_flCustomBloomScale=bloom_max;
m_flCustomBloomScaleMinimum=bloom_min;
#ifdef MAPBASE
m_bUseCustomBloomScale = true;
#endif
}
//-----------------------------------------------------------------------------

View File

@ -19,6 +19,9 @@
#include "soundflags.h"
#include "engine/IEngineSound.h"
#include "filters.h"
#ifdef MAPBASE
#include "fmtstr.h"
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
@ -27,6 +30,10 @@
const float MICROPHONE_SETTLE_EPSILON = 0.005;
#ifdef MAPBASE
static ConVar sv_microphones_always_pickup_sentences( "sv_microphones_always_pickup_sentences", "0", FCVAR_NONE, "Allows env_microphones to always detect and play back sentences, regardless of their keyvalues." );
#endif
// List of env_microphones who want to be told whenever a sound is started
static CUtlVector< CHandle<CEnvMicrophone> > s_Microphones;
@ -236,7 +243,11 @@ void CEnvMicrophone::InputDisable( inputdata_t &inputdata )
m_bDisabled = true;
if ( m_hSpeaker )
{
#ifdef MAPBASE
CBaseEntity::StopSound( m_hSpeaker->entindex(), m_nChannel, m_szLastSound );
#else
CBaseEntity::StopSound( m_hSpeaker->entindex(), CHAN_STATIC, m_szLastSound );
#endif
m_szLastSound[0] = 0;
// Remove ourselves from the list of active mics
@ -554,31 +565,41 @@ MicrophoneResult_t CEnvMicrophone::SoundPlayed( int entindex, const char *soundn
CPASAttenuationFilter filter( m_hSpeaker );
EmitSound_t ep;
#ifdef MAPBASE
ep.m_nChannel = m_nChannel;
if (m_flVolumeScale != 1.0f)
ep.m_flVolume = (flVolume * m_flVolumeScale);
#else
ep.m_nChannel = CHAN_STATIC;
ep.m_flVolume = flVolume;
#endif
ep.m_pSoundName = soundname;
ep.m_SoundLevel = soundlevel;
ep.m_nFlags = iFlags;
#ifdef MAPBASE
if (m_flPitchScale != 1.0f)
ep.m_nPitch = (int)((float)iPitch * m_flPitchScale);
else
ep.m_nPitch = iPitch;
ep.m_pOrigin = &vecOrigin;
#else
ep.m_nPitch = iPitch;
ep.m_pOrigin = &m_hSpeaker->GetAbsOrigin();
#endif
ep.m_flSoundTime = soundtime;
ep.m_nSpeakerEntity = entindex;
CBaseEntity::EmitSound( filter, m_hSpeaker->entindex(), ep );
#ifdef MAPBASE
if (m_bHearingSentence)
{
CBaseEntity::EmitSentenceByIndex( filter, m_hSpeaker->entindex(), m_nChannel, atoi(soundname), flVolume, soundlevel, 0, iPitch, &vecOrigin, NULL, true, soundtime,
m_iSpeakerDSPPreset, entindex );
}
else
#endif
{
#ifdef MAPBASE
ep.m_nChannel = m_nChannel;
if (m_flVolumeScale != 1.0f)
ep.m_flVolume = (flVolume * m_flVolumeScale);
else
ep.m_flVolume = flVolume;
if (m_flPitchScale != 1.0f)
ep.m_nPitch = (int)((float)iPitch * m_flPitchScale);
else
ep.m_nPitch = iPitch;
ep.m_pOrigin = &vecOrigin;
#else
ep.m_nChannel = CHAN_STATIC;
ep.m_flVolume = flVolume;
ep.m_nPitch = iPitch;
ep.m_pOrigin = &m_hSpeaker->GetAbsOrigin();
#endif
ep.m_pSoundName = soundname;
ep.m_SoundLevel = soundlevel;
ep.m_nFlags = iFlags;
ep.m_flSoundTime = soundtime;
ep.m_nSpeakerEntity = entindex;
CBaseEntity::EmitSound( filter, m_hSpeaker->entindex(), ep );
}
Q_strncpy( m_szLastSound, soundname, sizeof(m_szLastSound) );
m_OnRoutedSound.FireOutput( this, this, 0 );
@ -646,3 +667,56 @@ bool CEnvMicrophone::OnSoundPlayed( int entindex, const char *soundname, soundle
return bSwallowed;
}
#ifdef MAPBASE
//-----------------------------------------------------------------------------
// Purpose: Called by the sound system whenever a sentence is played so that
// active microphones can have a chance to pick up the sound.
// Output : Returns whether or not the sentence was swallowed by the microphone.
// Swallowed sentences should not be played by the sound system.
//-----------------------------------------------------------------------------
bool CEnvMicrophone::OnSentencePlayed( int entindex, int sentenceIndex, soundlevel_t soundlevel, float flVolume, int iFlags, int iPitch, const Vector *pOrigin, float soundtime, CUtlVector< Vector >& soundorigins )
{
bool bSwallowed = false;
// Loop through all registered microphones and tell them the sound was just played
int iCount = s_Microphones.Count();
if ( iCount > 0 )
{
CNumStr szSentenceStr( sentenceIndex );
// Iterate backwards because we might be deleting microphones.
for ( int i = iCount - 1; i >= 0; i-- )
{
if ( s_Microphones[i] && (s_Microphones[i]->ShouldHearSentences() || sv_microphones_always_pickup_sentences.GetBool()) )
{
// HACKHACK: Don't want to duplicate all of the code, so just use the same function with a new member variable
s_Microphones[i]->ToggleHearingSentence( true );
MicrophoneResult_t eResult = s_Microphones[i]->SoundPlayed(
entindex,
szSentenceStr,
soundlevel,
flVolume,
iFlags,
iPitch,
pOrigin,
soundtime,
soundorigins );
s_Microphones[i]->ToggleHearingSentence( false );
if ( eResult == MicrophoneResult_Swallow )
{
// Microphone told us to swallow it
bSwallowed = true;
}
else if ( eResult == MicrophoneResult_Remove )
{
s_Microphones.FastRemove( i );
}
}
}
}
return bSwallowed;
}
#endif

View File

@ -20,6 +20,9 @@ const int SF_MICROPHONE_SOUND_BULLET_IMPACT = 0x08;
const int SF_MICROPHONE_SWALLOW_ROUTED_SOUNDS = 0x10;
const int SF_MICROPHONE_SOUND_EXPLOSION = 0x20;
const int SF_MICROPHONE_IGNORE_NONATTENUATED = 0x40;
#ifdef MAPBASE
const int SF_MICROPHONE_SOUND_SENTENCE = 0x80;
#endif
// Return codes from SoundPlayed
@ -50,6 +53,10 @@ public:
void SetSensitivity( float flSensitivity );
void SetSpeakerName( string_t iszSpeakerName );
#ifdef MAPBASE
bool ShouldHearSentences() const { return HasSpawnFlags( SF_MICROPHONE_SOUND_SENTENCE ); }
inline void ToggleHearingSentence( bool bToggle ) { m_bHearingSentence = bToggle; }
#endif
void InputEnable( inputdata_t &inputdata );
void InputDisable( inputdata_t &inputdata );
@ -67,6 +74,12 @@ public:
static bool OnSoundPlayed( int entindex, const char *soundname, soundlevel_t soundlevel,
float flVolume, int iFlags, int iPitch, const Vector *pOrigin, float soundtime, CUtlVector< Vector >& soundorigins );
#ifdef MAPBASE
// Same as above, except for sentences.
static bool OnSentencePlayed( int entindex, int sentenceIndex, soundlevel_t soundlevel,
float flVolume, int iFlags, int iPitch, const Vector *pOrigin, float soundtime, CUtlVector< Vector >& soundorigins );
#endif
private:
// Per-microphone notification that a sound has played.
@ -91,6 +104,8 @@ private:
float m_flPitchScale = 1.0f;
float m_flVolumeScale = 1.0f;
int m_nChannel = CHAN_STATIC;
bool m_bHearingSentence; // HACKHACK: Allows SoundPlayed() to know when to play a sentence instead
#endif
COutputFloat m_SoundLevel; // Fired when the sampled volume level changes.

View File

@ -37,6 +37,11 @@ public:
Class_T Classify ( void );
void HandleAnimEvent( animevent_t *pEvent );
int GetSoundInterests ( void );
#ifdef MAPBASE
// Use Magnusson's default subtitle color (209,178,178)
bool GetGameTextSpeechParams( hudtextparms_t &params ) { params.r1 = 209; params.g1 = 178; params.b1 = 178; return BaseClass::GetGameTextSpeechParams( params ); }
#endif
};
LINK_ENTITY_TO_CLASS( npc_magnusson, CNPC_Magnusson );

View File

@ -277,7 +277,8 @@ bool CGenericActorCustom::KeyValue( const char *szKeyName, const char *szValue )
//-----------------------------------------------------------------------------
void CGenericActorCustom::SpeakIfAllowed( const char *concept, AI_CriteriaSet *modifiers )
{
Speak( concept, modifiers ? *modifiers : AI_CriteriaSet() );
AI_CriteriaSet empty;
Speak( concept, modifiers ? *modifiers : empty );
}
//-----------------------------------------------------------------------------

View File

@ -1755,7 +1755,11 @@ void CAI_ActBusyBehavior::PlaySoundForActBusy( busyanimparts_t AnimPart )
CAI_Expresser *pExpresser = GetOuter()->GetExpresser();
if ( pExpresser )
{
#ifdef NEW_RESPONSE_SYSTEM
CAI_Concept concept = STRING(pBusyAnim->iszSounds[AnimPart]);
#else
const char *concept = STRING(pBusyAnim->iszSounds[AnimPart]);
#endif
// Must be able to speak the concept
if ( !pExpresser->IsSpeaking() && pExpresser->CanSpeakConcept( concept ) )
@ -2738,8 +2742,15 @@ void CAI_ActBusyGoal::InputForceThisNPCToStopBusy( inputdata_t &inputdata )
if ( !pBehavior )
return;
// Just stop busying
pBehavior->StopBusying();
if (!IsActive() && pBehavior->GetActBusyGoal() == this)
{
pBehavior->Disable();
}
else
{
// Just stop busying
pBehavior->StopBusying();
}
}
#endif

View File

@ -153,6 +153,10 @@ public:
bool IsInSafeZone( CBaseEntity *pEntity );
int CountEnemiesInSafeZone();
#ifdef MAPBASE
CAI_ActBusyGoal *GetActBusyGoal() const { return m_hActBusyGoal; }
#endif
private:
virtual int SelectSchedule( void );
int SelectScheduleForLeaving( void );

View File

@ -143,7 +143,12 @@ void CAI_PolicingBehavior::HostSpeakSentence( const char *pSentence, SentencePri
}
else if ( GetOuter()->GetExpresser() )
{
#ifdef NEW_RESPONSE_SYSTEM
CAI_Concept concept = pSentence;
GetOuter()->GetExpresser()->Speak( concept );
#else
GetOuter()->GetExpresser()->Speak( pSentence );
#endif
}
#endif
}
@ -168,7 +173,12 @@ void CAI_PolicingBehavior::HostSpeakSentence( const char *pSentence, const char
}
else if ( GetOuter()->GetExpresser() )
{
#ifdef NEW_RESPONSE_SYSTEM
CAI_Concept concept( pSentence );
GetOuter()->GetExpresser()->Speak( concept, modifiers );
#else
GetOuter()->GetExpresser()->Speak( pSentence, modifiers );
#endif
}
#endif
}

View File

@ -243,6 +243,65 @@ void CSpeaker::DispatchResponse( const char *conceptName )
PrecacheScriptSound( response );
}
#ifdef NEW_RESPONSE_SYSTEM
switch (result.GetType())
{
case ResponseRules::RESPONSE_SPEAK:
{
pTarget->EmitSound( response );
}
break;
case ResponseRules::RESPONSE_SENTENCE:
{
int sentenceIndex = SENTENCEG_Lookup( response );
if (sentenceIndex == -1)
{
// sentence not found
break;
}
// FIXME: Get pitch from npc?
CPASAttenuationFilter filter( pTarget );
CBaseEntity::EmitSentenceByIndex( filter, pTarget->entindex(), CHAN_VOICE, sentenceIndex, 1, result.GetSoundLevel(), 0, PITCH_NORM );
}
break;
case ResponseRules::RESPONSE_SCENE:
{
CBaseFlex *pFlex = NULL;
if (pTarget != this)
{
// Attempt to get flex on the target
pFlex = dynamic_cast<CBaseFlex*>(pTarget);
}
InstancedScriptedScene(pFlex, response);
}
break;
case ResponseRules::RESPONSE_PRINT:
{
}
break;
case ResponseRules::RESPONSE_ENTITYIO:
{
CAI_Expresser::FireEntIOFromResponse( response, pTarget );
break;
}
#ifdef MAPBASE_VSCRIPT
case ResponseRules::RESPONSE_VSCRIPT:
{
CAI_Expresser::RunScriptResponse( pTarget, response, &set, false );
break;
}
case ResponseRules::RESPONSE_VSCRIPT_FILE:
{
CAI_Expresser::RunScriptResponse( pTarget, response, &set, true );
break;
}
#endif
default:
break;
}
#else
switch ( result.GetType() )
{
case RESPONSE_SPEAK:
@ -283,6 +342,7 @@ void CSpeaker::DispatchResponse( const char *conceptName )
default:
break;
}
#endif
// AllocPooledString?
m_OnSpeak.Set(MAKE_STRING(response), pTarget, this);

View File

@ -4734,7 +4734,8 @@ bool CLogicPlayerProxy::AcceptInput( const char *szInputName, CBaseEntity *pActi
{
DevMsg("logic_playerproxy: Player not found!\n");
g_EventQueue.AddEvent("!player", szInputName, Value, 0.01f, pActivator, pCaller);
// Need to allocate the string here in case szInputName is freed before the input fires
g_EventQueue.AddEvent("!player", STRING( AllocPooledString(szInputName) ), Value, 0.01f, pActivator, pCaller);
}
}

View File

@ -303,7 +303,7 @@ public:
virtual bool IsHoldingEntity( CBaseEntity *pEnt );
virtual void ForceDropOfCarriedPhysObjects( CBaseEntity *pOnlyIfHoldindThis );
virtual float GetHeldObjectMass( IPhysicsObject *pHeldObject );
virtual CBaseEntity *CHL2_Player::GetHeldObject( void );
virtual CBaseEntity *GetHeldObject( void );
virtual bool IsFollowingPhysics( void ) { return (m_afPhysicsFlags & PFLAG_ONBARNACLE) > 0; }
void InputForceDropPhysObjects( inputdata_t &data );

View File

@ -23,12 +23,12 @@ public:
void Spawn( void )
{
Precache( );
SetModel( "models/items/battery.mdl" );
SetModel( DefaultOrCustomModel( "models/items/battery.mdl" ) );
BaseClass::Spawn( );
}
void Precache( void )
{
PrecacheModel ("models/items/battery.mdl");
PrecacheModel( DefaultOrCustomModel( "models/items/battery.mdl" ) );
PrecacheScriptSound( "ItemBattery.Touch" );
@ -36,10 +36,30 @@ public:
bool MyTouch( CBasePlayer *pPlayer )
{
CHL2_Player *pHL2Player = dynamic_cast<CHL2_Player *>( pPlayer );
#ifdef MAPBASE
return ( pHL2Player && pHL2Player->ApplyBattery( m_flPowerMultiplier ) );
#else
return ( pHL2Player && pHL2Player->ApplyBattery() );
#endif
}
#ifdef MAPBASE
void InputSetPowerMultiplier( inputdata_t &inputdata ) { m_flPowerMultiplier = inputdata.value.Float(); }
float m_flPowerMultiplier = 1.0f;
DECLARE_DATADESC();
#endif
};
LINK_ENTITY_TO_CLASS(item_battery, CItemBattery);
PRECACHE_REGISTER(item_battery);
#ifdef MAPBASE
BEGIN_DATADESC( CItemBattery )
DEFINE_KEYFIELD( m_flPowerMultiplier, FIELD_FLOAT, "PowerMultiplier" ),
DEFINE_INPUTFUNC( FIELD_FLOAT, "SetPowerMultiplier", InputSetPowerMultiplier ),
END_DATADESC()
#endif

View File

@ -30,11 +30,29 @@ public:
void Spawn( void );
void Precache( void );
bool MyTouch( CBasePlayer *pPlayer );
#ifdef MAPBASE
float GetItemAmount() { return sk_healthkit.GetFloat() * m_flHealthMultiplier; }
void InputSetHealthMultiplier( inputdata_t &inputdata ) { m_flHealthMultiplier = inputdata.value.Float(); }
float m_flHealthMultiplier = 1.0f;
DECLARE_DATADESC();
#endif
};
LINK_ENTITY_TO_CLASS( item_healthkit, CHealthKit );
PRECACHE_REGISTER(item_healthkit);
#ifdef MAPBASE
BEGIN_DATADESC( CHealthKit )
DEFINE_KEYFIELD( m_flHealthMultiplier, FIELD_FLOAT, "HealthMultiplier" ),
DEFINE_INPUTFUNC( FIELD_FLOAT, "SetHealthMultiplier", InputSetHealthMultiplier ),
END_DATADESC()
#endif
//-----------------------------------------------------------------------------
// Purpose:
@ -66,7 +84,11 @@ void CHealthKit::Precache( void )
//-----------------------------------------------------------------------------
bool CHealthKit::MyTouch( CBasePlayer *pPlayer )
{
#ifdef MAPBASE
if ( pPlayer->TakeHealth( GetItemAmount(), DMG_GENERIC ) )
#else
if ( pPlayer->TakeHealth( sk_healthkit.GetFloat(), DMG_GENERIC ) )
#endif
{
CSingleUserRecipientFilter user( pPlayer );
user.MakeReliable();
@ -119,7 +141,11 @@ public:
bool MyTouch( CBasePlayer *pPlayer )
{
#ifdef MAPBASE
if ( pPlayer->TakeHealth( GetItemAmount(), DMG_GENERIC ) )
#else
if ( pPlayer->TakeHealth( sk_healthvial.GetFloat(), DMG_GENERIC ) )
#endif
{
CSingleUserRecipientFilter user( pPlayer );
user.MakeReliable();
@ -145,11 +171,132 @@ public:
return false;
}
#ifdef MAPBASE
float GetItemAmount() { return sk_healthvial.GetFloat() * m_flHealthMultiplier; }
void InputSetHealthMultiplier( inputdata_t &inputdata ) { m_flHealthMultiplier = inputdata.value.Float(); }
float m_flHealthMultiplier = 1.0f;
DECLARE_DATADESC();
#endif
};
LINK_ENTITY_TO_CLASS( item_healthvial, CHealthVial );
PRECACHE_REGISTER( item_healthvial );
#ifdef MAPBASE
BEGIN_DATADESC( CHealthVial )
DEFINE_KEYFIELD( m_flHealthMultiplier, FIELD_FLOAT, "HealthMultiplier" ),
DEFINE_INPUTFUNC( FIELD_FLOAT, "SetHealthMultiplier", InputSetHealthMultiplier ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Small health kit. Heals the player when picked up.
//-----------------------------------------------------------------------------
class CHealthKitCustom : public CItem
{
public:
DECLARE_CLASS( CHealthKitCustom, CItem );
CHealthKitCustom();
void Spawn( void );
void Precache( void );
bool MyTouch( CBasePlayer *pPlayer );
float GetItemAmount() { return m_flHealthAmount; }
void InputSetHealthAmount( inputdata_t &inputdata ) { m_flHealthAmount = inputdata.value.Float(); }
float m_flHealthAmount;
string_t m_iszTouchSound;
DECLARE_DATADESC();
};
LINK_ENTITY_TO_CLASS( item_healthkit_custom, CHealthKitCustom );
//PRECACHE_REGISTER(item_healthkit_custom);
#ifdef MAPBASE
BEGIN_DATADESC( CHealthKitCustom )
DEFINE_KEYFIELD( m_flHealthAmount, FIELD_FLOAT, "HealthAmount" ),
DEFINE_KEYFIELD( m_iszTouchSound, FIELD_STRING, "TouchSound" ),
DEFINE_INPUTFUNC( FIELD_FLOAT, "SetHealthAmount", InputSetHealthAmount ),
END_DATADESC()
#endif
CHealthKitCustom::CHealthKitCustom()
{
SetModelName( AllocPooledString( "models/items/healthkit.mdl" ) );
m_flHealthAmount = sk_healthkit.GetFloat();
m_iszTouchSound = AllocPooledString( "HealthKit.Touch" );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CHealthKitCustom::Spawn( void )
{
Precache();
SetModel( STRING( GetModelName() ) );
BaseClass::Spawn();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CHealthKitCustom::Precache( void )
{
PrecacheModel( STRING( GetModelName() ) );
PrecacheScriptSound( STRING( m_iszTouchSound ) );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pPlayer -
// Output :
//-----------------------------------------------------------------------------
bool CHealthKitCustom::MyTouch( CBasePlayer *pPlayer )
{
if ( pPlayer->TakeHealth( GetItemAmount(), DMG_GENERIC ) )
{
CSingleUserRecipientFilter user( pPlayer );
user.MakeReliable();
UserMessageBegin( user, "ItemPickup" );
WRITE_STRING( GetClassname() );
MessageEnd();
CPASAttenuationFilter filter( pPlayer, STRING( m_iszTouchSound ) );
EmitSound( filter, pPlayer->entindex(), STRING( m_iszTouchSound ) );
if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_YES )
{
Respawn();
}
else
{
UTIL_Remove(this);
}
return true;
}
return false;
}
#endif
//-----------------------------------------------------------------------------
// Wall mounted health kit. Heals the player when used.
//-----------------------------------------------------------------------------

View File

@ -1210,6 +1210,10 @@ void CNPC_BaseZombie::DieChopped( const CTakeDamageInfo &info )
if( pAnimating )
{
pAnimating->SetBodygroup( ZOMBIE_BODYGROUP_HEADCRAB, !m_fIsHeadless );
#ifdef MAPBASE
// Inherit some animating properties
pAnimating->m_nSkin = m_nSkin;
#endif
}
#ifdef MAPBASE

View File

@ -36,6 +36,9 @@ public:
// Now that all allies can holster/unholster, this is a precaution in case it breaks anything.
// Try OnFoundEnemy > UnholsterWeapon if you want Alyx to automatically unholster in non-episodic HL2 maps.
bool CanUnholsterWeapon() { return false; }
// Use Alyx's default subtitle color (255,212,255)
bool GetGameTextSpeechParams( hudtextparms_t &params ) { params.r1 = 255; params.g1 = 212; params.b1 = 255; return BaseClass::GetGameTextSpeechParams( params ); }
#endif
EHANDLE m_hEmpTool;

View File

@ -57,6 +57,9 @@ public:
#ifdef MAPBASE
// This skips CAI_PlayerAlly's CanFlinch() function since Episodic Alyx can flinch to begin with.
virtual bool CanFlinch( void ) { return CAI_BaseActor::CanFlinch(); }
// Use Alyx's default subtitle color (255,212,255)
bool GetGameTextSpeechParams( hudtextparms_t &params ) { params.r1 = 255; params.g1 = 212; params.b1 = 255; return BaseClass::GetGameTextSpeechParams( params ); }
#endif
virtual float GetJumpGravity() const { return 1.8f; }

View File

@ -81,6 +81,11 @@ public:
void GatherConditions();
void UseFunc( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
#ifdef MAPBASE
// Use Barney's default subtitle color (215,255,255)
bool GetGameTextSpeechParams( hudtextparms_t &params ) { params.r1 = 215; params.g1 = 255; params.b1 = 255; return BaseClass::GetGameTextSpeechParams( params ); }
#endif
CAI_FuncTankBehavior m_FuncTankBehavior;
COutputEvent m_OnPlayerUse;

View File

@ -34,6 +34,11 @@ public:
void HandleAnimEvent( animevent_t *pEvent );
int GetSoundInterests ( void );
bool UseSemaphore( void );
#ifdef MAPBASE
// Use Breen's default subtitle color (188,188,188)
bool GetGameTextSpeechParams( hudtextparms_t &params ) { params.r1 = 188; params.g1 = 188; params.b1 = 188; return BaseClass::GetGameTextSpeechParams( params ); }
#endif
};
LINK_ENTITY_TO_CLASS( npc_breen, CNPC_Breen );

View File

@ -1557,6 +1557,13 @@ Activity CNPC_Combine::NPC_BackupActivity( Activity eNewActivity )
else if (eNewActivity == ACT_RUN)
return ACT_RUN_RIFLE;
// Some models might not contain ACT_COMBINE_BUGBAIT, which the soldier model uses instead of ACT_IDLE_ON_FIRE.
// Contrariwise, soldiers may be called to use ACT_IDLE_ON_FIRE in other parts of the AI and need to translate to ACT_COMBINE_BUGBAIT.
if (eNewActivity == ACT_COMBINE_BUGBAIT)
return ACT_IDLE_ON_FIRE;
else if (eNewActivity == ACT_IDLE_ON_FIRE)
return ACT_COMBINE_BUGBAIT;
return BaseClass::NPC_BackupActivity( eNewActivity );
}
#endif
@ -3004,7 +3011,11 @@ bool CNPC_Combine::SpeakIfAllowed( const char *concept, const char *modifiers, S
AI_CriteriaSet set;
if (modifiers)
{
#ifdef NEW_RESPONSE_SYSTEM
GatherCriteria( &set, concept, modifiers );
#else
GetExpresser()->MergeModifiers(set, modifiers);
#endif
}
return SpeakIfAllowed( concept, set, sentencepriority, sentencecriteria );
}

View File

@ -689,9 +689,15 @@ int CCombineDropshipContainer::OnTakeDamage( const CTakeDamageInfo &info )
if ( m_iHealth <= 0 )
{
m_iHealth = 0;
Event_Killed( dmgInfo );
return 0;
#ifdef MAPBASE_VSCRIPT
// False = Cheat death
if (ScriptDeathHook( const_cast<CTakeDamageInfo*>(&info) ) != false)
#endif
{
m_iHealth = 0;
Event_Killed( dmgInfo );
return 0;
}
}
// Spawn damage effects

View File

@ -37,6 +37,11 @@ public:
int GetSoundInterests( void );
void SetupWithoutParent( void );
void PrescheduleThink( void );
#ifdef MAPBASE
// Use Eli's default subtitle color (255,208,172)
bool GetGameTextSpeechParams( hudtextparms_t &params ) { params.r1 = 255; params.g1 = 208; params.b1 = 172; return BaseClass::GetGameTextSpeechParams( params ); }
#endif
};
LINK_ENTITY_TO_CLASS( npc_eli, CNPC_Eli );

View File

@ -35,6 +35,11 @@ public:
Class_T Classify ( void );
void HandleAnimEvent( animevent_t *pEvent );
int GetSoundInterests ( void );
#ifdef MAPBASE
// Use Kleiner's default subtitle color (255,255,200)
bool GetGameTextSpeechParams( hudtextparms_t &params ) { params.r1 = 255; params.g1 = 255; params.b1 = 200; return BaseClass::GetGameTextSpeechParams( params ); }
#endif
};
LINK_ENTITY_TO_CLASS( npc_kleiner, CNPC_Kleiner );

View File

@ -1156,7 +1156,11 @@ bool CNPC_MetroPolice::SpeakIfAllowed( const char *concept, const char *modifier
AI_CriteriaSet set;
if (modifiers)
{
#ifdef NEW_RESPONSE_SYSTEM
GatherCriteria( &set, concept, modifiers );
#else
GetExpresser()->MergeModifiers(set, modifiers);
#endif
}
return SpeakIfAllowed( concept, set, sentencepriority, sentencecriteria );
}
@ -1622,6 +1626,11 @@ bool CNPC_MetroPolice::ShouldAttemptToStitch()
//-----------------------------------------------------------------------------
Vector CNPC_MetroPolice::StitchAimTarget( const Vector &posSrc, bool bNoisy )
{
#ifdef MAPBASE
if ( !GetEnemy() )
return vec3_origin;
#endif
// This will make us aim a stitch at the feet of the player so we can see it
if ( !GetEnemy()->IsPlayer() )
return GetShootTarget()->BodyTarget( posSrc, bNoisy );

View File

@ -41,6 +41,11 @@ public:
bool CreateBehaviors( void );
int SelectSchedule( void );
#ifdef MAPBASE
// Use Mossman's default subtitle color (220,255,198)
bool GetGameTextSpeechParams( hudtextparms_t &params ) { params.r1 = 220; params.g1 = 255; params.b1 = 198; return BaseClass::GetGameTextSpeechParams( params ); }
#endif
private:
CAI_FollowBehavior m_FollowBehavior;
};

View File

@ -4223,17 +4223,10 @@ void CNPC_PlayerCompanion::Event_KilledOther( CBaseEntity *pVictim, const CTakeD
}
//-----------------------------------------------------------------------------
// Purpose: Handles stuff ported from Alyx.
//
// For some reason, I thought Alyx's mobbed AI was used to measure enemy count for criteria stuff, which I wanted citizens to use.
// Now that I realize enemy counting for criteria is elsewhere and this is used for just mobbing in general, I deactivated it
// since it would barely be used and I don't know what kind of an impact it has on performance.
//
// If you want to use it, feel free to re-activate.
// Purpose: Handles custom combat speech stuff ported from Alyx.
//-----------------------------------------------------------------------------
void CNPC_PlayerCompanion::DoCustomCombatAI( void )
{
/*
#define COMPANION_MIN_MOB_DIST_SQR Square(120) // Any enemy closer than this adds to the 'mob'
#define COMPANION_MIN_CONSIDER_DIST Square(1200) // Only enemies within this range are counted and considered to generate AI speech
@ -4296,7 +4289,6 @@ void CNPC_PlayerCompanion::DoCustomCombatAI( void )
{
SpeakIfAllowed( TLK_MANY_ENEMIES );
}
*/
}
#endif

View File

@ -138,6 +138,11 @@ public:
// used so a grub can notify me that I stepped on it. Says a line.
void OnSquishedGrub( const CBaseEntity *pGrub );
#ifdef MAPBASE
// Use the vortigaunts' default subtitle color (188,241,174)
bool GetGameTextSpeechParams( hudtextparms_t &params ) { params.r1 = 188; params.g1 = 241; params.b1 = 174; return BaseClass::GetGameTextSpeechParams( params ); }
#endif
private:
int NumAntlionsInRadius( float flRadius );

View File

@ -1210,16 +1210,28 @@ const char *CZombieCustom::GetMoanSound( int nSound )
// We could probably do this through the response system alone now, but whatever.
modifiers.AppendCriteria( "moansound", UTIL_VarArgs("%i", nSound & 4) );
#ifdef NEW_RESPONSE_SYSTEM
AI_Response response;
CAI_Concept concept = "TLK_ZOMBIE_MOAN";
concept.SetSpeaker( this );
if (!FindResponse( response, concept, &modifiers ))
return "NPC_BaseZombie.Moan1";
#else
AI_Response *response = SpeakFindResponse(TLK_ZOMBIE_MOAN, modifiers);
if ( !response )
return "NPC_BaseZombie.Moan1";
#endif
// Must be static so it could be returned
static char szSound[128];
#ifdef NEW_RESPONSE_SYSTEM
response.GetName(szSound, sizeof(szSound));
#else
response->GetName(szSound, sizeof(szSound));
delete response;
#endif
return szSound;
}
@ -1251,7 +1263,8 @@ void CZombieCustom::AttackSound( void )
//-----------------------------------------------------------------------------
void CZombieCustom::SpeakIfAllowed(const char *concept, AI_CriteriaSet *modifiers)
{
Speak( concept, modifiers ? *modifiers : AI_CriteriaSet() );
AI_CriteriaSet empty;
Speak( concept, modifiers ? *modifiers : empty );
}
//-----------------------------------------------------------------------------

View File

@ -561,9 +561,15 @@ int CPropAPC::OnTakeDamage( const CTakeDamageInfo &info )
m_iHealth -= dmgInfo.GetDamage();
if ( m_iHealth <= 0 )
{
m_iHealth = 0;
Event_Killed( dmgInfo );
return 0;
#ifdef MAPBASE_VSCRIPT
// False = Cheat death
if (ScriptDeathHook( const_cast<CTakeDamageInfo*>(&info) ) != false)
#endif
{
m_iHealth = 0;
Event_Killed( dmgInfo );
return 0;
}
}
// Chain

View File

@ -90,9 +90,12 @@ public:
#ifdef MAPBASE
// This is in CBaseEntity, but I can't find a use for it anywhere.
// Must not have been fully implemented. Please remove this if it turns out to be something important.
// It may have been originally intended for TF2 or some other game-specific item class. Please remove this if it turns out to be something important.
virtual bool IsCombatItem() { return true; }
// Used to access item_healthkit values, etc. from outside of the class
virtual float GetItemAmount() { return 1.0f; }
void InputEnablePlayerPickup( inputdata_t &inputdata );
void InputDisablePlayerPickup( inputdata_t &inputdata );
void InputEnableNPCPickup( inputdata_t &inputdata );

View File

@ -18,13 +18,8 @@
// Valve uses global pooled strings in various parts of the code, particularly Episodic code,
// so they could get away with integer/pointer comparisons instead of string comparisons.
//
// While developing Mapbase, I thought of what it would be like if that system was more uniform and more widely used.
// My OCD ended up taking me over and that thought became real, even though on today's machines these are (for the most part) micro-optimizations
// that just clutter the code.
//
// It was fun and satisfying to do this and I hope you are not judging me for my strange ways.
// I wanted to toggle them via a preprocessor so you could turn them off at will, but that just made things messier.
// I hope this doesn't cause problems for anyone.
// This system was developed early in Mapbase's development as an attempt to make this technique more widely used.
// For the most part, this mainly just serves to apply micro-optimize parts of the code.
//
// -------------------------------------------------------------
@ -79,7 +74,6 @@ inline bool EntIsClass( CBaseEntity *ent, string_t str2 )
// Since classnames are pooled, the global string and the entity's classname should point to the same string in memory.
// As long as this rule is preserved, we only need a pointer comparison. A string comparison isn't necessary.
// Feel free to correct me if I'm disastrously wrong.
return ent->m_iClassname == str2;
}

View File

@ -672,6 +672,33 @@ int CAI_Monitor::TranslateScheduleString(const char *schedName)
return 0;
}
template<typename Translator>
static void SetForEachDelimited( CAI_Monitor &monitor, const char *szValue, const char *delimiters, void (CAI_Monitor::*setter)(int), Translator translator)
{
char *value = strdup(szValue);
char *token = strtok(value, ":");
while (token)
{
(monitor.*setter)(translator(token));
token = strtok(NULL, ":");
}
free(value);
}
template<int (CAI_Monitor::*translator)(const char*)>
struct CAI_MonitorTranslator
{
CAI_Monitor &monitor;
CAI_MonitorTranslator(CAI_Monitor &monitor) : monitor(monitor) {}
int operator()(const char *value)
{
return (monitor.*translator)(value);
}
};
//-----------------------------------------------------------------------------
// Purpose: Cache user entity field values until spawn is called.
// Input : szKeyName - Key to handle.
@ -688,13 +715,7 @@ bool CAI_Monitor::KeyValue( const char *szKeyName, const char *szValue )
}
else if (FStrEq(szKeyName, "Conditions"))
{
char *token = strtok(strdup(szValue), ":");
while (token)
{
SetCondition(TranslateConditionString(token));
token = strtok(NULL, ":");
}
SetForEachDelimited(*this, szValue, ":", &CAI_Monitor::SetCondition, CAI_MonitorTranslator<&CAI_Monitor::TranslateConditionString>(*this));
}
else if (FStrEq(szKeyName, "SchedulesSimple"))
{
@ -703,13 +724,7 @@ bool CAI_Monitor::KeyValue( const char *szKeyName, const char *szValue )
}
else if (FStrEq(szKeyName, "Schedules"))
{
char *token = strtok(strdup(szValue), ":");
while (token)
{
SetSchedule(TranslateScheduleString(token));
token = strtok(NULL, ":");
}
SetForEachDelimited(*this, szValue, ":", &CAI_Monitor::SetSchedule, CAI_MonitorTranslator<&CAI_Monitor::TranslateScheduleString>(*this));
}
else if (FStrEq(szKeyName, "HintsSimple"))
{
@ -718,13 +733,7 @@ bool CAI_Monitor::KeyValue( const char *szKeyName, const char *szValue )
}
else if (FStrEq(szKeyName, "Hints"))
{
char *token = strtok(strdup(szValue), ":");
while (token)
{
SetHint(atoi(szValue));
token = strtok(NULL, ":");
}
SetForEachDelimited(*this, szValue, ":", &CAI_Monitor::SetHint, atoi);
}
else
return CBaseEntity::KeyValue( szKeyName, szValue );

View File

@ -199,7 +199,7 @@ void CLogicExternalData::InputWriteKeyValue( inputdata_t &inputdata )
// Separate key from value
char *delimiter = Q_strstr(szValue, " ");
if (delimiter && (delimiter + 1) != '\0')
if (delimiter && delimiter[1] != '\0')
{
Q_strncpy(key, szValue, MIN((delimiter - szValue) + 1, sizeof(key)));
Q_strncpy(value, delimiter + 1, sizeof(value));

View File

@ -21,6 +21,8 @@ public:
DECLARE_DATADESC();
DECLARE_SERVERCLASS();
CMovieDisplay() { m_bMuted = true; }
virtual ~CMovieDisplay();
virtual bool KeyValue( const char *szKeyName, const char *szValue );
@ -53,6 +55,7 @@ private:
private:
CNetworkVar( bool, m_bEnabled );
CNetworkVar( bool, m_bLooping );
CNetworkVar( bool, m_bMuted);
CNetworkString( m_szDisplayText, 128 );
@ -93,6 +96,7 @@ BEGIN_DATADESC( CMovieDisplay )
DEFINE_KEYFIELD( m_iScreenWidth, FIELD_INTEGER, "width" ),
DEFINE_KEYFIELD( m_iScreenHeight, FIELD_INTEGER, "height" ),
DEFINE_KEYFIELD( m_bLooping, FIELD_BOOLEAN, "looping" ),
DEFINE_KEYFIELD( m_bMuted, FIELD_BOOLEAN, "muted"),
DEFINE_FIELD( m_bDoFullTransmit, FIELD_BOOLEAN ),
@ -108,6 +112,7 @@ END_DATADESC()
IMPLEMENT_SERVERCLASS_ST( CMovieDisplay, DT_MovieDisplay )
SendPropBool( SENDINFO( m_bEnabled ) ),
SendPropBool( SENDINFO( m_bLooping ) ),
SendPropBool( SENDINFO( m_bMuted ) ),
SendPropString( SENDINFO( m_szMovieFilename ) ),
SendPropString( SENDINFO( m_szGroupName ) ),
END_SEND_TABLE()
@ -120,6 +125,7 @@ CMovieDisplay::~CMovieDisplay()
//-----------------------------------------------------------------------------
// Read in Hammer data
//-----------------------------------------------------------------------------
bool CMovieDisplay::KeyValue( const char *szKeyName, const char *szValue )
{
// NOTE: Have to do these separate because they set two values instead of one

View File

@ -159,6 +159,16 @@ BEGIN_DATADESC(CRagdollProp)
DEFINE_RAGDOLL_ELEMENT( 21 ),
DEFINE_RAGDOLL_ELEMENT( 22 ),
DEFINE_RAGDOLL_ELEMENT( 23 ),
#ifdef MAPBASE
DEFINE_RAGDOLL_ELEMENT( 24 ),
DEFINE_RAGDOLL_ELEMENT( 25 ),
DEFINE_RAGDOLL_ELEMENT( 26 ),
DEFINE_RAGDOLL_ELEMENT( 27 ),
DEFINE_RAGDOLL_ELEMENT( 28 ),
DEFINE_RAGDOLL_ELEMENT( 29 ),
DEFINE_RAGDOLL_ELEMENT( 30 ),
DEFINE_RAGDOLL_ELEMENT( 31 ),
#endif
END_DATADESC()
@ -191,8 +201,10 @@ void CRagdollProp::Spawn( void )
// Starts out as the default fade scale value
m_flDefaultFadeScale = m_flFadeScale;
#ifndef MAPBASE
// NOTE: If this fires, then the assert or the datadesc is wrong! (see DEFINE_RAGDOLL_ELEMENT above)
Assert( RAGDOLL_MAX_ELEMENTS == 24 );
#endif
Precache();
SetModel( STRING( GetModelName() ) );
@ -1363,7 +1375,7 @@ CBaseAnimating *CreateServerRagdollSubmodel( CBaseAnimating *pOwner, const char
#ifdef MAPBASE_VSCRIPT
// Hook for pre-spawn ragdolling
if (pOwner->m_ScriptScope.IsInitialized() && CBaseAnimating::g_Hook_OnServerRagdoll.CanRunInScope( pOwner->m_ScriptScope ))
if (pOwner && pOwner->m_ScriptScope.IsInitialized() && CBaseAnimating::g_Hook_OnServerRagdoll.CanRunInScope( pOwner->m_ScriptScope ))
{
// ragdoll, submodel
ScriptVariant_t args[] = { ScriptVariant_t( pRagdoll->GetScriptInstance() ), true };

View File

@ -7698,6 +7698,10 @@ void CBasePlayer::Weapon_DropSlot( int weaponSlot )
}
}
#ifdef MAPBASE
ConVar player_autoswitch_on_first_pickup("player_autoswitch_on_pickup", "1", FCVAR_NONE, "Determines how the player should autoswitch when picking up a new weapon. 0 = no autoswitch, 1 = always (default), 2 = use unused weighting system");
#endif
//-----------------------------------------------------------------------------
// Purpose: Override to add weapon to the hud
//-----------------------------------------------------------------------------
@ -7706,24 +7710,25 @@ void CBasePlayer::Weapon_Equip( CBaseCombatWeapon *pWeapon )
BaseClass::Weapon_Equip( pWeapon );
#ifdef MAPBASE
// So, I discovered that BumpWeapon seems to have some deprecated code.
// It automatically switches the player to all new weapons. Sounds normal, right?
// Except that's *also* handled here. Since the BumpWeapon code implied the player could pick up weapons while carrying the mega physcannon,
// I assumed it was some old, deprecated code and, since I needed to remove a piece of (also deprecated) code in it, I decided to remove it entirely
// and hand weapon switching to this alone. Seems straightforward, right?
//
// Well, it turns out, this code was more complicated than I thought and used various weights and stuff to determine if a weapon was worth automatically switching to.
// It doesn't automatically switch to most of the weapons. Even though I seem to be right about that old code being deprecated,
// I got irritated and...uh...replaced the correct Weapon_Equip code with the old deprecated code from BumpWeapon.
//
// Trust me. It was hard and pointless to get used to. You'll thank me later.
if ( !PlayerHasMegaPhysCannon() )
// BumpWeapon's code appeared to be deprecated; The same operation is already handled here, but with much more code involved.
// There's also an unused weighting system which was overridden by that deprecated code. The unused weighting code can be enabled
// via player_autoswitch_on_first_pickup.
bool bShouldSwitch = false;
switch (player_autoswitch_on_first_pickup.GetInt())
{
Weapon_Switch( pWeapon );
// Unused Weighting
case 2:
bShouldSwitch = g_pGameRules->FShouldSwitchWeapon( this, pWeapon );
break;
// Always (old behavior)
case 1:
bShouldSwitch = true;
break;
}
#else
bool bShouldSwitch = g_pGameRules->FShouldSwitchWeapon( this, pWeapon );
#endif
#ifdef HL2_DLL
if ( bShouldSwitch == false && PhysCannonGetHeldEntity( GetActiveWeapon() ) == pWeapon &&
@ -7738,7 +7743,6 @@ void CBasePlayer::Weapon_Equip( CBaseCombatWeapon *pWeapon )
{
Weapon_Switch( pWeapon );
}
#endif
}
#ifdef MAPBASE

View File

@ -90,6 +90,10 @@ BEGIN_DATADESC( CRopeKeyframe )
DEFINE_INPUTFUNC( FIELD_VOID, "Break", InputBreak ),
#ifdef MAPBASE
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSlack", InputSetSlack ),
DEFINE_INPUTFUNC( FIELD_FLOAT, "SetWidth", InputSetWidth ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSubdivision", InputSetSubdivision ),
// Outputs
DEFINE_OUTPUT( m_OnBreak, "OnBreak" ),
#endif
@ -613,6 +617,51 @@ void CRopeKeyframe::InputBreak( inputdata_t &inputdata )
#endif
}
#ifdef MAPBASE
//-----------------------------------------------------------------------------
// Purpose: Sets the slack
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CRopeKeyframe::InputSetSlack( inputdata_t &inputdata )
{
m_Slack = inputdata.value.Int();
// Must resize in order for changes to occur
m_RopeFlags |= ROPE_RESIZE;
if (!(m_RopeFlags & ROPE_USE_WIND))
{
Warning( "WARNING: SetSlack on %s may need wind enabled in order to function\n", GetDebugName() );
}
}
//-----------------------------------------------------------------------------
// Purpose: Sets the width
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CRopeKeyframe::InputSetWidth( inputdata_t &inputdata )
{
m_Width = inputdata.value.Float();
}
//-----------------------------------------------------------------------------
// Purpose: Sets the subdivision
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CRopeKeyframe::InputSetSubdivision( inputdata_t &inputdata )
{
m_Subdiv = inputdata.value.Int();
// Must resize in order for changes to occur
m_RopeFlags |= ROPE_RESIZE;
if (!(m_RopeFlags & ROPE_USE_WIND))
{
Warning( "WARNING: SetSubdivision on %s may need wind enabled in order to function\n", GetDebugName() );
}
}
#endif
//-----------------------------------------------------------------------------
// Purpose: Breaks the rope
// Output : Returns true on success, false on failure.

Some files were not shown because too many files have changed in this diff Show More