diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 48256d92fa..c51715cf09 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -23,7 +23,12 @@ All contributions must follow the following rules: is usually not fit for Mapbase. * All content in a contribution must be either already legally open-source or done with the - full permission of the content's original creator(s). + full permission of the content's original creator(s). If licensing is involved, the contribution + must ensure Mapbase follows said licensing. + * **NOTE:** Due to concerns with mods which do not wish to be open-source, content using GPL licenses (or any + license with similar open-source requirements) are currently not allowed to be distributed with Mapbase. + Contributions which can draw from them without actually distributing the licensed content may theoretically + be excepted from this rule. * Contributions must not break existing maps/content or interfere with them in a negative or non-objective way. @@ -34,6 +39,15 @@ 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. + * Do not modify the README to add attribution for your contribution. That is handled by Mapbase's maintainers. + +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 diff --git a/README b/README index 12ecc545af..924b32fe50 100644 --- a/README +++ b/README @@ -1,6 +1,6 @@ //=================================================================================================================================================== - Mapbase - Source 2013 + Mapbase v7.0 - Source 2013 https://github.com/mapbase-source/source-sdk-2013 https://www.moddb.com/mods/mapbase @@ -20,10 +20,14 @@ 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 -the Source modding community packaged into a whole, so credit is taken very seriously. +Mapbase is an open-source project and its contents can be distributed and used at the discretion of its users. However, this project contains content from +a vast number of different sources which have their own licensing or attribution requirements. We try to handle most of that ourselves, but users who plan on +distributing Mapbase content are expected to comply with certain rules. Up-to-date information about Mapbase content usage and credit are addressed in this article on Mapbase's wiki: https://github.com/mapbase-source/source-sdk-2013/wiki/Using-Mapbase-Content @@ -37,25 +41,33 @@ 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. --- Mapbase also implements some of Tony Sergi's code changes from the Source 2007 SDK codebase. Both SDKs are publicly distributed by Valve and are available on Steam. +Mapbase uses content from the following non-Source SDK 2013 Valve games or SDKs: + +-- Alien Swarm SDK (Used to backport features and code from newer branches of Source into a Source 2013/Half-Life 2 environment) +-- Source SDK 2007 Code (Used to implement some of Tony Sergi's code changes) + +-- Alien Swarm (Used to port assets from the aforementioned SDK code features, e.g. game instructor icons) +-- Left 4 Dead (Used to port certain animations as well as assets from the aforementioned SDK code features, e.g. particle rain) +-- Half-Life: Source (Used to port friction tool textures) -Some of the features backported from the Alien Swarm SDK (e.g. game instructor, particle rain) require assets from later versions of Source in order to work properly. -The required assets have been backported from Alien Swarm and Left 4 Dead for the release build. They are not available in the code repository. +Valve allows assets from these titles to be distributed for modding purposes. Note that ported assets are only used in the release build, not the code repository. -Here's a list of Mapbase's other known external code sources: +Mapbase may also contain new third-party software distributed under specific licensing. Please see the bottom of thirdpartylegalnotices.txt for more information. + +Here's a list of Mapbase's other external code sources: - https://github.com/95Navigator/insolence-2013 (Initial custom shader code and projected texture improvements; also used to implement ASW SDK particle precipitation code) -- https://github.com/Biohazard90/g-string_2013 (Custom shadow filters, included indirectly via Insolence repo) -- https://github.com/KyleGospo/City-17-Episode-One-Source (Brush phong and projected texture changes, included indirectly via Insolence repo) +-- https://github.com/Biohazard90/g-string_2013 (Custom shadow filters, included indirectly via Insolence repo) +-- https://github.com/KyleGospo/City-17-Episode-One-Source (Brush phong and projected texture changes, included indirectly via Insolence repo) - https://github.com/DownFall-Team/DownFall (Multiple skybox code and fix for ent_fire delay not using floats; Also used as a guide to port certain Alien Swarm SDK changes to Source 2013, including radial fog, rope code, and treesway) -- https://github.com/momentum-mod/game (Used as a guide to port postprocess_controller and env_dof_controller to Source 2013) +- https://github.com/momentum-mod/game (Used as a guide to port postprocess_controller and env_dof_controller to Source 2013 from the Alien Swarm SDK) - https://github.com/DeathByNukes/source-sdk-2013 (VBSP manifest fixes) - https://github.com/entropy-zero/source-sdk-2013 (skill_changed game event) +- https://github.com/Nbc66/source-sdk-2013-ce/tree/v142 (Base for VS2019 toolset support) //--------------------------------------------------------------------------------------------------------------------------------------------------- @@ -91,9 +103,11 @@ 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) +- https://github.com/mapbase-source/source-sdk-2013/pull/140 (logic_substring entity and icon created by moofemp) +- https://github.com/mapbase-source/source-sdk-2013/pull/143 (Propper features for VBSP from Matty-64) - 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 +121,25 @@ 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) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/122 (Minor VScript-related adjustments) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/148 (Minor fixup) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/167 (Security fixes) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/168 (Squirrel update) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/171 (VScript documentation sorting) + +== 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) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/124 (Memory error fixes) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/130 (Memory error fixes) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/131 (env_projectedtexture target shadows fix) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/132 (Console error fix) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/152 (Additional GCC/Linux compilation fixes) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/159 (Additional GCC/Linux compilation fixes) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/162 (VS2019 exception specification fix) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/170 (HL2 non-Episodic build fix) //--------------------------------------------------------------------------------------------------------------------------------------------------- @@ -135,6 +168,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: diff --git a/sp/src/devtools/makefile_base_posix.mak b/sp/src/devtools/makefile_base_posix.mak index 559ee2beb6..21147f214f 100644 --- a/sp/src/devtools/makefile_base_posix.mak +++ b/sp/src/devtools/makefile_base_posix.mak @@ -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 diff --git a/sp/src/game/client/c_baseanimating.cpp b/sp/src/game/client/c_baseanimating.cpp index 640459e516..1c86747248 100644 --- a/sp/src/game/client/c_baseanimating.cpp +++ b/sp/src/game/client/c_baseanimating.cpp @@ -259,6 +259,9 @@ LINK_ENTITY_TO_CLASS( client_ragdoll, C_ClientRagdoll ); BEGIN_DATADESC( C_ClientRagdoll ) DEFINE_FIELD( m_bFadeOut, FIELD_BOOLEAN ), DEFINE_FIELD( m_bImportant, FIELD_BOOLEAN ), +#ifdef MAPBASE + DEFINE_FIELD( m_flForcedRetireTime, FIELD_FLOAT ), +#endif DEFINE_FIELD( m_iCurrentFriction, FIELD_INTEGER ), DEFINE_FIELD( m_iMinFriction, FIELD_INTEGER ), DEFINE_FIELD( m_iMaxFriction, FIELD_INTEGER ), @@ -294,6 +297,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 +314,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 +366,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(); @@ -365,6 +380,9 @@ C_ClientRagdoll::C_ClientRagdoll( bool bRestoring ) m_bFadeOut = false; m_bFadingOut = false; m_bImportant = false; +#ifdef MAPBASE + m_flForcedRetireTime = 0.0f; +#endif m_bNoModelParticles = false; SetClassname("client_ragdoll"); @@ -445,7 +463,11 @@ void C_ClientRagdoll::OnRestore( void ) if ( m_bFadeOut == true ) { +#ifdef MAPBASE + s_RagdollLRU.MoveToTopOfLRU( this, m_bImportant, m_flForcedRetireTime ); +#else s_RagdollLRU.MoveToTopOfLRU( this, m_bImportant ); +#endif } NoteRagdollCreationTick( this ); @@ -772,6 +794,10 @@ C_BaseAnimating::C_BaseAnimating() : m_nPrevSequence = -1; m_nRestoreSequence = -1; m_pRagdoll = NULL; + m_pClientsideRagdoll = NULL; +#ifdef MAPBASE + m_pServerRagdoll = NULL; +#endif m_builtRagdoll = false; m_hitboxBoneCacheHandle = 0; int i; @@ -1538,10 +1564,43 @@ HSCRIPT C_BaseAnimating::ScriptGetAttachmentMatrix( int iAttachment ) void C_BaseAnimating::ScriptGetBoneTransform( int iBone, HSCRIPT hTransform ) { - if (hTransform == NULL) + matrix3x4_t *matTransform = HScriptToClass( hTransform ); + if (matTransform == NULL) + return; + + GetBoneTransform( iBone, *matTransform ); +} + +void C_BaseAnimating::ScriptSetBoneTransform( int iBone, HSCRIPT hTransform ) +{ + matrix3x4_t *matTransform = HScriptToClass( hTransform ); + if (matTransform == NULL) return; - GetBoneTransform( iBone, *HScriptToClass( hTransform ) ); + 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 +1778,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 } //----------------------------------------------------------------------------- @@ -1925,6 +2000,10 @@ CollideType_t C_BaseAnimating::GetCollideType( void ) return BaseClass::GetCollideType(); } +#ifdef MAPBASE +ConVar ai_death_pose_enabled( "ai_death_pose_enabled", "1", FCVAR_NONE, "Toggles the death pose fix code, which cancels sequence transitions while a NPC is ragdolling." ); +#endif + //----------------------------------------------------------------------------- // Purpose: if the active sequence changes, keep track of the previous ones and decay them based on their decay rate //----------------------------------------------------------------------------- @@ -1941,6 +2020,14 @@ void C_BaseAnimating::MaintainSequenceTransitions( IBoneSetup &boneSetup, float return; } +#ifdef MAPBASE + if ( IsAboutToRagdoll() && ai_death_pose_enabled.GetBool() ) + { + m_nPrevNewSequenceParity = m_nNewSequenceParity; + return; + } +#endif + m_SequenceTransitioner.CheckForSequenceChange( boneSetup.GetStudioHdr(), GetSequence(), @@ -2720,14 +2807,29 @@ void C_BaseAnimating::CalculateIKLocks( float currentTime ) // debugoverlay->AddBoxOverlay( origin, Vector( -1, -1, -1 ), Vector( 1, 1, 1 ), QAngle( 0, 0, 0 ), 255, 0, 0, 0, 0 ); - float d = (pTarget->est.pos - origin).Length(); + Vector vecDelta = (origin - pTarget->est.pos); + float d = vecDelta.Length(); if ( d >= flDist) continue; flDist = d; - pTarget->SetPos( origin ); - pTarget->SetAngles( angles ); +#ifdef MAPBASE + // For blending purposes, IK attachments should obey weight + if ( pTarget->est.flWeight < 1.0f ) + { + Quaternion qTarget; + AngleQuaternion( angles, qTarget ); + + QuaternionSlerp( pTarget->est.q, qTarget, pTarget->est.flWeight, pTarget->est.q ); + pTarget->SetPos( pTarget->est.pos + (vecDelta * pTarget->est.flWeight) ); + } + else +#endif + { + pTarget->SetPos( origin ); + pTarget->SetAngles( angles ); + } // debugoverlay->AddBoxOverlay( pTarget->est.pos, Vector( -pTarget->est.radius, -pTarget->est.radius, -pTarget->est.radius ), Vector( pTarget->est.radius, pTarget->est.radius, pTarget->est.radius), QAngle( 0, 0, 0 ), 0, 255, 0, 0, 0 ); } @@ -4808,12 +4910,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; @@ -4881,26 +4989,26 @@ C_BaseAnimating *C_BaseAnimating::BecomeRagdollOnClient() { MoveToLastReceivedPosition( true ); GetAbsOrigin(); - C_BaseAnimating *pRagdoll = CreateRagdollCopy(); + m_pClientsideRagdoll = CreateRagdollCopy(); matrix3x4_t boneDelta0[MAXSTUDIOBONES]; matrix3x4_t boneDelta1[MAXSTUDIOBONES]; matrix3x4_t currentBones[MAXSTUDIOBONES]; const float boneDt = 0.1f; GetRagdollInitBoneArrays( boneDelta0, boneDelta1, currentBones, boneDt ); - pRagdoll->InitAsClientRagdoll( boneDelta0, boneDelta1, currentBones, boneDt ); + m_pClientsideRagdoll->InitAsClientRagdoll( boneDelta0, boneDelta1, currentBones, boneDt ); #ifdef MAPBASE_VSCRIPT // Hook for ragdolling if (m_ScriptScope.IsInitialized() && g_Hook_OnClientRagdoll.CanRunInScope( m_ScriptScope )) { // ragdoll - ScriptVariant_t args[] = { ScriptVariant_t( pRagdoll->GetScriptInstance() ) }; + ScriptVariant_t args[] = { ScriptVariant_t( m_pClientsideRagdoll->GetScriptInstance() ) }; g_Hook_OnClientRagdoll.Call( m_ScriptScope, NULL, args ); } #endif - return pRagdoll; + return m_pClientsideRagdoll; } bool C_BaseAnimating::InitAsClientRagdoll( const matrix3x4_t *pDeltaBones0, const matrix3x4_t *pDeltaBones1, const matrix3x4_t *pCurrentBonePosition, float boneDt, bool bFixedConstraints ) @@ -5366,6 +5474,11 @@ void C_BaseAnimating::StudioFrameAdvance() if ( flNewCycle < 0.0f || flNewCycle >= 1.0f ) { + if (flNewCycle >= 1.0f) + { + ReachedEndOfSequence(); + } + if ( IsSequenceLooping( hdr, GetSequence() ) ) { flNewCycle -= (int)(flNewCycle); diff --git a/sp/src/game/client/c_baseanimating.h b/sp/src/game/client/c_baseanimating.h index 50b3659b9e..0f2f57df23 100644 --- a/sp/src/game/client/c_baseanimating.h +++ b/sp/src/game/client/c_baseanimating.h @@ -38,6 +38,7 @@ class C_BaseClientShader */ class IRagdoll; +class C_ClientRagdoll; class CIKContext; class CIKState; class ConVar; @@ -79,7 +80,7 @@ class CAttachmentData QAngle m_angRotation; Vector m_vOriginVelocity; int m_nLastFramecount : 31; - int m_bAnglesComputed : 1; + bool m_bAnglesComputed : 1; }; @@ -301,6 +302,7 @@ class C_BaseAnimating : public C_BaseEntity, private IModelLoadCallback 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 @@ class C_BaseAnimating : public C_BaseEntity, private IModelLoadCallback void ClientSideAnimationChanged(); virtual unsigned int ComputeClientSideAnimationFlags(); + virtual void ReachedEndOfSequence() { return; } + virtual void ResetClientsideFrame( void ) { SetCycle( 0 ); } void SetCycle( float flCycle ); @@ -464,6 +468,11 @@ class C_BaseAnimating : public C_BaseEntity, private IModelLoadCallback 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 @@ class C_BaseAnimating : public C_BaseEntity, private IModelLoadCallback static ScriptHook_t g_Hook_OnClientRagdoll; static ScriptHook_t g_Hook_FireEvent; + static ScriptHook_t g_Hook_BuildTransformations; float ScriptGetPoseParameter(const char* szName); #endif @@ -521,6 +531,10 @@ class C_BaseAnimating : public C_BaseEntity, private IModelLoadCallback public: CRagdoll *m_pRagdoll; + C_BaseAnimating *m_pClientsideRagdoll; // From Alien Swarm SDK +#ifdef MAPBASE + C_BaseAnimating *m_pServerRagdoll; // Not from Alien Swarm SDK (note that this can exist without the entity having died) +#endif // Texture group to use int m_nSkin; @@ -723,6 +737,10 @@ class C_ClientRagdoll : public C_BaseAnimating, public IPVSNotify bool m_bFadeOut; bool m_bImportant; +#ifdef MAPBASE + // Required to save/restore Alien Swarm SDK ragdoll LRU forced fade + float m_flForcedRetireTime; +#endif float m_flEffectTime; private: diff --git a/sp/src/game/client/c_basecombatweapon.cpp b/sp/src/game/client/c_basecombatweapon.cpp index 639fd4c581..3ae13cdee8 100644 --- a/sp/src/game/client/c_basecombatweapon.cpp +++ b/sp/src/game/client/c_basecombatweapon.cpp @@ -125,9 +125,15 @@ void C_BaseCombatWeapon::OnRestore() int C_BaseCombatWeapon::GetWorldModelIndex( void ) { +#ifdef MAPBASE + int iIndex = GetOwner() ? m_iWorldModelIndex.Get() : m_iDroppedModelIndex.Get(); +#else + int iIndex = m_iWorldModelIndex.Get(); +#endif + if ( GameRules() ) { - const char *pBaseName = modelinfo->GetModelName( modelinfo->GetModel( m_iWorldModelIndex ) ); + const char *pBaseName = modelinfo->GetModelName( modelinfo->GetModel( iIndex ) ); const char *pTranslatedName = GameRules()->TranslateEffectForVisionFilter( "weapons", pBaseName ); if ( pTranslatedName != pBaseName ) @@ -136,7 +142,7 @@ int C_BaseCombatWeapon::GetWorldModelIndex( void ) } } - return m_iWorldModelIndex; + return iIndex; } //----------------------------------------------------------------------------- @@ -502,25 +508,44 @@ int C_BaseCombatWeapon::DrawModel( int flags ) // check if local player chases owner of this weapon in first person C_BasePlayer *localplayer = C_BasePlayer::GetLocalPlayer(); - if ( localplayer && localplayer->IsObserver() && GetOwner() ) + if ( localplayer ) { #ifdef MAPBASE if (localplayer->m_bDrawPlayerModelExternally) { // If this isn't the main view, draw the weapon. view_id_t viewID = CurrentViewID(); - if (viewID != VIEW_MAIN && viewID != VIEW_INTRO_CAMERA) - return BaseClass::DrawModel( flags ); + if ( (!localplayer->InFirstPersonView() || (viewID != VIEW_MAIN && viewID != VIEW_INTRO_CAMERA)) && (viewID != VIEW_SHADOW_DEPTH_TEXTURE || !localplayer->IsEffectActive(EF_DIMLIGHT)) ) + { + // TODO: Is this inefficient? + int nModelIndex = GetModelIndex(); + int nWorldModelIndex = GetWorldModelIndex(); + if (nModelIndex != nWorldModelIndex) + { + SetModelIndex(nWorldModelIndex); + } + + int iDraw = BaseClass::DrawModel(flags); + + if (nModelIndex != nWorldModelIndex) + { + SetModelIndex(nModelIndex); + } + + return iDraw; + } } #endif - - // don't draw weapon if chasing this guy as spectator - // we don't check that in ShouldDraw() since this may change - // without notification + if ( localplayer->IsObserver() && GetOwner() ) + { + // don't draw weapon if chasing this guy as spectator + // we don't check that in ShouldDraw() since this may change + // without notification - if ( localplayer->GetObserverMode() == OBS_MODE_IN_EYE && - localplayer->GetObserverTarget() == GetOwner() ) - return false; + if ( localplayer->GetObserverMode() == OBS_MODE_IN_EYE && + localplayer->GetObserverTarget() == GetOwner() ) + return false; + } } return BaseClass::DrawModel( flags ); diff --git a/sp/src/game/client/c_baseentity.cpp b/sp/src/game/client/c_baseentity.cpp index a3b4037812..b7ded40fca 100644 --- a/sp/src/game/client/c_baseentity.cpp +++ b/sp/src/game/client/c_baseentity.cpp @@ -428,6 +428,11 @@ BEGIN_RECV_TABLE_NOBASE( C_BaseEntity, DT_AnimTimeMustBeFirst ) RecvPropInt( RECVINFO(m_flAnimTime), 0, RecvProxy_AnimTime ), END_RECV_TABLE() +#ifdef MAPBASE_VSCRIPT +ScriptHook_t C_BaseEntity::g_Hook_UpdateOnRemove; +ScriptHook_t C_BaseEntity::g_Hook_ModifyEmitSoundParams; +#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 +555,14 @@ 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( C_BaseEntity::g_Hook_UpdateOnRemove, "UpdateOnRemove", FIELD_VOID, "Called when the entity is being removed." ) + + BEGIN_SCRIPTHOOK( C_BaseEntity::g_Hook_ModifyEmitSoundParams, "ModifyEmitSoundParams", FIELD_VOID, "Called every time a sound is emitted on this entity, allowing for its parameters to be modified." ) + DEFINE_SCRIPTHOOK_PARAM( "params", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + +#endif // MAPBASE_VSCRIPT END_SCRIPTDESC(); @@ -1340,6 +1352,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; @@ -4914,9 +4932,15 @@ C_BaseEntity *C_BaseEntity::Instance( int iEnt ) } #ifdef WIN32 + +#if _MSC_VER < 1900 #pragma warning( push ) #include #pragma warning( pop ) +#else +#include +#endif + #endif //----------------------------------------------------------------------------- diff --git a/sp/src/game/client/c_baseentity.h b/sp/src/game/client/c_baseentity.h index dabfbbd9d6..fe159afee2 100644 --- a/sp/src/game/client/c_baseentity.h +++ b/sp/src/game/client/c_baseentity.h @@ -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,9 @@ class C_BaseEntity : public IClientEntity string_t m_iszScriptId; #ifdef MAPBASE_VSCRIPT CScriptScope m_ScriptScope; + + static ScriptHook_t g_Hook_UpdateOnRemove; + static ScriptHook_t g_Hook_ModifyEmitSoundParams; #endif // IClientUnknown overrides. @@ -400,7 +402,7 @@ class C_BaseEntity : public IClientEntity #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, @@ -1262,7 +1264,7 @@ class C_BaseEntity : public IClientEntity #ifdef _DEBUG void FunctionCheck( void *pFunction, const char *name ); - ENTITYFUNCPTR TouchSet( ENTITYFUNCPTR func, char *name ) + ENTITYFUNCPTR TouchSet( ENTITYFUNCPTR func, const char *name ) { //COMPILE_TIME_ASSERT( sizeof(func) == 4 ); m_pfnTouch = func; diff --git a/sp/src/game/client/c_baselesson.cpp b/sp/src/game/client/c_baselesson.cpp index 8a2d961722..ec01575bb8 100644 --- a/sp/src/game/client/c_baselesson.cpp +++ b/sp/src/game/client/c_baselesson.cpp @@ -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 ); diff --git a/sp/src/game/client/c_baselesson.h b/sp/src/game/client/c_baselesson.h index 2cad41ca88..b413d282ea 100644 --- a/sp/src/game/client/c_baselesson.h +++ b/sp/src/game/client/c_baselesson.h @@ -426,7 +426,7 @@ class CScriptedIconLesson : public CIconLesson LessonEvent_t * AddUpdateEvent( void ); private: - static CUtlDict< int, int > CScriptedIconLesson::LessonActionMap; + static CUtlDict< int, int > LessonActionMap; EHANDLE m_hLocalPlayer; float m_fOutput; diff --git a/sp/src/game/client/c_baseplayer.cpp b/sp/src/game/client/c_baseplayer.cpp index ced6ced984..6e06834155 100644 --- a/sp/src/game/client/c_baseplayer.cpp +++ b/sp/src/game/client/c_baseplayer.cpp @@ -281,6 +281,7 @@ END_RECV_TABLE() RecvPropInt ( RECVINFO( m_spawnflags ), 0, RecvProxy_ShiftPlayerSpawnflags ), RecvPropBool ( RECVINFO( m_bDrawPlayerModelExternally ) ), + RecvPropBool ( RECVINFO( m_bInTriggerFall ) ), #endif END_RECV_TABLE() @@ -331,7 +332,10 @@ END_RECV_TABLE() RecvPropString( RECVINFO(m_szLastPlaceName) ), - RecvPropEHandle(RECVINFO(m_hPostProcessCtrl)), // Send to everybody - for spectating +#ifdef MAPBASE // From Alien Swarm SDK + RecvPropEHandle( RECVINFO( m_hPostProcessCtrl ) ), // Send to everybody - for spectating + RecvPropEHandle( RECVINFO( m_hColorCorrectionCtrl ) ), // Send to everybody - for spectating +#endif #if defined USES_ECON_ITEMS RecvPropUtlVector( RECVINFO_UTLVECTOR( m_hMyWearables ), MAX_WEARABLES_SENT_FROM_SERVER, RecvPropEHandle(NULL, 0, 0) ), @@ -1363,6 +1367,10 @@ void C_BasePlayer::AddEntity( void ) // Add in lighting effects CreateLightEffects(); + +#ifdef MAPBASE + SetLocalAnglesDim( X_INDEX, 0 ); +#endif } extern float UTIL_WaterLevel( const Vector &position, float minz, float maxz ); @@ -1485,8 +1493,9 @@ int C_BasePlayer::DrawModel( int flags ) if (m_bDrawPlayerModelExternally) { // Draw the player in any view except the main or "intro" view, both of which are default first-person views. + // HACKHACK: Also don't draw in shadow depth textures if the player's flashlight is on, as that causes the playermodel to block it. view_id_t viewID = CurrentViewID(); - if (viewID == VIEW_MAIN || viewID == VIEW_INTRO_CAMERA) + if (viewID == VIEW_MAIN || viewID == VIEW_INTRO_CAMERA || (viewID == VIEW_SHADOW_DEPTH_TEXTURE && IsEffectActive(EF_DIMLIGHT))) { // Make sure the player model wouldn't draw anyway... if (!ShouldDrawThisPlayer()) @@ -1509,6 +1518,38 @@ int C_BasePlayer::DrawModel( int flags ) return BaseClass::DrawModel( flags ); } +#ifdef MAPBASE +ConVar cl_player_allow_thirdperson_projtex( "cl_player_allow_thirdperson_projtex", "1", FCVAR_NONE, "Allows players to receive projected textures if they're non-local or in third person." ); +ConVar cl_player_allow_thirdperson_rttshadows( "cl_player_allow_thirdperson_rttshadows", "0", FCVAR_NONE, "Allows players to cast RTT shadows if they're non-local or in third person." ); +ConVar cl_player_allow_firstperson_projtex( "cl_player_allow_firstperson_projtex", "1", FCVAR_NONE, "Allows players to receive projected textures even if they're in first person." ); +ConVar cl_player_allow_firstperson_rttshadows( "cl_player_allow_firstperson_rttshadows", "0", FCVAR_NONE, "Allows players to cast RTT shadows even if they're in first person." ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ShadowType_t C_BasePlayer::ShadowCastType() +{ + if ( (!IsLocalPlayer() || ShouldDraw()) ? !cl_player_allow_thirdperson_rttshadows.GetBool() : !cl_player_allow_firstperson_rttshadows.GetBool() ) + return SHADOWS_NONE; + + if ( !IsVisible() ) + return SHADOWS_NONE; + + return SHADOWS_RENDER_TO_TEXTURE_DYNAMIC; +} + +//----------------------------------------------------------------------------- +// Should this object receive shadows? +//----------------------------------------------------------------------------- +bool C_BasePlayer::ShouldReceiveProjectedTextures( int flags ) +{ + if ( (!IsLocalPlayer() || ShouldDraw()) ? !cl_player_allow_thirdperson_projtex.GetBool() : !cl_player_allow_firstperson_projtex.GetBool() ) + return false; + + return BaseClass::ShouldReceiveProjectedTextures( flags ); +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -2917,6 +2958,7 @@ void C_BasePlayer::UpdateFogBlend( void ) } } +#ifdef MAPBASE // From Alien Swarm SDK //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- @@ -2925,6 +2967,15 @@ C_PostProcessController* C_BasePlayer::GetActivePostProcessController() const return m_hPostProcessCtrl.Get(); } +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +C_ColorCorrection* C_BasePlayer::GetActiveColorCorrection() const +{ + return m_hColorCorrectionCtrl.Get(); +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- diff --git a/sp/src/game/client/c_baseplayer.h b/sp/src/game/client/c_baseplayer.h index fac6c0dd8f..bc8ca65419 100644 --- a/sp/src/game/client/c_baseplayer.h +++ b/sp/src/game/client/c_baseplayer.h @@ -23,7 +23,10 @@ #include "hintsystem.h" #include "SoundEmitterSystem/isoundemittersystembase.h" #include "c_env_fog_controller.h" +#ifdef MAPBASE // From Alien Swarm SDK #include "c_postprocesscontroller.h" +#include "c_colorcorrection.h" +#endif #include "igameevents.h" #include "GameEventListener.h" @@ -204,6 +207,11 @@ class C_BasePlayer : public C_BaseCombatCharacter, public CGameEventListener void SetMaxSpeed( float flMaxSpeed ) { m_flMaxspeed = flMaxSpeed; } float MaxSpeed() const { return m_flMaxspeed; } +#ifdef MAPBASE + // See c_baseplayer.cpp + virtual ShadowType_t ShadowCastType(); + virtual bool ShouldReceiveProjectedTextures( int flags ); +#else // Should this object cast shadows? virtual ShadowType_t ShadowCastType() { return SHADOWS_NONE; } @@ -211,6 +219,7 @@ class C_BasePlayer : public C_BaseCombatCharacter, public CGameEventListener { return false; } +#endif bool IsLocalPlayer( void ) const; @@ -381,7 +390,10 @@ class C_BasePlayer : public C_BaseCombatCharacter, public CGameEventListener void UpdateFogController( void ); void UpdateFogBlend( void ); +#ifdef MAPBASE // From Alien Swarm SDK C_PostProcessController* GetActivePostProcessController() const; + C_ColorCorrection* GetActiveColorCorrection() const; +#endif float GetFOVTime( void ){ return m_flFOVTime; } @@ -458,6 +470,8 @@ class C_BasePlayer : public C_BaseCombatCharacter, public CGameEventListener // Allows the player's model to draw on non-main views, like monitors or mirrors. bool m_bDrawPlayerModelExternally; + + bool m_bInTriggerFall; #endif protected: @@ -645,7 +659,10 @@ class C_BasePlayer : public C_BaseCombatCharacter, public CGameEventListener // One for left and one for right side of step StepSoundCache_t m_StepSoundCache[ 2 ]; - CNetworkHandle(C_PostProcessController, m_hPostProcessCtrl); // active postprocessing controller +#ifdef MAPBASE // From Alien Swarm SDK + CNetworkHandle( C_PostProcessController, m_hPostProcessCtrl ); // active postprocessing controller + CNetworkHandle( C_ColorCorrection, m_hColorCorrectionCtrl ); // active FXVolume color correction +#endif public: diff --git a/sp/src/game/client/c_baseviewmodel.cpp b/sp/src/game/client/c_baseviewmodel.cpp index b053ac1b67..6e73e1556f 100644 --- a/sp/src/game/client/c_baseviewmodel.cpp +++ b/sp/src/game/client/c_baseviewmodel.cpp @@ -192,7 +192,7 @@ bool C_BaseViewModel::Interpolate( float currentTime ) } -inline bool C_BaseViewModel::ShouldFlipViewModel() +bool C_BaseViewModel::ShouldFlipViewModel() { #if defined(CSTRIKE_DLL) || defined (MAPBASE) // If cl_righthand is set, then we want them all right-handed. diff --git a/sp/src/game/client/c_colorcorrection.cpp b/sp/src/game/client/c_colorcorrection.cpp index 6960031d5f..12e29768e8 100644 --- a/sp/src/game/client/c_colorcorrection.cpp +++ b/sp/src/game/client/c_colorcorrection.cpp @@ -6,6 +6,7 @@ //===========================================================================// #include "cbase.h" +#include "c_colorcorrection.h" #include "filesystem.h" #include "cdll_client_int.h" #include "colorcorrectionmgr.h" @@ -17,45 +18,27 @@ static ConVar mat_colcorrection_disableentities( "mat_colcorrection_disableentities", "0", FCVAR_NONE, "Disable map color-correction entities" ); - -//------------------------------------------------------------------------------ -// Purpose : Color correction entity with radial falloff -//------------------------------------------------------------------------------ -class C_ColorCorrection : public C_BaseEntity -{ -public: - DECLARE_CLASS( C_ColorCorrection, C_BaseEntity ); - - DECLARE_CLIENTCLASS(); - - C_ColorCorrection(); - virtual ~C_ColorCorrection(); - - void OnDataChanged(DataUpdateType_t updateType); - bool ShouldDraw(); - - void ClientThink(); - -private: - Vector m_vecOrigin; - - float m_minFalloff; - float m_maxFalloff; - float m_flCurWeight; - char m_netLookupFilename[MAX_PATH]; - - bool m_bEnabled; - - ClientCCHandle_t m_CCHandle; -}; +#ifdef MAPBASE // From Alien Swarm SDK +static ConVar mat_colcorrection_forceentitiesclientside( "mat_colcorrection_forceentitiesclientside", "0", FCVAR_CHEAT, "Forces color correction entities to be updated on the client" ); +#endif IMPLEMENT_CLIENTCLASS_DT(C_ColorCorrection, DT_ColorCorrection, CColorCorrection) RecvPropVector( RECVINFO(m_vecOrigin) ), RecvPropFloat( RECVINFO(m_minFalloff) ), RecvPropFloat( RECVINFO(m_maxFalloff) ), RecvPropFloat( RECVINFO(m_flCurWeight) ), +#ifdef MAPBASE // From Alien Swarm SDK + RecvPropFloat( RECVINFO(m_flMaxWeight) ), + RecvPropFloat( RECVINFO(m_flFadeInDuration) ), + RecvPropFloat( RECVINFO(m_flFadeOutDuration) ), +#endif RecvPropString( RECVINFO(m_netLookupFilename) ), RecvPropBool( RECVINFO(m_bEnabled) ), +#ifdef MAPBASE // From Alien Swarm SDK + RecvPropBool( RECVINFO(m_bMaster) ), + RecvPropBool( RECVINFO(m_bClientSide) ), + RecvPropBool( RECVINFO(m_bExclusive) ) +#endif END_RECV_TABLE() @@ -65,14 +48,43 @@ END_RECV_TABLE() //------------------------------------------------------------------------------ C_ColorCorrection::C_ColorCorrection() { +#ifdef MAPBASE // From Alien Swarm SDK + m_minFalloff = -1.0f; + m_maxFalloff = -1.0f; + m_flFadeInDuration = 0.0f; + m_flFadeOutDuration = 0.0f; + m_flCurWeight = 0.0f; + m_flMaxWeight = 1.0f; + m_netLookupFilename[0] = '\0'; + m_bEnabled = false; + m_bMaster = false; + m_bExclusive = false; +#endif m_CCHandle = INVALID_CLIENT_CCHANDLE; + +#ifdef MAPBASE // From Alien Swarm SDK + m_bFadingIn = false; + m_flFadeStartWeight = 0.0f; + m_flFadeStartTime = 0.0f; + m_flFadeDuration = 0.0f; +#endif } C_ColorCorrection::~C_ColorCorrection() { +#ifdef MAPBASE // From Alien Swarm SDK + g_pColorCorrectionMgr->RemoveColorCorrectionEntity( this, m_CCHandle ); +#else g_pColorCorrectionMgr->RemoveColorCorrection( m_CCHandle ); +#endif } +#ifdef MAPBASE // From Alien Swarm SDK +bool C_ColorCorrection::IsClientSide() const +{ + return m_bClientSide || mat_colcorrection_forceentitiesclientside.GetBool(); +} +#endif //------------------------------------------------------------------------------ // Purpose : @@ -87,11 +99,21 @@ void C_ColorCorrection::OnDataChanged(DataUpdateType_t updateType) { if ( m_CCHandle == INVALID_CLIENT_CCHANDLE ) { +#ifdef MAPBASE // From Alien Swarm SDK + // forming a unique name without extension + char cleanName[MAX_PATH]; + V_StripExtension( m_netLookupFilename, cleanName, sizeof( cleanName ) ); + char name[MAX_PATH]; + Q_snprintf( name, MAX_PATH, "%s_%d", cleanName, entindex() ); + + m_CCHandle = g_pColorCorrectionMgr->AddColorCorrectionEntity( this, name, m_netLookupFilename ); +#else char filename[MAX_PATH]; Q_strncpy( filename, m_netLookupFilename, MAX_PATH ); m_CCHandle = g_pColorCorrectionMgr->AddColorCorrection( filename ); SetNextClientThink( ( m_CCHandle != INVALID_CLIENT_CCHANDLE ) ? CLIENT_THINK_ALWAYS : CLIENT_THINK_NEVER ); +#endif } } } @@ -104,6 +126,129 @@ bool C_ColorCorrection::ShouldDraw() return false; } +#ifdef MAPBASE // From Alien Swarm SDK +void C_ColorCorrection::Update( C_BasePlayer *pPlayer, float ccScale ) +{ + Assert( m_CCHandle != INVALID_CLIENT_CCHANDLE ); + + if ( mat_colcorrection_disableentities.GetInt() ) + { + // Allow the colorcorrectionui panel (or user) to turn off color-correction entities + g_pColorCorrectionMgr->SetColorCorrectionWeight( m_CCHandle, 0.0f, m_bExclusive ); + return; + } + + // fade weight on client + if ( IsClientSide() ) + { + m_flCurWeight = Lerp( GetFadeRatio(), m_flFadeStartWeight, m_bFadingIn ? m_flMaxWeight : 0.0f ); + } + + if( !m_bEnabled && m_flCurWeight == 0.0f ) + { + g_pColorCorrectionMgr->SetColorCorrectionWeight( m_CCHandle, 0.0f, m_bExclusive ); + return; + } + + Vector playerOrigin = pPlayer->GetAbsOrigin(); + + float weight = 0; + if ( ( m_minFalloff != -1 ) && ( m_maxFalloff != -1 ) && m_minFalloff != m_maxFalloff ) + { + float dist = (playerOrigin - m_vecOrigin).Length(); + weight = (dist-m_minFalloff) / (m_maxFalloff-m_minFalloff); + if ( weight<0.0f ) weight = 0.0f; + if ( weight>1.0f ) weight = 1.0f; + } + + g_pColorCorrectionMgr->SetColorCorrectionWeight( m_CCHandle, m_flCurWeight * ( 1.0 - weight ) * ccScale, m_bExclusive ); +} + +void C_ColorCorrection::EnableOnClient( bool bEnable, bool bSkipFade ) +{ + if ( !IsClientSide() ) + { + return; + } + + m_bFadingIn = bEnable; + + // initialize countdown timer + m_flFadeStartWeight = m_flCurWeight; + float flFadeTimeScale = 1.0f; + if ( m_flMaxWeight != 0.0f ) + { + flFadeTimeScale = m_flCurWeight / m_flMaxWeight; + } + + if ( m_bFadingIn ) + { + flFadeTimeScale = 1.0f - flFadeTimeScale; + } + + if ( bSkipFade ) + { + flFadeTimeScale = 0.0f; + } + + StartFade( flFadeTimeScale * ( m_bFadingIn ? m_flFadeInDuration : m_flFadeOutDuration ) ); + + // update the clientside weight once here, in case the fade duration is 0 + m_flCurWeight = Lerp( GetFadeRatio(), m_flFadeStartWeight, m_bFadingIn ? m_flMaxWeight : 0.0f ); +} + +Vector C_ColorCorrection::GetOrigin() +{ + return m_vecOrigin; +} + +float C_ColorCorrection::GetMinFalloff() +{ + return m_minFalloff; +} + +float C_ColorCorrection::GetMaxFalloff() +{ + return m_maxFalloff; +} + +void C_ColorCorrection::SetWeight( float fWeight ) +{ + g_pColorCorrectionMgr->SetColorCorrectionWeight( m_CCHandle, fWeight, false ); +} + +void C_ColorCorrection::StartFade( float flDuration ) +{ + m_flFadeStartTime = gpGlobals->curtime; + m_flFadeDuration = MAX( flDuration, 0.0f ); +} + +float C_ColorCorrection::GetFadeRatio() const +{ + float flRatio = 1.0f; + + if ( m_flFadeDuration != 0.0f ) + { + flRatio = ( gpGlobals->curtime - m_flFadeStartTime ) / m_flFadeDuration; + flRatio = clamp( flRatio, 0.0f, 1.0f ); + } + return flRatio; +} + +bool C_ColorCorrection::IsFadeTimeElapsed() const +{ + return ( ( gpGlobals->curtime - m_flFadeStartTime ) > m_flFadeDuration ) || + ( ( gpGlobals->curtime - m_flFadeStartTime ) < 0.0f ); +} + +void UpdateColorCorrectionEntities( C_BasePlayer *pPlayer, float ccScale, C_ColorCorrection **pList, int listCount ) +{ + for ( int i = 0; i < listCount; i++ ) + { + pList[i]->Update(pPlayer, ccScale); + } +} +#else void C_ColorCorrection::ClientThink() { if ( m_CCHandle == INVALID_CLIENT_CCHANDLE ) @@ -141,6 +286,7 @@ void C_ColorCorrection::ClientThink() BaseClass::ClientThink(); } +#endif diff --git a/sp/src/game/client/c_colorcorrection.h b/sp/src/game/client/c_colorcorrection.h new file mode 100644 index 0000000000..63149a0a02 --- /dev/null +++ b/sp/src/game/client/c_colorcorrection.h @@ -0,0 +1,88 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Note that this header exists in the Alien Swarm SDK, but not in stock Source SDK 2013. +// Although technically a new Mapbase file, it only serves to move otherwise identical code, +// so most code and repo conventions will pretend it was always there. +// +// -------------------------------------------------------------------- +// +// Purpose: Color correction entity with simple radial falloff +// +//=============================================================================// + +#ifndef C_COLORCORRECTION_H +#define C_COLORCORRECTION_H +#ifdef _WIN32 +#pragma once +#endif + +#include "colorcorrectionmgr.h" + +//------------------------------------------------------------------------------ +// Purpose : Color correction entity with radial falloff +//------------------------------------------------------------------------------ +class C_ColorCorrection : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_ColorCorrection, C_BaseEntity ); + + DECLARE_CLIENTCLASS(); + + C_ColorCorrection(); + virtual ~C_ColorCorrection(); + + void OnDataChanged(DataUpdateType_t updateType); + bool ShouldDraw(); + +#ifdef MAPBASE // From Alien Swarm SDK + virtual void Update(C_BasePlayer *pPlayer, float ccScale); + + bool IsMaster() const { return m_bMaster; } + bool IsClientSide() const; + bool IsExclusive() const { return m_bExclusive; } + + void EnableOnClient( bool bEnable, bool bSkipFade = false ); + + Vector GetOrigin(); + float GetMinFalloff(); + float GetMaxFalloff(); + + void SetWeight( float fWeight ); + +protected: + void StartFade( float flDuration ); + float GetFadeRatio() const; + bool IsFadeTimeElapsed() const; +#else + void ClientThink(); + +private: +#endif + Vector m_vecOrigin; + + float m_minFalloff; + float m_maxFalloff; + float m_flCurWeight; + char m_netLookupFilename[MAX_PATH]; + + bool m_bEnabled; + +#ifdef MAPBASE // From Alien Swarm SDK + float m_flFadeInDuration; + float m_flFadeOutDuration; + float m_flMaxWeight; + + bool m_bMaster; + bool m_bClientSide; + bool m_bExclusive; + + bool m_bFadingIn; + float m_flFadeStartWeight; + float m_flFadeStartTime; + float m_flFadeDuration; +#endif + + ClientCCHandle_t m_CCHandle; +}; + +#endif diff --git a/sp/src/game/client/c_colorcorrectionvolume.cpp b/sp/src/game/client/c_colorcorrectionvolume.cpp index 4bbcea9472..f7e337087e 100644 --- a/sp/src/game/client/c_colorcorrectionvolume.cpp +++ b/sp/src/game/client/c_colorcorrectionvolume.cpp @@ -36,9 +36,26 @@ class C_ColorCorrectionVolume : public C_BaseEntity void OnDataChanged(DataUpdateType_t updateType); bool ShouldDraw(); +#ifdef MAPBASE // From Alien Swarm SDK + void Update( C_BasePlayer *pPlayer, float ccScale ); + + void StartTouch( C_BaseEntity *pOther ); + void EndTouch( C_BaseEntity *pOther ); +#else void ClientThink(); +#endif private: +#ifdef MAPBASE // From Alien Swarm SDK + float m_LastEnterWeight; + float m_LastEnterTime; + + float m_LastExitWeight; + float m_LastExitTime; + bool m_bEnabled; + float m_MaxWeight; + float m_FadeDuration; +#endif float m_Weight; char m_lookupFilename[MAX_PATH]; @@ -46,6 +63,11 @@ class C_ColorCorrectionVolume : public C_BaseEntity }; IMPLEMENT_CLIENTCLASS_DT(C_ColorCorrectionVolume, DT_ColorCorrectionVolume, CColorCorrectionVolume) +#ifdef MAPBASE // From Alien Swarm SDK + RecvPropBool( RECVINFO( m_bEnabled ) ), + RecvPropFloat( RECVINFO( m_MaxWeight ) ), + RecvPropFloat( RECVINFO( m_FadeDuration ) ), +#endif RecvPropFloat( RECVINFO(m_Weight) ), RecvPropString( RECVINFO(m_lookupFilename) ), END_RECV_TABLE() @@ -82,11 +104,21 @@ void C_ColorCorrectionVolume::OnDataChanged(DataUpdateType_t updateType) { if ( m_CCHandle == INVALID_CLIENT_CCHANDLE ) { +#ifdef MAPBASE // From Alien Swarm SDK + // forming a unique name without extension + char cleanName[MAX_PATH]; + V_StripExtension( m_lookupFilename, cleanName, sizeof( cleanName ) ); + char name[MAX_PATH]; + Q_snprintf( name, MAX_PATH, "%s_%d", cleanName, entindex() ); + + m_CCHandle = g_pColorCorrectionMgr->AddColorCorrectionVolume( this, name, m_lookupFilename ); +#else char filename[MAX_PATH]; Q_strncpy( filename, m_lookupFilename, MAX_PATH ); m_CCHandle = g_pColorCorrectionMgr->AddColorCorrection( filename ); SetNextClientThink( ( m_CCHandle != INVALID_CLIENT_CCHANDLE ) ? CLIENT_THINK_ALWAYS : CLIENT_THINK_NEVER ); +#endif } } } @@ -99,11 +131,95 @@ bool C_ColorCorrectionVolume::ShouldDraw() return false; } +#ifdef MAPBASE // From Alien Swarm SDK +//-------------------------------------------------------------------------------------------------------- +void C_ColorCorrectionVolume::StartTouch( CBaseEntity *pEntity ) +{ + m_LastEnterTime = gpGlobals->curtime; + m_LastEnterWeight = m_Weight; +} + + +//-------------------------------------------------------------------------------------------------------- +void C_ColorCorrectionVolume::EndTouch( CBaseEntity *pEntity ) +{ + m_LastExitTime = gpGlobals->curtime; + m_LastExitWeight = m_Weight; +} + + +void C_ColorCorrectionVolume::Update( C_BasePlayer *pPlayer, float ccScale ) +{ + if ( pPlayer ) + { + bool isTouching = CollisionProp()->IsPointInBounds( pPlayer->EyePosition() ); + bool wasTouching = m_LastEnterTime > m_LastExitTime; + + if ( isTouching && !wasTouching ) + { + StartTouch( pPlayer ); + } + else if ( !isTouching && wasTouching ) + { + EndTouch( pPlayer ); + } + } + + if( !m_bEnabled ) + { + m_Weight = 0.0f; + } + else + { + if( m_LastEnterTime > m_LastExitTime ) + { + // we most recently entered the volume + + if( m_Weight < 1.0f ) + { + float dt = gpGlobals->curtime - m_LastEnterTime; + float weight = m_LastEnterWeight + dt / ((1.0f-m_LastEnterWeight)*m_FadeDuration); + if( weight>1.0f ) + weight = 1.0f; + + m_Weight = weight; + } + } + else + { + // we most recently exitted the volume + + if( m_Weight > 0.0f ) + { + float dt = gpGlobals->curtime - m_LastExitTime; + float weight = (1.0f-m_LastExitWeight) + dt / (m_LastExitWeight*m_FadeDuration); + if( weight>1.0f ) + weight = 1.0f; + + m_Weight = 1.0f - weight; + } + } + } + + // Vector entityPosition = GetAbsOrigin(); + g_pColorCorrectionMgr->SetColorCorrectionWeight( m_CCHandle, m_Weight * ccScale ); +} + + +void UpdateColorCorrectionVolumes( C_BasePlayer *pPlayer, float ccScale, C_ColorCorrectionVolume **pList, int listCount ) +{ + for ( int i = 0; i < listCount; i++ ) + { + pList[i]->Update(pPlayer, ccScale); + } +} +#else void C_ColorCorrectionVolume::ClientThink() { Vector entityPosition = GetAbsOrigin(); g_pColorCorrectionMgr->SetColorCorrectionWeight( m_CCHandle, m_Weight ); } +#endif diff --git a/sp/src/game/client/c_effects.cpp b/sp/src/game/client/c_effects.cpp index afd5bcf995..a273cb286b 100644 --- a/sp/src/game/client/c_effects.cpp +++ b/sp/src/game/client/c_effects.cpp @@ -40,7 +40,10 @@ ConVar r_RainSplashPercentage( "r_RainSplashPercentage", "20", FCVAR_CHEAT ); // ConVar r_RainParticleDensity( "r_RainParticleDensity", "1", FCVAR_NONE, "Density of Particle Rain 0-1" ); #ifdef MAPBASE -ConVar r_RainParticleClampOffset( "r_RainParticleClampOffset", "112", FCVAR_NONE, "How far inward or outward to extrude clamped precipitation particle systems" ); +ConVar r_RainParticleClampOffset_Rain( "r_RainParticleClampOffset_Rain", "120", FCVAR_NONE, "How far inward or outward to extrude clamped precipitation particle systems using the 'Particle Rain' type." ); +ConVar r_RainParticleClampOffset_Ash( "r_RainParticleClampOffset_Ash", "300", FCVAR_NONE, "How far inward or outward to extrude clamped precipitation particle systems using the 'Particle Ash' type." ); +ConVar r_RainParticleClampOffset_RainStorm( "r_RainParticleClampOffset_RainStorm", "112", FCVAR_NONE, "How far inward or outward to extrude clamped precipitation particle systems using the 'Particle Rain Storm' type." ); +ConVar r_RainParticleClampOffset_Snow( "r_RainParticleClampOffset_Snow", "300", FCVAR_NONE, "How far inward or outward to extrude clamped precipitation particle systems using the 'Particle Snow' type." ); ConVar r_RainParticleClampDebug( "r_RainParticleClampDebug", "0", FCVAR_NONE, "Enables debug code for precipitation particle system clamping" ); #endif @@ -951,6 +954,70 @@ void CClient_Precipitation::CreateParticlePrecip( void ) UpdateParticlePrecip( pPlayer ); } +#ifdef MAPBASE +void CClient_Precipitation::ClampParticlePosition( Vector &vPlayerPos, Vector &vOffsetPos, Vector &vOffsetPosNear, Vector &vOffsetPosFar ) +{ + Vector mins, maxs; + modelinfo->GetModelBounds( GetModel(), mins, maxs ); + + // Account for precipitation height + maxs.z += 180; + + Vector vecOrigin; //= WorldSpaceCenter(); + VectorLerp( mins, maxs, 0.5f, vecOrigin ); + + maxs -= vecOrigin; + mins -= vecOrigin; + + //float flMax = r_RainParticleClampOffset.GetFloat(); + float flMax = 0; + switch (m_nPrecipType) + { + case PRECIPITATION_TYPE_PARTICLERAIN: + flMax = r_RainParticleClampOffset_Rain.GetFloat(); + break; + + case PRECIPITATION_TYPE_PARTICLEASH: + flMax = r_RainParticleClampOffset_Ash.GetFloat(); + break; + + case PRECIPITATION_TYPE_PARTICLERAINSTORM: + flMax = r_RainParticleClampOffset_RainStorm.GetFloat(); + break; + + case PRECIPITATION_TYPE_PARTICLESNOW: + flMax = r_RainParticleClampOffset_Snow.GetFloat(); + break; + } + + Vector addend( flMax, flMax, 0 ); + mins += addend; + maxs -= addend; + + if (flMax > 0) + { + // Unless this is extruding outwards, make sure the offset isn't inverting the bounds. + // This means precipitation triggers with bounds less than offset*2 will turn into a thin line + // and the involved precipitation will pretty much be spatial at all times, which is okay. + mins.x = clamp( mins.x, -FLT_MAX, -1 ); + mins.y = clamp( mins.y, -FLT_MAX, -1 ); + maxs.x = clamp( maxs.x, 1, FLT_MAX ); + maxs.y = clamp( maxs.y, 1, FLT_MAX ); + } + + if (r_RainParticleClampDebug.GetBool()) + debugoverlay->AddBoxOverlay( vecOrigin, mins, maxs, vec3_angle, 255, 0, 0, 128, 0.15f ); + + maxs += vecOrigin; + mins += vecOrigin; + + CalcClosestPointOnAABB( mins, maxs, vPlayerPos, vPlayerPos ); + CalcClosestPointOnAABB( mins, maxs, vOffsetPos, vOffsetPos ); + CalcClosestPointOnAABB( mins, maxs, vOffsetPosNear, vOffsetPosNear ); + CalcClosestPointOnAABB( mins, maxs, vOffsetPosFar, vOffsetPosFar ); +} +#endif + void CClient_Precipitation::UpdateParticlePrecip( C_BasePlayer *pPlayer ) { if ( !pPlayer ) @@ -980,44 +1047,7 @@ void CClient_Precipitation::UpdateParticlePrecip( C_BasePlayer *pPlayer ) #ifdef MAPBASE if (m_spawnflags & SF_PRECIP_PARTICLE_CLAMP) { - Vector mins, maxs; - modelinfo->GetModelBounds( GetModel(), mins, maxs ); - - // Account for precipitation height - maxs.z += 180; - - Vector vecOrigin; //= WorldSpaceCenter(); - VectorLerp( mins, maxs, 0.5f, vecOrigin ); - - maxs -= vecOrigin; - mins -= vecOrigin; - - float flMax = r_RainParticleClampOffset.GetFloat(); - Vector addend( flMax, flMax, 0 ); - mins += addend; - maxs -= addend; - - if (flMax > 0) - { - // Unless this is extruding outwards, make sure the offset isn't inverting the bounds. - // This means precipitation triggers with bounds less than offset*2 will turn into a thin line - // and the involved precipitation will pretty much be spatial at all times, which is okay. - mins.x = clamp( mins.x, -FLT_MAX, -1 ); - mins.y = clamp( mins.y, -FLT_MAX, -1 ); - maxs.x = clamp( maxs.x, 1, FLT_MAX ); - maxs.y = clamp( maxs.y, 1, FLT_MAX ); - } - - if (r_RainParticleClampDebug.GetBool()) - debugoverlay->AddBoxOverlay( vecOrigin, mins, maxs, vec3_angle, 255, 0, 0, 128, 0.15f ); - - maxs += vecOrigin; - mins += vecOrigin; - - CalcClosestPointOnAABB( mins, maxs, vPlayerPos, vPlayerPos ); - CalcClosestPointOnAABB( mins, maxs, vOffsetPos, vOffsetPos ); - CalcClosestPointOnAABB( mins, maxs, vOffsetPosNear, vOffsetPosNear ); - CalcClosestPointOnAABB( mins, maxs, vOffsetPosFar, vOffsetPosFar ); + ClampParticlePosition( vPlayerPos, vOffsetPos, vOffsetPosNear, vOffsetPosFar ); } #endif @@ -1236,6 +1266,13 @@ void CClient_Precipitation::DispatchInnerParticlePrecip( C_BasePlayer *pPlayer, Vector vOffsetPosFar = vPlayerPos + Vector ( 0, 0, 180 ) + ( vForward * m_flParticleInnerDist ); // 100.0 Vector vDensity = Vector( r_RainParticleDensity.GetFloat(), 0, 0 ) * m_flDensity; +#ifdef MAPBASE + if (m_spawnflags & SF_PRECIP_PARTICLE_CLAMP) + { + ClampParticlePosition( vPlayerPos, vOffsetPos, vOffsetPosNear, vOffsetPosFar ); + } +#endif + #ifdef MAPBASE if (!(m_spawnflags & SF_PRECIP_PARTICLE_NO_OUTER)) #endif diff --git a/sp/src/game/client/c_effects.h b/sp/src/game/client/c_effects.h index 6012dff724..5f7e02a58f 100644 --- a/sp/src/game/client/c_effects.h +++ b/sp/src/game/client/c_effects.h @@ -130,6 +130,10 @@ friend class CClient_Precipitation::CPrecipitationEffect; void CreateAshParticle( void ); void CreateRainOrSnowParticle( Vector vSpawnPosition, Vector vVelocity ); +#ifdef MAPBASE + void ClampParticlePosition( Vector &vPlayerPos, Vector &vOffsetPos, Vector &vOffsetPosNear, Vector &vOffsetPosFar ); +#endif + // Information helpful in creating and rendering particles IMaterial *m_MatHandle; // material used diff --git a/sp/src/game/client/c_env_global_light.cpp b/sp/src/game/client/c_env_global_light.cpp index c48803aefa..b143a79dd0 100644 --- a/sp/src/game/client/c_env_global_light.cpp +++ b/sp/src/game/client/c_env_global_light.cpp @@ -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 diff --git a/sp/src/game/client/c_env_projectedtexture.cpp b/sp/src/game/client/c_env_projectedtexture.cpp index c78cddb2bc..7bd15a1742 100644 --- a/sp/src/game/client/c_env_projectedtexture.cpp +++ b/sp/src/game/client/c_env_projectedtexture.cpp @@ -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; @@ -283,6 +283,8 @@ void C_EnvProjectedTexture::UpdateLight( void ) // VectorNormalize( vRight ); // VectorNormalize( vUp ); + + VectorVectors( vForward, vRight, vUp ); } } else diff --git a/sp/src/game/client/c_env_screenoverlay.cpp b/sp/src/game/client/c_env_screenoverlay.cpp index 5496d45303..a31258829d 100644 --- a/sp/src/game/client/c_env_screenoverlay.cpp +++ b/sp/src/game/client/c_env_screenoverlay.cpp @@ -48,6 +48,9 @@ class C_EnvScreenOverlay : public C_BaseEntity int m_iCachedDesiredOverlay; int m_iCurrentOverlay; float m_flCurrentOverlayTime; +#ifdef MAPBASE + int m_iOverlayIndex; +#endif }; IMPLEMENT_CLIENTCLASS_DT( C_EnvScreenOverlay, DT_EnvScreenOverlay, CEnvScreenOverlay ) @@ -56,6 +59,9 @@ IMPLEMENT_CLIENTCLASS_DT( C_EnvScreenOverlay, DT_EnvScreenOverlay, CEnvScreenOve RecvPropFloat( RECVINFO( m_flStartTime ) ), RecvPropInt( RECVINFO( m_iDesiredOverlay ) ), RecvPropBool( RECVINFO( m_bIsActive ) ), +#ifdef MAPBASE + RecvPropInt( RECVINFO( m_iOverlayIndex ) ), +#endif END_RECV_TABLE() //----------------------------------------------------------------------------- @@ -77,7 +83,11 @@ void C_EnvScreenOverlay::PostDataUpdate( DataUpdateType_t updateType ) BaseClass::PostDataUpdate( updateType ); // If we have a start time now, start the overlays going +#ifdef MAPBASE + if ( m_bIsActive && m_flStartTime > 0 && (view->GetScreenOverlayMaterial() == NULL || (m_iOverlayIndex != -1 && view->GetIndexedScreenOverlayMaterial(m_iOverlayIndex) == NULL)) ) +#else if ( m_bIsActive && m_flStartTime > 0 && view->GetScreenOverlayMaterial() == NULL ) +#endif { StartOverlays(); } @@ -111,7 +121,16 @@ void C_EnvScreenOverlay::StopOverlays( void ) if ( m_bWasActive && !m_bIsActive ) { - view->SetScreenOverlayMaterial( NULL ); +#ifdef MAPBASE + if (m_iOverlayIndex != -1) + { + view->SetIndexedScreenOverlayMaterial( m_iOverlayIndex, NULL ); + } + else +#endif + { + view->SetScreenOverlayMaterial( NULL ); + } } } @@ -163,7 +182,16 @@ void C_EnvScreenOverlay::StartCurrentOverlay( void ) IMaterial *pMaterial = materials->FindMaterial( m_iszOverlayNames[m_iCurrentOverlay], TEXTURE_GROUP_CLIENT_EFFECTS, false ); if ( !IsErrorMaterial( pMaterial ) ) { - view->SetScreenOverlayMaterial( pMaterial ); +#ifdef MAPBASE + if (m_iOverlayIndex != -1) + { + view->SetIndexedScreenOverlayMaterial( m_iOverlayIndex, pMaterial ); + } + else +#endif + { + view->SetScreenOverlayMaterial( pMaterial ); + } } else { diff --git a/sp/src/game/client/c_gameinstructor.h b/sp/src/game/client/c_gameinstructor.h index 00c97c66e4..14ae908caa 100644 --- a/sp/src/game/client/c_gameinstructor.h +++ b/sp/src/game/client/c_gameinstructor.h @@ -9,7 +9,7 @@ #include "GameEventListener.h" -#include "vgui_controls/phandle.h" +#include "vgui_controls/PHandle.h" class CBaseLesson; diff --git a/sp/src/game/client/c_movie_display.cpp b/sp/src/game/client/c_movie_display.cpp index 2285b97ed8..2732740390 100644 --- a/sp/src/game/client/c_movie_display.cpp +++ b/sp/src/game/client/c_movie_display.cpp @@ -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() diff --git a/sp/src/game/client/c_movie_display.h b/sp/src/game/client/c_movie_display.h index d133e82e40..55d0211fd3 100644 --- a/sp/src/game/client/c_movie_display.h +++ b/sp/src/game/client/c_movie_display.h @@ -20,6 +20,7 @@ class C_MovieDisplay : public C_BaseEntity 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 @@ class C_MovieDisplay : public C_BaseEntity private: bool m_bEnabled; bool m_bLooping; + bool m_bMuted; char m_szMovieFilename[128]; char m_szGroupName[128]; }; diff --git a/sp/src/game/client/c_point_commentary_node.cpp b/sp/src/game/client/c_point_commentary_node.cpp index 47ea96efb0..3314330bd3 100644 --- a/sp/src/game/client/c_point_commentary_node.cpp +++ b/sp/src/game/client/c_point_commentary_node.cpp @@ -18,6 +18,15 @@ #include "convar.h" #include "hud_closecaption.h" #include "in_buttons.h" +#ifdef MAPBASE +#include "vgui_controls/Label.h" +#include "vgui_controls/ImagePanel.h" +#include "vgui_controls/AnimationController.h" +#include "filesystem.h" +#include "scenefilecache/ISceneFileCache.h" +#include "choreoscene.h" +#include "c_sceneentity.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -37,6 +46,15 @@ bool IsInCommentaryMode( void ) static bool g_bTracingVsCommentaryNodes = false; +#ifdef MAPBASE +ConVar commentary_type_force( "commentary_type_force", "-1", FCVAR_NONE, "Forces all commentary nodes to use the specified type." ); +ConVar commentary_type_text_endtime( "commentary_type_text_endtime", "120" ); +ConVar commentary_type_image_endtime( "commentary_type_image_endtime", "120" ); +ConVar commentary_audio_element_below_cc( "commentary_audio_element_below_cc", "1", FCVAR_NONE, "Allows commentary audio elements to display even when CC is enabled (although this is done by inverting their Y axis)" ); +ConVar commentary_audio_element_below_cc_margin( "commentary_audio_element_below_cc_margin", "4" ); +ConVar commentary_combine_speaker_and_printname( "commentary_combine_speaker_and_printname", "1" ); +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -52,12 +70,26 @@ class CHudCommentary : public CHudElement, public vgui::Panel virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); void StartCommentary( C_PointCommentaryNode *pNode, char *pszSpeakers, int iNode, int iNodeMax, float flStartTime, float flEndTime ); +#ifdef MAPBASE + void StartTextCommentary( C_PointCommentaryNode *pNode, const char *pszText, char *pszSpeakers, int iNode, int iNodeMax, float flStartTime, float flEndTime ); + void StartImageCommentary( C_PointCommentaryNode *pNode, const char *pszImage, char *pszSpeakers, int iNode, int iNodeMax, float flStartTime, float flEndTime ); + void StartSceneCommentary( C_PointCommentaryNode *pNode, char *pszSpeakers, int iNode, int iNodeMax, float flStartTime, float flEndTime ); +#endif void StopCommentary( void ); bool IsTheActiveNode( C_PointCommentaryNode *pNode ) { return (pNode == m_hActiveNode); } +#ifdef MAPBASE + void CombineSpeakerAndPrintName( const char *pszPrintName ); + void RepositionCloseCaption(); +#endif + // vgui overrides virtual void Paint( void ); virtual bool ShouldDraw( void ); +#ifdef MAPBASE + virtual void PerformLayout(); + void ResolveBounds( int width, int height ); +#endif private: CHandle m_hActiveNode; @@ -68,6 +100,19 @@ class CHudCommentary : public CHudElement, public vgui::Panel wchar_t m_szCount[MAX_COUNT_STRING]; CMaterialReference m_matIcon; bool m_bHiding; +#ifdef MAPBASE + int m_iCommentaryType; + float m_flPanelScale; + float m_flOverrideX; + float m_flOverrideY; + + vgui::Label *m_pLabel; + vgui::ImagePanel *m_pImage; + vgui::HFont m_hFont; + + // HACKHACK: Needed as a failsafe to prevent desync + int m_iCCDefaultY; +#endif // Painting CPanelAnimationVarAliasType( int, m_iBarX, "bar_xpos", "8", "proportional_int" ); @@ -84,24 +129,65 @@ class CHudCommentary : public CHudElement, public vgui::Panel CPanelAnimationVarAliasType( int, m_iIconTall, "icon_height", "8", "proportional_int" ); CPanelAnimationVarAliasType( int, m_nIconTextureId, "icon_texture", "vgui/hud/icon_commentary", "textureid" ); +#ifdef MAPBASE + CPanelAnimationVarAliasType( int, m_iTypeAudioX, "type_audio_xpos", "190", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTypeAudioY, "type_audio_ypos", "350", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTypeAudioW, "type_audio_wide", "380", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTypeAudioT, "type_audio_tall", "40", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTypeTextX, "type_text_xpos", "180", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTypeTextY, "type_text_ypos", "150", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTypeTextW, "type_text_wide", "400", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTypeTextT, "type_text_tall", "200", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTypeTextCountXFR, "type_text_count_xpos_from_right", "10", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTypeTextCountYFB, "type_text_count_ypos_from_bottom", "10", "proportional_int" ); + CPanelAnimationVar( Color, m_TextBackgroundColor, "BackgroundColorTextContent", "0 0 0 224" ); + CPanelAnimationVar( Color, m_TypeTextContentColor, "TextContentColor", "255 230 180 255" ); + CPanelAnimationVar( int, m_iTextBorderSpace, "type_text_border_space", "8" ); +#endif + CPanelAnimationVar( bool, m_bUseScriptBGColor, "use_script_bgcolor", "0" ); +#ifdef MAPBASE + CPanelAnimationVar( Color, m_BackgroundColor, "BackgroundColor", "Panel.BgColor" ); + CPanelAnimationVar( Color, m_ForegroundColor, "ForegroundColor", "255 170 0 255" ); +#else CPanelAnimationVar( Color, m_BackgroundColor, "BackgroundColor", "0 0 0 0" ); +#endif CPanelAnimationVar( Color, m_BGOverrideColor, "BackgroundOverrideColor", "Panel.BgColor" ); }; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -class C_PointCommentaryNode : public C_BaseAnimating +class C_PointCommentaryNode : public C_BaseAnimating, public IChoreoEventCallback { DECLARE_CLASS( C_PointCommentaryNode, C_BaseAnimating ); public: DECLARE_CLIENTCLASS(); DECLARE_DATADESC(); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); +#endif virtual void OnPreDataChanged( DataUpdateType_t type ); virtual void OnDataChanged( DataUpdateType_t type ); + void StartAudioCommentary( const char *pszCommentaryFile, C_BasePlayer *pPlayer ); +#ifdef MAPBASE + void StartTextCommentary( const char *pszCommentaryFile, C_BasePlayer *pPlayer ); + void StartImageCommentary( const char *pszCommentaryFile, C_BasePlayer *pPlayer ); + void StartSceneCommentary( const char *pszCommentaryFile, C_BasePlayer *pPlayer ); + + // From IChoreoEventCallback + virtual void StartEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ); +#else + virtual void StartEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) {} +#endif + virtual void EndEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) {} + virtual void ProcessEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) {} + virtual bool CheckEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) { return true; } + + void ClientThink(); + void OnRestore( void ) { BaseClass::OnRestore(); @@ -149,6 +235,11 @@ class C_PointCommentaryNode : public C_BaseAnimating { int iRenderGroup = gHUD.LookupRenderGroupIndexByName( "commentary" ); gHUD.LockRenderGroup( iRenderGroup ); + +#ifdef MAPBASE + // Special commentary localization file (useful for things like text nodes or print names) + g_pVGuiLocalize->AddFile( "resource/commentary_%language%.txt" ); +#endif } if ( g_CommentaryNodes.Find(this) == g_CommentaryNodes.InvalidIndex() ) @@ -168,6 +259,20 @@ class C_PointCommentaryNode : public C_BaseAnimating } } +#ifdef MAPBASE_VSCRIPT // VScript funcs + bool IsActive() { return m_bActive; } + + int GetCommentaryType() { return m_iCommentaryType; } + void SetCommentaryType( int iType ) { m_iCommentaryType = iType; } + + const char *GetCommentaryFile() { return m_iszCommentaryFile; } + void SetCommentaryFile( const char *pszNewFile ) { Q_strncpy( m_iszCommentaryFile, pszNewFile, sizeof( m_iszCommentaryFile ) ); } + const char *GetSpeakers() { return m_iszSpeakers; } + void SetSpeakers( const char *pszSpeakers ) { Q_strncpy( m_iszSpeakers, pszSpeakers, sizeof( m_iszSpeakers ) ); } + const char *GetPrintName() { return m_iszPrintName; } + void SetPrintName( const char *pszPrintName ) { Q_strncpy( m_iszPrintName, pszPrintName, sizeof( m_iszPrintName ) ); } +#endif + public: // Data received from the server bool m_bActive; @@ -181,6 +286,21 @@ class C_PointCommentaryNode : public C_BaseAnimating CSoundPatch *m_sndCommentary; EHANDLE m_hViewPosition; bool m_bRestartAfterRestore; +#ifdef MAPBASE + char m_iszPrintName[MAX_SPEAKER_NAME]; + int m_iCommentaryType; + float m_flPanelScale; + float m_flPanelX; + float m_flPanelY; + + CChoreoScene *m_pScene; + //CHandle m_hScene; + EHANDLE m_hSceneOrigin; +#endif + +#ifdef MAPBASE_VSCRIPT + static ScriptHook_t g_Hook_PreStartCommentaryClient; +#endif }; IMPLEMENT_CLIENTCLASS_DT(C_PointCommentaryNode, DT_PointCommentaryNode, CPointCommentaryNode) @@ -192,6 +312,13 @@ IMPLEMENT_CLIENTCLASS_DT(C_PointCommentaryNode, DT_PointCommentaryNode, CPointCo RecvPropInt( RECVINFO( m_iNodeNumber ) ), RecvPropInt( RECVINFO( m_iNodeNumberMax ) ), RecvPropEHandle( RECVINFO(m_hViewPosition) ), +#ifdef MAPBASE + RecvPropString( RECVINFO( m_iszPrintName ) ), + RecvPropInt( RECVINFO( m_iCommentaryType ) ), + RecvPropFloat( RECVINFO( m_flPanelScale ) ), + RecvPropFloat( RECVINFO( m_flPanelX ) ), + RecvPropFloat( RECVINFO( m_flPanelY ) ), +#endif END_RECV_TABLE() BEGIN_DATADESC( C_PointCommentaryNode ) @@ -200,6 +327,26 @@ BEGIN_DATADESC( C_PointCommentaryNode ) DEFINE_SOUNDPATCH( m_sndCommentary ), END_DATADESC() +#ifdef MAPBASE_VSCRIPT +ScriptHook_t C_PointCommentaryNode::g_Hook_PreStartCommentaryClient; + +BEGIN_ENT_SCRIPTDESC( C_PointCommentaryNode, C_BaseAnimating, "Commentary nodes which play commentary in commentary mode." ) + + DEFINE_SCRIPTFUNC( IsActive, "" ) + DEFINE_SCRIPTFUNC( GetCommentaryFile, "" ) + DEFINE_SCRIPTFUNC( SetCommentaryFile, "" ) + DEFINE_SCRIPTFUNC( GetSpeakers, "" ) + DEFINE_SCRIPTFUNC( SetSpeakers, "" ) + DEFINE_SCRIPTFUNC( GetPrintName, "" ) + DEFINE_SCRIPTFUNC( SetPrintName, "" ) + DEFINE_SCRIPTFUNC( GetCommentaryType, "" ) + DEFINE_SCRIPTFUNC( SetCommentaryType, "" ) + + DEFINE_SIMPLE_SCRIPTHOOK( C_PointCommentaryNode::g_Hook_PreStartCommentaryClient, "PreStartCommentaryClient", FIELD_BOOLEAN, "Called just before commentary begins on the client. Use this to modify variables or commentary behavior before it begins. Returning false will prevent the commentary from starting." ) + +END_SCRIPTDESC(); +#endif + //----------------------------------------------------------------------------- // Purpose: @@ -229,6 +376,22 @@ void C_PointCommentaryNode::OnDataChanged( DataUpdateType_t updateType ) C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); if ( m_bActive && pPlayer ) { +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_PreStartCommentaryClient.CanRunInScope( m_ScriptScope )) + { + ScriptVariant_t functionReturn; + if (g_Hook_PreStartCommentaryClient.Call( m_ScriptScope, &functionReturn, NULL ) && functionReturn.m_type == FIELD_BOOLEAN) + { + // Don't play the commentary if it returned false + if (functionReturn.m_bool == false) + { + engine->ServerCmd( "commentary_finishnode\n" ); + return; + } + } + } +#endif + // Use the HDR / Non-HDR version based on whether we're running HDR or not char *pszCommentaryFile; if ( g_pMaterialSystemHardwareConfig->GetHDRType() == HDR_TYPE_NONE && m_iszCommentaryFileNoHDR && m_iszCommentaryFileNoHDR[0] ) @@ -245,71 +408,386 @@ void C_PointCommentaryNode::OnDataChanged( DataUpdateType_t updateType ) return; } - EmitSound_t es; - es.m_nChannel = CHAN_STATIC; - es.m_pSoundName = pszCommentaryFile; - es.m_SoundLevel = SNDLVL_GUNFIRE; - es.m_nFlags = SND_SHOULDPAUSE; +#ifdef MAPBASE + int iCommentaryType = m_iCommentaryType; + if (commentary_type_force.GetInt() != -1) + iCommentaryType = commentary_type_force.GetInt(); - CBaseEntity *pSoundEntity; - if ( m_hViewPosition ) + switch (iCommentaryType) { - pSoundEntity = m_hViewPosition; + case COMMENTARY_TYPE_TEXT: + StartTextCommentary( pszCommentaryFile, pPlayer ); + break; + + case COMMENTARY_TYPE_IMAGE: + StartImageCommentary( pszCommentaryFile, pPlayer ); + break; + + case COMMENTARY_TYPE_SCENE: + StartSceneCommentary( pszCommentaryFile, pPlayer ); + break; + + default: + case COMMENTARY_TYPE_AUDIO: + StartAudioCommentary( pszCommentaryFile, pPlayer ); + break; } - else if ( render->GetViewEntity() ) +#else + StartAudioCommentary( pszCommentaryFile, pPlayer ); +#endif + } + else if ( m_bWasActive ) + { + StopLoopingSounds(); + + CHudCommentary *pHudCommentary = (CHudCommentary *)GET_HUDELEMENT( CHudCommentary ); + if ( pHudCommentary->IsTheActiveNode(this) ) { - pSoundEntity = cl_entitylist->GetEnt( render->GetViewEntity() ); - es.m_SoundLevel = SNDLVL_NONE; + pHudCommentary->StopCommentary(); } - else + } + + m_bRestartAfterRestore = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PointCommentaryNode::StartAudioCommentary( const char *pszCommentaryFile, C_BasePlayer *pPlayer ) +{ + EmitSound_t es; + es.m_nChannel = CHAN_STATIC; + es.m_pSoundName = pszCommentaryFile; + es.m_SoundLevel = SNDLVL_GUNFIRE; + es.m_nFlags = SND_SHOULDPAUSE; + + CBaseEntity *pSoundEntity; + if ( m_hViewPosition ) + { + pSoundEntity = m_hViewPosition; + } + else if ( render->GetViewEntity() ) + { + pSoundEntity = cl_entitylist->GetEnt( render->GetViewEntity() ); + es.m_SoundLevel = SNDLVL_NONE; + } + else + { + pSoundEntity = pPlayer; + } + CSingleUserRecipientFilter filter( pPlayer ); + m_sndCommentary = (CSoundEnvelopeController::GetController()).SoundCreate( filter, pSoundEntity->entindex(), es ); + if ( m_sndCommentary ) + { + (CSoundEnvelopeController::GetController()).SoundSetCloseCaptionDuration( m_sndCommentary, -1 ); + (CSoundEnvelopeController::GetController()).Play( m_sndCommentary, 1.0f, 100, m_flStartTime ); + } + + // Get the duration so we know when it finishes + float flDuration = enginesound->GetSoundDuration( STRING( CSoundEnvelopeController::GetController().SoundGetName( m_sndCommentary ) ) ) ; + bool bSubtitlesEnabled = false; + + CHudCloseCaption *pHudCloseCaption = (CHudCloseCaption *)GET_HUDELEMENT( CHudCloseCaption ); + if ( pHudCloseCaption ) + { + // This is where we play the commentary close caption (and lock the other captions out). + // Also, if close captions are off we force a caption in non-English + if ( closecaption.GetBool() || ( !closecaption.GetBool() && !english.GetBool() ) ) { - pSoundEntity = pPlayer; + // Clear the close caption element in preparation + pHudCloseCaption->Reset(); + + // Process the commentary caption + pHudCloseCaption->ProcessCaptionDirect( pszCommentaryFile, flDuration ); + + // Find the close caption hud element & lock it + pHudCloseCaption->Lock(); + + bSubtitlesEnabled = true; } - CSingleUserRecipientFilter filter( pPlayer ); - m_sndCommentary = (CSoundEnvelopeController::GetController()).SoundCreate( filter, pSoundEntity->entindex(), es ); - if ( m_sndCommentary ) + } + + char *pszSpeakers = m_iszSpeakers; + + // Tell the HUD element + CHudCommentary *pHudCommentary = (CHudCommentary *)GET_HUDELEMENT( CHudCommentary ); + pHudCommentary->StartCommentary( this, pszSpeakers, m_iNodeNumber, m_iNodeNumberMax, m_flStartTime, m_flStartTime + flDuration ); +} + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PointCommentaryNode::StartTextCommentary( const char *pszCommentaryFile, C_BasePlayer *pPlayer ) +{ + // Get the duration so we know when it finishes + //float flDuration = enginesound->GetSoundDuration( STRING( CSoundEnvelopeController::GetController().SoundGetName( m_sndCommentary ) ) ) ; + + // TODO: Determine from text length? + float flDuration = commentary_type_text_endtime.GetFloat(); + + // Tell the HUD element + CHudCommentary *pHudCommentary = (CHudCommentary *)GET_HUDELEMENT( CHudCommentary ); + pHudCommentary->StartTextCommentary( this, pszCommentaryFile, m_iszSpeakers, m_iNodeNumber, m_iNodeNumberMax, m_flStartTime, m_flStartTime + flDuration ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PointCommentaryNode::StartImageCommentary( const char *pszCommentaryFile, C_BasePlayer *pPlayer ) +{ + // Get the duration so we know when it finishes + //float flDuration = enginesound->GetSoundDuration( STRING( CSoundEnvelopeController::GetController().SoundGetName( m_sndCommentary ) ) ) ; + + float flDuration = commentary_type_image_endtime.GetFloat(); + + // Tell the HUD element + CHudCommentary *pHudCommentary = (CHudCommentary *)GET_HUDELEMENT( CHudCommentary ); + pHudCommentary->StartImageCommentary( this, pszCommentaryFile, m_iszSpeakers, m_iNodeNumber, m_iNodeNumberMax, m_flStartTime, m_flStartTime + flDuration ); +} + +extern CChoreoStringPool g_ChoreoStringPool; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PointCommentaryNode::StartSceneCommentary( const char *pszCommentaryFile, C_BasePlayer *pPlayer ) +{ + char loadfile[MAX_PATH]; + Q_strncpy( loadfile, pszCommentaryFile, sizeof( loadfile ) ); + Q_SetExtension( loadfile, ".vcd", sizeof( loadfile ) ); + Q_FixSlashes( loadfile ); + + // + // Raw scene file support + // + void *pBuffer = 0; + size_t bufsize = scenefilecache->GetSceneBufferSize( loadfile ); + if ( bufsize > 0 ) + { + // Definitely in scenes.image + pBuffer = malloc( bufsize ); + if ( !scenefilecache->GetSceneData( pszCommentaryFile, (byte *)pBuffer, bufsize ) ) { - (CSoundEnvelopeController::GetController()).SoundSetCloseCaptionDuration( m_sndCommentary, -1 ); - (CSoundEnvelopeController::GetController()).Play( m_sndCommentary, 1.0f, 100, m_flStartTime ); + free( pBuffer ); } - // Get the duration so we know when it finishes - float flDuration = enginesound->GetSoundDuration( STRING( CSoundEnvelopeController::GetController().SoundGetName( m_sndCommentary ) ) ) ; - - CHudCloseCaption *pHudCloseCaption = (CHudCloseCaption *)GET_HUDELEMENT( CHudCloseCaption ); - if ( pHudCloseCaption ) + + if ( IsBufferBinaryVCD( (char*)pBuffer, bufsize ) ) { - // This is where we play the commentary close caption (and lock the other captions out). - // Also, if close captions are off we force a caption in non-English - if ( closecaption.GetBool() || ( !closecaption.GetBool() && !english.GetBool() ) ) + m_pScene = new CChoreoScene( NULL ); + CUtlBuffer buf( pBuffer, bufsize, CUtlBuffer::READ_ONLY ); + if ( !m_pScene->RestoreFromBinaryBuffer( buf, loadfile, &g_ChoreoStringPool ) ) { - // Clear the close caption element in preparation - pHudCloseCaption->Reset(); + Warning( "Unable to restore scene '%s'\n", loadfile ); + delete m_pScene; + m_pScene = NULL; + } + } + } + else if (filesystem->ReadFileEx( loadfile, "MOD", &pBuffer, true )) + { + // Not in scenes.image, but it's a raw file + g_TokenProcessor.SetBuffer((char*)pBuffer); + m_pScene = ChoreoLoadScene( loadfile, this, &g_TokenProcessor, Scene_Printf ); + } + + free( pBuffer ); + + if( m_pScene ) + { + m_pScene->SetPrintFunc( Scene_Printf ); + m_pScene->SetEventCallbackInterface( this ); + } + else + { + // Cancel commentary (TODO: clean up?) + return; + } - // Process the commentary caption - pHudCloseCaption->ProcessCaptionDirect( pszCommentaryFile, flDuration ); + int types[ 2 ]; + types[ 0 ] = CChoreoEvent::SPEAK; + //types[ 1 ] = CChoreoEvent::GENERIC; // TODO: Support for the game_text event? + m_pScene->RemoveEventsExceptTypes( types, 1 ); - // Find the close caption hud element & lock it - pHudCloseCaption->Lock(); + // Iterate events and precache necessary resources + for ( int i = 0; i < m_pScene->GetNumEvents(); i++ ) + { + CChoreoEvent *event = m_pScene->GetEvent( i ); + if ( !event ) + continue; + + // load any necessary data + switch (event->GetType() ) + { + default: + break; + case CChoreoEvent::SPEAK: + { + // Defined in SoundEmitterSystem.cpp + // NOTE: The script entries associated with .vcds are forced to preload to avoid + // loading hitches during triggering + CBaseEntity::PrecacheScriptSound( event->GetParameters() ); + + if ( event->GetCloseCaptionType() == CChoreoEvent::CC_MASTER && + event->GetNumSlaves() > 0 ) + { + char tok[ CChoreoEvent::MAX_CCTOKEN_STRING ]; + if ( event->GetPlaybackCloseCaptionToken( tok, sizeof( tok ) ) ) + { + CBaseEntity::PrecacheScriptSound( tok ); + } + } } + break; } + } + + PrecacheScriptSound( "AI_BaseNPC.SentenceStop" ); + + if ( m_hViewPosition ) + { + m_hSceneOrigin = m_hViewPosition; + } + else if ( render->GetViewEntity() ) + { + m_hSceneOrigin = cl_entitylist->GetEnt( render->GetViewEntity() ); + } + else + { + m_hSceneOrigin = pPlayer; + } - // Tell the HUD element - CHudCommentary *pHudCommentary = (CHudCommentary *)GET_HUDELEMENT( CHudCommentary ); - pHudCommentary->StartCommentary( this, m_iszSpeakers, m_iNodeNumber, m_iNodeNumberMax, m_flStartTime, m_flStartTime + flDuration ); + // Get the duration so we know when it finishes + float flDuration = m_pScene->GetDuration(); + + // Add a tiny amount of time at the end to ensure audio doesn't get cut off + flDuration += 0.5f; + + CHudCloseCaption *pHudCloseCaption = (CHudCloseCaption *)GET_HUDELEMENT( CHudCloseCaption ); + if ( pHudCloseCaption ) + { + // This is where we play the commentary close caption (and lock the other captions out). + // Also, if close captions are off we force a caption in non-English + if ( closecaption.GetBool() || ( !closecaption.GetBool() && !english.GetBool() ) ) + { + // Clear the close caption element in preparation + pHudCloseCaption->Reset(); + + // Find the close caption hud element & lock it + pHudCloseCaption->Lock(); + } } - else if ( m_bWasActive ) + + // Tell the HUD element + CHudCommentary *pHudCommentary = (CHudCommentary *)GET_HUDELEMENT( CHudCommentary ); + pHudCommentary->StartSceneCommentary( this, m_iszSpeakers, m_iNodeNumber, m_iNodeNumberMax, m_flStartTime, m_flStartTime + flDuration ); + + // Start thinking for the scene + SetNextClientThink( CLIENT_THINK_ALWAYS ); +} + +//----------------------------------------------------------------------------- +// Purpose: All events are leading edge triggered +// Input : currenttime - +// *event - +//----------------------------------------------------------------------------- +void C_PointCommentaryNode::StartEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) +{ + Assert( event ); + + if ( !Q_stricmp( event->GetName(), "NULL" ) ) + { + return; + } + + //Msg("Starting event \"%s\" (%s)\n", event->GetName(), event->GetParameters()); + + // load any necessary data + switch (event->GetType() ) { - StopLoopingSounds(); + default: + break; + case CChoreoEvent::SPEAK: + { + CSingleUserRecipientFilter filter( C_BasePlayer::GetLocalPlayer() ); - CHudCommentary *pHudCommentary = (CHudCommentary *)GET_HUDELEMENT( CHudCommentary ); - if ( pHudCommentary->IsTheActiveNode(this) ) + EmitSound_t es; + es.m_nChannel = CHAN_VOICE2; + es.m_flVolume = 1; + es.m_SoundLevel = SNDLVL_GUNFIRE; + //es.m_nFlags = SND_SHOULDPAUSE; + + es.m_bEmitCloseCaption = false; + es.m_pSoundName = event->GetParameters(); + + // Just in case + if (!m_hSceneOrigin) + m_hSceneOrigin = C_BasePlayer::GetLocalPlayer(); + + EmitSound( filter, m_hSceneOrigin->entindex(), es ); + + // Close captioning only on master token no matter what... + // Also, if close captions are off we force a caption in non-English + if ( event->GetCloseCaptionType() == CChoreoEvent::CC_MASTER && closecaption.GetBool() || ( !closecaption.GetBool() && !english.GetBool() ) ) + { + char tok[ CChoreoEvent::MAX_CCTOKEN_STRING ]; + bool validtoken = event->GetPlaybackCloseCaptionToken( tok, sizeof( tok ) ); + if ( validtoken ) + { + CRC32_t tokenCRC; + CRC32_Init( &tokenCRC ); + + char lowercase[ 256 ]; + Q_strncpy( lowercase, tok, sizeof( lowercase ) ); + Q_strlower( lowercase ); + + CRC32_ProcessBuffer( &tokenCRC, lowercase, Q_strlen( lowercase ) ); + CRC32_Final( &tokenCRC ); + + float endtime = event->GetLastSlaveEndTime(); + float durationShort = event->GetDuration(); + float durationLong = endtime - event->GetStartTime(); + float duration = MAX( durationShort, durationLong ); + + CHudCloseCaption *hudCloseCaption = GET_HUDELEMENT( CHudCloseCaption ); + if ( hudCloseCaption ) + { + hudCloseCaption->ProcessCaptionDirect( lowercase, duration ); + } + } + + } + } + break; + // TODO: Support for the game_text event? + /* + case CChoreoEvent::GENERIC: { - pHudCommentary->StopCommentary(); + } + break; + */ } - m_bRestartAfterRestore = false; + event->m_flPrevTime = currenttime; +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PointCommentaryNode::ClientThink() +{ + BaseClass::ClientThink(); + +#ifdef MAPBASE + if (m_iCommentaryType == COMMENTARY_TYPE_SCENE && m_pScene) + { + m_pScene->Think( gpGlobals->curtime - m_flStartTime ); + SetNextClientThink( CLIENT_THINK_ALWAYS ); + } +#endif } //----------------------------------------------------------------------------- @@ -322,6 +800,19 @@ void C_PointCommentaryNode::StopLoopingSounds( void ) (CSoundEnvelopeController::GetController()).SoundDestroy( m_sndCommentary ); m_sndCommentary = NULL; } + +#ifdef MAPBASE + if ( m_pScene ) + { + delete m_pScene; + m_pScene = NULL; + + // Must do this to terminate audio + // (TODO: This causes problems when players switch from a scene node immediately to a regular audio node) + if (m_hSceneOrigin) + m_hSceneOrigin->EmitSound( "AI_BaseNPC.SentenceStop" ); + } +#endif } //----------------------------------------------------------------------------- @@ -374,6 +865,14 @@ CHudCommentary::CHudCommentary( const char *name ) : vgui::Panel( NULL, "HudComm m_hActiveNode = NULL; m_bShouldPaint = true; + +#ifdef MAPBASE + m_pLabel = new vgui::Label( this, "HudCommentaryTextLabel", L"Textual commentary" ); + m_pImage = new vgui::ImagePanel( this, "HudCommentaryImagePanel" ); + m_pImage->SetShouldScaleImage( true ); + + m_iCCDefaultY = 0; +#endif } void CHudCommentary::ApplySchemeSettings( vgui::IScheme *pScheme ) @@ -384,6 +883,11 @@ void CHudCommentary::ApplySchemeSettings( vgui::IScheme *pScheme ) { SetBgColor( m_BGOverrideColor ); } + +#ifdef MAPBASE + m_pLabel->SetPaintBackgroundType( 2 ); + m_pLabel->SetSize( 0, GetTall() ); +#endif } //----------------------------------------------------------------------------- @@ -405,6 +909,21 @@ void CHudCommentary::Paint() if ( pHudCloseCaption ) { pHudCloseCaption->Reset(); + +#ifdef MAPBASE + // Reset close caption element if needed + if (pHudCloseCaption->IsUsingCommentaryDimensions()) + { + int ccX, ccY; + pHudCloseCaption->GetPos( ccX, ccY ); + //pHudCloseCaption->SetPos( ccX, ccY + m_iTypeAudioT ); + + // Run this animation command instead of setting the position directly + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( pHudCloseCaption, "YPos", ccY + m_iTypeAudioT, 0.0f, 0.4f, vgui::AnimationController::INTERPOLATOR_ACCEL ); + + pHudCloseCaption->SetUsingCommentaryDimensions( false ); + } +#endif } } } @@ -413,6 +932,26 @@ void CHudCommentary::Paint() // Detect the end of the commentary if ( flPercentage >= 1 && m_hActiveNode ) { +#ifdef MAPBASE + // Ensure that the scene is terminated + if (m_iCommentaryType == COMMENTARY_TYPE_SCENE) + m_hActiveNode->StopLoopingSounds(); + + // Reset close caption element if needed + CHudCloseCaption *pHudCloseCaption = (CHudCloseCaption *)GET_HUDELEMENT( CHudCloseCaption ); + if (pHudCloseCaption && pHudCloseCaption->IsUsingCommentaryDimensions()) + { + int ccX, ccY; + pHudCloseCaption->GetPos( ccX, ccY ); + //pHudCloseCaption->SetPos( ccX, ccY + m_iTypeAudioT ); + + // Run this animation command instead of setting the position directly + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( pHudCloseCaption, "YPos", ccY + m_iTypeAudioT, 0.0f, 0.4f, vgui::AnimationController::INTERPOLATOR_ACCEL ); + + pHudCloseCaption->SetUsingCommentaryDimensions( false ); + } +#endif + m_hActiveNode = NULL; g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "HideCommentary" ); @@ -426,27 +965,62 @@ void CHudCommentary::Paint() int x, y, wide, tall; GetBounds( x, y, wide, tall ); - int xOffset = m_iBarX; + int xOffset = m_iBarX; int yOffset = m_iBarY; // Find our fade based on our time shown - Color clr = Color( 255, 170, 0, GetAlpha() ); + Color clr = m_ForegroundColor; + +#ifdef MAPBASE + switch (m_iCommentaryType) + { + case COMMENTARY_TYPE_TEXT: + { + // Figure out the size before setting bounds + int lW, lT; + m_pLabel->GetContentSize( lW, lT ); + + lT += (m_iTextBorderSpace * 2); + + vgui::surface()->DrawSetColor( clr ); + vgui::surface()->DrawOutlinedRect( xOffset, yOffset, xOffset + (m_iBarWide * m_flPanelScale), yOffset + (lT /** m_flPanelScale*/) ); //m_iTypeTextT - (yOffset /*+ m_iBarTall*/) ); + } break; + + case COMMENTARY_TYPE_IMAGE: + { + // Figure out the size before setting bounds + int iW, iT; + m_pImage->GetSize( iW, iT ); + //vgui::surface()->DrawGetTextureSize( m_pImage->GetImage()->GetID(), iW, iT ); + + iW += (m_iTextBorderSpace * 2); + iT += (m_iTextBorderSpace * 2); + vgui::surface()->DrawSetColor( clr ); + vgui::surface()->DrawOutlinedRect( xOffset, yOffset, xOffset + iW, yOffset + iT ); //m_iTypeTextT - (yOffset /*+ m_iBarTall*/) ); + } break; + + default: + case COMMENTARY_TYPE_SCENE: + case COMMENTARY_TYPE_AUDIO: + { + // Draw the progress bar + vgui::surface()->DrawSetColor( clr ); + vgui::surface()->DrawOutlinedRect( xOffset, yOffset, xOffset+(m_iBarWide*m_flPanelScale), yOffset+m_iBarTall ); + vgui::surface()->DrawSetColor( clr ); + vgui::surface()->DrawFilledRect( xOffset+2, yOffset+2, xOffset+(int)(flPercentage*(m_iBarWide*m_flPanelScale))-2, yOffset+m_iBarTall-2 ); + } break; + } +#else // Draw the progress bar vgui::surface()->DrawSetColor( clr ); vgui::surface()->DrawOutlinedRect( xOffset, yOffset, xOffset+m_iBarWide, yOffset+m_iBarTall ); vgui::surface()->DrawSetColor( clr ); vgui::surface()->DrawFilledRect( xOffset+2, yOffset+2, xOffset+(int)(flPercentage*m_iBarWide)-2, yOffset+m_iBarTall-2 ); +#endif // Draw the speaker names - // Get our scheme and font information - vgui::HScheme scheme = vgui::scheme()->GetScheme( "ClientScheme" ); - vgui::HFont hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "CommentaryDefault" ); - if ( !hFont ) - { - hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "Default" ); - } - vgui::surface()->DrawSetTextFont( hFont ); + vgui::surface()->DrawSetTextFont( m_hFont ); vgui::surface()->DrawSetTextColor( clr ); vgui::surface()->DrawSetTextPos( m_iSpeakersX, m_iSpeakersY ); vgui::surface()->DrawPrintText( m_szSpeakers, wcslen(m_szSpeakers) ); @@ -469,7 +1043,7 @@ void CHudCommentary::Paint() { int w, h; UTIL_ReplaceKeyBindings( pszText, 0, wzFinal, sizeof( wzFinal ) ); - vgui::surface()->GetTextSize( hFont, wzFinal, w, h ); + vgui::surface()->GetTextSize( m_hFont, wzFinal, w, h ); vgui::surface()->DrawSetTextPos( m_iBarX + m_iBarWide - w, iY ); vgui::surface()->DrawPrintText( wzFinal, wcslen(wzFinal) ); } @@ -478,9 +1052,16 @@ void CHudCommentary::Paint() // Draw the commentary count // Determine our text size, and move that far in from the right hand size (plus the offset) int iCountWide, iCountTall; - vgui::surface()->GetTextSize( hFont, m_szCount, iCountWide, iCountTall ); + vgui::surface()->GetTextSize( m_hFont, m_szCount, iCountWide, iCountTall ); + +#ifdef MAPBASE + if (m_iCommentaryType != COMMENTARY_TYPE_AUDIO && m_iCommentaryType != COMMENTARY_TYPE_SCENE) + vgui::surface()->DrawSetTextPos( wide - m_iTypeTextCountXFR - iCountWide, tall - m_iTypeTextCountYFB - iCountTall ); + else +#endif vgui::surface()->DrawSetTextPos( wide - m_iCountXFR - iCountWide, m_iCountY ); - vgui::surface()->DrawPrintText( m_szCount, wcslen(m_szCount) ); + + vgui::surface()->DrawPrintText( m_szCount, wcslen( m_szCount ) ); // Draw the icon vgui::surface()->DrawSetColor( Color(255,170,0,GetAlpha()) ); @@ -496,12 +1077,136 @@ bool CHudCommentary::ShouldDraw() return ( m_hActiveNode || GetAlpha() > 0 ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudCommentary::PerformLayout() +{ + BaseClass::PerformLayout(); + + switch (m_iCommentaryType) + { + case COMMENTARY_TYPE_TEXT: + { + int xOffset = m_iBarX; + int yOffset = m_iBarY; + + m_pLabel->SetBounds( + xOffset + m_iTextBorderSpace, yOffset + m_iTextBorderSpace, + (float)(m_iBarWide * m_flPanelScale) - m_iTextBorderSpace, GetTall() ); + + // Figure out the size before setting bounds + int lW, lT; + m_pLabel->GetContentSize( lW, lT ); + + //lT = (float)lT * m_flPanelScale; // Don't affect height when scaling + + m_pLabel->SetTall( lT ); + + lW += (float)((m_iTextBorderSpace * 2) + (xOffset * 2)); + lT += (float)((m_iTextBorderSpace * 2) + (yOffset * 2)); + + ResolveBounds( lW, lT ); + } break; + + case COMMENTARY_TYPE_IMAGE: + { + int xOffset = m_iBarX; + int yOffset = m_iBarY; + + // Figure out the size before setting bounds + int iW, iT; + //m_pImage->GetImage()->GetSize( iW, iT ); + vgui::surface()->DrawGetTextureSize( m_pImage->GetImage()->GetID(), iW, iT ); + if (iW <= 0) + iW = 1; + + int iTargetSize = (m_iBarWide - m_iTextBorderSpace); + iT *= (iTargetSize / iW); + iW = iTargetSize; + + iW = (float)iW * m_flPanelScale; + iT = (float)iT * m_flPanelScale; + + m_pImage->SetBounds( + xOffset + m_iTextBorderSpace, + yOffset + m_iTextBorderSpace, + iW, iT ); + + iW += (float)((m_iTextBorderSpace * 2) + (xOffset * 2)); + iT += (float)((m_iTextBorderSpace * 2) + (yOffset * 2)); + + ResolveBounds( iW, iT ); + } break; + + default: + case COMMENTARY_TYPE_SCENE: + case COMMENTARY_TYPE_AUDIO: + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Resolves position on screen; Heavily borrows from CHudMessage::XPosition/YPosition +//----------------------------------------------------------------------------- +void CHudCommentary::ResolveBounds( int width, int height ) +{ + int xPos; + int yPos; + + // ====== X ====== + if ( m_flOverrideX == -1 ) + { + xPos = (ScreenWidth() - width) * 0.5f; + } + else + { + if ( m_flOverrideX < 0 ) + xPos = (1.0 + m_flOverrideX) * ScreenWidth() - width; // Align to right + else + xPos = m_flOverrideX * (ScreenWidth() - width); + } + + // Clamp to edge of screen + if ( xPos + width > ScreenWidth() ) + xPos = ScreenWidth() - width; + else if ( xPos < 0 ) + xPos = 0; + + // ====== Y ====== + if ( m_flOverrideY == -1 ) + { + yPos = (ScreenHeight() - height) * 0.5f; + } + else + { + if ( m_flOverrideY < 0 ) + yPos = (1.0 + m_flOverrideY) * ScreenHeight() - height; // Align to bottom + else + yPos = m_flOverrideY * (ScreenHeight() - height); + } + + // Clamp to edge of screen + if ( yPos + height > ScreenHeight() ) + yPos = ScreenHeight() - height; + else if ( yPos < 0 ) + yPos = 0; + + SetBounds( xPos, yPos, width, height ); +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CHudCommentary::Init( void ) { m_matIcon.Init( "vgui/hud/icon_commentary", TEXTURE_GROUP_VGUI ); + +#ifdef MAPBASE + SetProportional( true ); +#endif } //----------------------------------------------------------------------------- @@ -511,6 +1216,9 @@ void CHudCommentary::VidInit( void ) { SetAlpha(0); StopCommentary(); +#ifdef MAPBASE + m_iCCDefaultY = 0; +#endif } //----------------------------------------------------------------------------- @@ -525,7 +1233,30 @@ void CHudCommentary::StartCommentary( C_PointCommentaryNode *pNode, char *pszSpe m_flStartTime = flStartTime; m_flEndTime = flEndTime; m_bHiding = false; - g_pVGuiLocalize->ConvertANSIToUnicode( pszSpeakers, m_szSpeakers, sizeof(m_szSpeakers) ); +#ifdef MAPBASE + m_iCommentaryType = COMMENTARY_TYPE_AUDIO; + m_flPanelScale = pNode->m_flPanelScale; + m_flOverrideX = pNode->m_flPanelX; + m_flOverrideY = pNode->m_flPanelY; +#endif + g_pVGuiLocalize->ConvertANSIToUnicode( pszSpeakers, m_szSpeakers, sizeof( m_szSpeakers ) ); + +#ifdef MAPBASE + SetBounds( m_iTypeAudioX, m_iTypeAudioY, m_iTypeAudioW, m_iTypeAudioT ); + SetBgColor( m_bUseScriptBGColor ? m_BGOverrideColor : m_BackgroundColor ); + + m_pLabel->SetPaintEnabled( false ); + m_pImage->SetPaintEnabled( false ); + m_pImage->EvictImage(); + + // Get our scheme and font information + vgui::HScheme scheme = GetScheme(); + m_hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "CommentaryDefault" ); + if ( !m_hFont ) + { + m_hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "Default" ); + } +#endif // Don't draw the element itself if closecaptions are on (and captions are always on in non-english mode) ConVarRef pCVar( "closecaption" ); @@ -537,6 +1268,20 @@ void CHudCommentary::StartCommentary( C_PointCommentaryNode *pNode, char *pszSpe { m_bShouldPaint = true; } + +#ifdef MAPBASE + if (!m_bShouldPaint && commentary_audio_element_below_cc.GetBool()) + { + m_bShouldPaint = true; + RepositionCloseCaption(); + } + + if (commentary_combine_speaker_and_printname.GetBool() && pNode && pNode->m_iszPrintName[0] != '\0') + { + CombineSpeakerAndPrintName( pNode->m_iszPrintName ); + } +#endif + SetPaintBackgroundEnabled( m_bShouldPaint ); char sz[MAX_COUNT_STRING]; @@ -555,13 +1300,305 @@ void CHudCommentary::StartCommentary( C_PointCommentaryNode *pNode, char *pszSpe } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudCommentary::StartTextCommentary( C_PointCommentaryNode *pNode, const char *pszText, char *pszSpeakers, int iNode, int iNodeMax, float flStartTime, float flEndTime ) +{ + if ( (flEndTime - flStartTime) <= 0 ) + return; + + m_hActiveNode = pNode; + m_flStartTime = flStartTime; + m_flEndTime = flEndTime; + m_bHiding = false; + m_iCommentaryType = COMMENTARY_TYPE_TEXT; + m_flPanelScale = pNode->m_flPanelScale; + m_flOverrideX = pNode->m_flPanelX; + m_flOverrideY = pNode->m_flPanelY; + g_pVGuiLocalize->ConvertANSIToUnicode( pszSpeakers, m_szSpeakers, sizeof( m_szSpeakers ) ); + + SetBounds( m_iTypeTextX, m_iTypeTextY, m_iTypeTextW, m_iTypeTextT ); + SetBgColor( m_bUseScriptBGColor ? m_BGOverrideColor : m_TextBackgroundColor ); + + // Get our scheme and font information + vgui::HScheme scheme = GetScheme(); + m_hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "CommentaryDefault" ); + if ( !m_hFont ) + { + m_hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "Default" ); + } + + m_pLabel->SetText( pszText ); + m_pLabel->SetFont( m_hFont ); + m_pLabel->SetWrap( true ); + m_pLabel->SetPaintEnabled( true ); + m_pLabel->SetPaintBackgroundEnabled( false ); + m_pLabel->SetPaintBorderEnabled( false ); + //m_pLabel->SizeToContents(); + m_pLabel->SetContentAlignment( vgui::Label::a_northwest ); + m_pLabel->SetFgColor( m_TypeTextContentColor ); + + m_pImage->SetPaintEnabled( false ); + m_pImage->EvictImage(); + + m_bShouldPaint = true; + + if (commentary_combine_speaker_and_printname.GetBool() && pNode && pNode->m_iszPrintName[0] != '\0') + { + CombineSpeakerAndPrintName( pNode->m_iszPrintName ); + } + + SetPaintBackgroundEnabled( m_bShouldPaint ); + + char sz[MAX_COUNT_STRING]; + Q_snprintf( sz, sizeof(sz), "%d \\ %d", iNode, iNodeMax ); + g_pVGuiLocalize->ConvertANSIToUnicode( sz, m_szCount, sizeof(m_szCount) ); + + // If the commentary just started, play the commentary fade in. + if ( fabs(flStartTime - gpGlobals->curtime) < 1.0 ) + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "ShowCommentary" ); + } + else + { + // We're reloading a savegame that has an active commentary going in it. Don't fade in. + SetAlpha( 255 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudCommentary::StartImageCommentary( C_PointCommentaryNode *pNode, const char *pszImage, char *pszSpeakers, int iNode, int iNodeMax, float flStartTime, float flEndTime ) +{ + if ( (flEndTime - flStartTime) <= 0 ) + return; + + m_hActiveNode = pNode; + m_flStartTime = flStartTime; + m_flEndTime = flEndTime; + m_bHiding = false; + m_iCommentaryType = COMMENTARY_TYPE_IMAGE; + m_flPanelScale = pNode->m_flPanelScale; + m_flOverrideX = pNode->m_flPanelX; + m_flOverrideY = pNode->m_flPanelY; + g_pVGuiLocalize->ConvertANSIToUnicode( pszSpeakers, m_szSpeakers, sizeof( m_szSpeakers ) ); + + SetBounds( m_iTypeTextX, m_iTypeTextY, m_iTypeTextW, m_iTypeTextT ); + SetBgColor( m_bUseScriptBGColor ? m_BGOverrideColor : m_TextBackgroundColor ); + + m_pLabel->SetPaintEnabled( false ); + + m_pImage->SetPaintEnabled( true ); + m_pImage->SetImage( pszImage ); + m_pImage->SetWide( m_iBarWide - m_iTextBorderSpace ); + + // Get our scheme and font information + vgui::HScheme scheme = GetScheme(); + m_hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "CommentaryDefault" ); + if ( !m_hFont ) + { + m_hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "Default" ); + } + + m_bShouldPaint = true; + + if (commentary_combine_speaker_and_printname.GetBool() && pNode && pNode->m_iszPrintName[0] != '\0') + { + CombineSpeakerAndPrintName( pNode->m_iszPrintName ); + } + + SetPaintBackgroundEnabled( m_bShouldPaint ); + + char sz[MAX_COUNT_STRING]; + Q_snprintf( sz, sizeof(sz), "%d \\ %d", iNode, iNodeMax ); + g_pVGuiLocalize->ConvertANSIToUnicode( sz, m_szCount, sizeof(m_szCount) ); + + // If the commentary just started, play the commentary fade in. + if ( fabs(flStartTime - gpGlobals->curtime) < 1.0 ) + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "ShowCommentary" ); + } + else + { + // We're reloading a savegame that has an active commentary going in it. Don't fade in. + SetAlpha( 255 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudCommentary::StartSceneCommentary( C_PointCommentaryNode *pNode, char *pszSpeakers, int iNode, int iNodeMax, float flStartTime, float flEndTime ) +{ + if ( (flEndTime - flStartTime) <= 0 ) + return; + + m_hActiveNode = pNode; + m_flStartTime = flStartTime; + m_flEndTime = flEndTime; + m_bHiding = false; + m_iCommentaryType = COMMENTARY_TYPE_SCENE; + m_flPanelScale = pNode->m_flPanelScale; + m_flOverrideX = pNode->m_flPanelX; + m_flOverrideY = pNode->m_flPanelY; + g_pVGuiLocalize->ConvertANSIToUnicode( pszSpeakers, m_szSpeakers, sizeof( m_szSpeakers ) ); + + SetBounds( m_iTypeAudioX, m_iTypeAudioY, m_iTypeAudioW, m_iTypeAudioT ); + SetBgColor( m_bUseScriptBGColor ? m_BGOverrideColor : m_BackgroundColor ); + + m_pLabel->SetPaintEnabled( false ); + m_pImage->SetPaintEnabled( false ); + m_pImage->EvictImage(); + + // Get our scheme and font information + vgui::HScheme scheme = GetScheme(); + m_hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "CommentaryDefault" ); + if ( !m_hFont ) + { + m_hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "Default" ); + } + + // Don't draw the element itself if closecaptions are on (and captions are always on in non-english mode) + ConVarRef pCVar( "closecaption" ); + if ( pCVar.IsValid() ) + { + m_bShouldPaint = ( !pCVar.GetBool() && english.GetBool() ); + } + else + { + m_bShouldPaint = true; + } + + if (!m_bShouldPaint && commentary_audio_element_below_cc.GetBool()) + { + m_bShouldPaint = true; + RepositionCloseCaption(); + } + + if (commentary_combine_speaker_and_printname.GetBool() && pNode && pNode->m_iszPrintName[0] != '\0') + { + CombineSpeakerAndPrintName( pNode->m_iszPrintName ); + } + + SetPaintBackgroundEnabled( m_bShouldPaint ); + + char sz[MAX_COUNT_STRING]; + Q_snprintf( sz, sizeof(sz), "%d \\ %d", iNode, iNodeMax ); + g_pVGuiLocalize->ConvertANSIToUnicode( sz, m_szCount, sizeof(m_szCount) ); + + // If the commentary just started, play the commentary fade in. + if ( fabs(flStartTime - gpGlobals->curtime) < 1.0 ) + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "ShowCommentary" ); + } + else + { + // We're reloading a savegame that has an active commentary going in it. Don't fade in. + SetAlpha( 255 ); + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CHudCommentary::StopCommentary( void ) { m_hActiveNode = NULL; + +#ifdef MAPBASE + // Reset close caption element if needed + CHudCloseCaption *pHudCloseCaption = (CHudCloseCaption *)GET_HUDELEMENT( CHudCloseCaption ); + if (pHudCloseCaption && pHudCloseCaption->IsUsingCommentaryDimensions()) + { + int ccX, ccY; + pHudCloseCaption->GetPos( ccX, ccY ); + //pHudCloseCaption->SetPos( ccX, ccY + m_iTypeAudioT ); + + // Run this animation command instead of setting the position directly + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( pHudCloseCaption, "YPos", ccY + m_iTypeAudioT, 0.0f, 0.4f, vgui::AnimationController::INTERPOLATOR_ACCEL ); + + pHudCloseCaption->SetUsingCommentaryDimensions( false ); + } +#endif +} + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudCommentary::CombineSpeakerAndPrintName( const char *pszPrintName ) +{ + wchar_t *pszLocal = g_pVGuiLocalize->Find( pszPrintName ); + if (m_szSpeakers[0] == '\0' || !m_bShouldPaint) // Use m_bShouldPaint as an indicator of whether or not we use subtitles + { + if (pszPrintName[0] == '#' && pszLocal) + wcsncpy( m_szSpeakers, pszLocal, sizeof( m_szSpeakers ) / sizeof( wchar_t ) ); + else + g_pVGuiLocalize->ConvertANSIToUnicode( pszPrintName, m_szSpeakers, sizeof( m_szSpeakers ) ); + } + else + { + static wchar_t iszPrintNameLocalized[MAX_SPEAKER_NAME]; + + if (pszPrintName[0] == '#' && pszLocal) + wcsncpy( iszPrintNameLocalized, pszLocal, sizeof( iszPrintNameLocalized ) / sizeof( wchar_t ) ); + else + g_pVGuiLocalize->ConvertANSIToUnicode( pszPrintName, iszPrintNameLocalized, sizeof( iszPrintNameLocalized ) ); + + V_snwprintf( m_szSpeakers, sizeof( m_szSpeakers ), L"%ls ~ %ls", m_szSpeakers, iszPrintNameLocalized ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudCommentary::RepositionCloseCaption() +{ + // Invert the Y axis + //SetPos( m_iTypeAudioX, ScreenHeight() - m_iTypeAudioY ); + + // Place underneath the close caption element + CHudCloseCaption *pHudCloseCaption = (CHudCloseCaption *)GET_HUDELEMENT( CHudCloseCaption ); + if (pHudCloseCaption /*&& !pHudCloseCaption->IsUsingCommentaryDimensions()*/) + { + int ccX, ccY; + pHudCloseCaption->GetPos( ccX, ccY ); + + // Save the default position in case we need to do a hard reset + // (this usually happens when players begin commentary before the CC element's return animation command is finished) + if (m_iCCDefaultY == 0) + { + m_iCCDefaultY = ccY; + } + + if (!pHudCloseCaption->IsUsingCommentaryDimensions()) + { + if (m_iCCDefaultY != ccY && !pHudCloseCaption->IsUsingCommentaryDimensions()) + { + DevMsg( "CHudCommentary had to reset misaligned CC element Y (%i) to default Y (%i)\n", ccY, m_iCCDefaultY ); + ccY = m_iCCDefaultY; + } + + ccY -= m_iTypeAudioT; + + // Run this animation command instead of setting the position directly + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( pHudCloseCaption, "YPos", ccY, 0.0f, 0.2f, vgui::AnimationController::INTERPOLATOR_DEACCEL ); + //pHudCloseCaption->SetPos( ccX, ccY ); + + pHudCloseCaption->SetUsingCommentaryDimensions( true ); + } + + SetPos( ccX, ccY + pHudCloseCaption->GetTall() + commentary_audio_element_below_cc_margin.GetInt() ); + + m_flPanelScale = (float)pHudCloseCaption->GetWide() / (float)GetWide(); + SetWide( pHudCloseCaption->GetWide() ); + } } +#endif //----------------------------------------------------------------------------- // Purpose: diff --git a/sp/src/game/client/c_rope.cpp b/sp/src/game/client/c_rope.cpp index 450faf7877..636b7c6e2d 100644 --- a/sp/src/game/client/c_rope.cpp +++ b/sp/src/game/client/c_rope.cpp @@ -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 diff --git a/sp/src/game/client/c_rope.h b/sp/src/game/client/c_rope.h index 3d82108148..204b412b1c 100644 --- a/sp/src/game/client/c_rope.h +++ b/sp/src/game/client/c_rope.h @@ -33,6 +33,9 @@ class C_RopeKeyframe : public C_BaseEntity DECLARE_CLASS( C_RopeKeyframe, C_BaseEntity ); DECLARE_CLIENTCLASS(); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); +#endif private: @@ -142,6 +145,11 @@ class C_RopeKeyframe : public C_BaseEntity 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 @@ class C_RopeKeyframe : public C_BaseEntity 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. diff --git a/sp/src/game/client/c_vguiscreen.h b/sp/src/game/client/c_vguiscreen.h index d304f5c9e7..71780093c5 100644 --- a/sp/src/game/client/c_vguiscreen.h +++ b/sp/src/game/client/c_vguiscreen.h @@ -112,6 +112,15 @@ class C_VGuiScreen : public C_BaseEntity C_BasePlayer *GetPlayerOwner( void ); bool IsInputOnlyToOwner( void ); +#ifdef MAPBASE + void GetSize( float &width, float &height ) const { width = m_flWidth; height = m_flHeight; } + void GetPixelSize( int &width, int &height ) const { width = m_nPixelWidth; height = m_nPixelHeight; } + void SetWidth( float flWidth ) { m_flWidth = flWidth; } + void SetHeight( float flHeight ) { m_flHeight = flHeight; } + void SetPixelWidth( int nWidth ) { m_nPixelWidth = nWidth; } + void SetPixelHeight( int nHeight ) { m_nPixelHeight = nHeight; } +#endif + private: // Vgui screen management void CreateVguiScreen( const char *pTypeName ); diff --git a/sp/src/game/client/cdll_util.h b/sp/src/game/client/cdll_util.h index 44f559b0c2..263325515a 100644 --- a/sp/src/game/client/cdll_util.h +++ b/sp/src/game/client/cdll_util.h @@ -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 ); diff --git a/sp/src/game/client/client_base.vpc b/sp/src/game/client/client_base.vpc index 29e5acc675..2fda4762ff 100644 --- a/sp/src/game/client/client_base.vpc +++ b/sp/src/game/client/client_base.vpc @@ -71,6 +71,7 @@ $Configuration $SystemFrameworks "Carbon" [$OSXALL] $SystemLibraries "rt" [$LINUXALL] $IgnoreImportLibrary "TRUE" + $AdditionalOptions "$BASE /force:multiple" [($VS2015||$VS2017||$VS2019||$VS2022)] // Required to fix _hypot in particles.lib (this may be a temporary solution) $AdditionalDependencies "$BASE winmm.lib" [$WIN32] $AdditionalDependencies "$BASE wsock32.lib Ws2_32.lib" [$BUILD_REPLAY] } @@ -536,7 +537,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 +1107,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 +1164,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" diff --git a/sp/src/game/client/client_mapbase.vpc b/sp/src/game/client/client_mapbase.vpc index 797325a0b7..97a2217d3c 100644 --- a/sp/src/game/client/client_mapbase.vpc +++ b/sp/src/game/client/client_mapbase.vpc @@ -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" { @@ -45,6 +47,8 @@ $Project $File "$SRCDIR\game\shared\mapbase\MapEdit.h" $File "$SRCDIR\game\shared\mapbase\matchers.cpp" $File "$SRCDIR\game\shared\mapbase\matchers.h" + $File "$SRCDIR\game\shared\mapbase\singleplayer_animstate.cpp" + $File "$SRCDIR\game\shared\mapbase\singleplayer_animstate.h" $File "$SRCDIR\game\shared\mapbase\vscript_funcs_shared.cpp" [$MAPBASE_VSCRIPT] $File "$SRCDIR\game\shared\mapbase\vscript_funcs_shared.h" [$MAPBASE_VSCRIPT] $File "$SRCDIR\game\shared\mapbase\vscript_singletons.cpp" [$MAPBASE_VSCRIPT] @@ -61,6 +65,7 @@ $Project $File "mapbase\c_func_fake_worldportal.h" $File "mapbase\c_point_glow.cpp" $File "mapbase\c_vgui_text_display.cpp" + $File "mapbase\mapbase_autocubemap.cpp" } $Folder "HL2 DLL" diff --git a/sp/src/game/client/clientmode_shared.cpp b/sp/src/game/client/clientmode_shared.cpp index f68531bb05..f2e6e31dc5 100644 --- a/sp/src/game/client/clientmode_shared.cpp +++ b/sp/src/game/client/clientmode_shared.cpp @@ -292,8 +292,11 @@ ClientModeShared::ClientModeShared() m_pWeaponSelection = NULL; m_nRootSize[ 0 ] = m_nRootSize[ 1 ] = -1; +#ifdef MAPBASE // From Alien Swarm SDK m_pCurrentPostProcessController = NULL; m_PostProcessLerpTimer.Invalidate(); + m_pCurrentColorCorrection = NULL; +#endif #if defined( REPLAY_ENABLED ) m_pReplayReminderPanel = NULL; @@ -635,6 +638,43 @@ void ClientModeShared::Update() } } +#ifdef MAPBASE // From Alien Swarm SDK +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ClientModeShared::OnColorCorrectionWeightsReset( void ) +{ + C_ColorCorrection *pNewColorCorrection = NULL; + C_ColorCorrection *pOldColorCorrection = m_pCurrentColorCorrection; + C_BasePlayer* pPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pPlayer ) + { + pNewColorCorrection = pPlayer->GetActiveColorCorrection(); + } + + if ( pNewColorCorrection != pOldColorCorrection ) + { + if ( pOldColorCorrection ) + { + pOldColorCorrection->EnableOnClient( false ); + } + if ( pNewColorCorrection ) + { + pNewColorCorrection->EnableOnClient( true, pOldColorCorrection == NULL ); + } + m_pCurrentColorCorrection = pNewColorCorrection; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float ClientModeShared::GetColorCorrectionScale( void ) const +{ + return 1.0f; +} +#endif + //----------------------------------------------------------------------------- // This processes all input before SV Move messages are sent //----------------------------------------------------------------------------- @@ -1259,7 +1299,7 @@ void ClientModeShared::FireGameEvent( IGameEvent *event ) } } - if ( team == 0 && GetLocalTeam() > 0 ) + if ( team == 0 && GetLocalTeam() ) { bValidTeam = false; } diff --git a/sp/src/game/client/clientmode_shared.h b/sp/src/game/client/clientmode_shared.h index a1389f9612..20288db8f7 100644 --- a/sp/src/game/client/clientmode_shared.h +++ b/sp/src/game/client/clientmode_shared.h @@ -91,6 +91,11 @@ class ClientModeShared : public IClientMode, public CGameEventListener virtual void ProcessInput(bool bActive); virtual bool CreateMove( float flInputSampleTime, CUserCmd *cmd ); virtual void Update(); +#ifdef MAPBASE // From Alien Swarm SDK + virtual void OnColorCorrectionWeightsReset( void ); + virtual float GetColorCorrectionScale( void ) const; + virtual void ClearCurrentColorCorrection() { m_pCurrentColorCorrection = NULL; } +#endif // Input virtual int KeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding ); @@ -165,12 +170,16 @@ class ClientModeShared : public IClientMode, public CGameEventListener CBaseHudWeaponSelection *m_pWeaponSelection; int m_nRootSize[2]; +#ifdef MAPBASE // From Alien Swarm SDK void UpdatePostProcessingEffects(); const C_PostProcessController* m_pCurrentPostProcessController; PostProcessParameters_t m_CurrentPostProcessParameters; PostProcessParameters_t m_LerpStartPostProcessParameters, m_LerpEndPostProcessParameters; CountdownTimer m_PostProcessLerpTimer; + + CHandle m_pCurrentColorCorrection; +#endif }; #endif // CLIENTMODE_NORMAL_H diff --git a/sp/src/game/client/clientshadowmgr.cpp b/sp/src/game/client/clientshadowmgr.cpp index 4ac6f31ed6..9dc2a5fe11 100644 --- a/sp/src/game/client/clientshadowmgr.cpp +++ b/sp/src/game/client/clientshadowmgr.cpp @@ -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; } diff --git a/sp/src/game/client/colorcorrectionmgr.cpp b/sp/src/game/client/colorcorrectionmgr.cpp index 770354b0fc..cf1210ac7f 100644 --- a/sp/src/game/client/colorcorrectionmgr.cpp +++ b/sp/src/game/client/colorcorrectionmgr.cpp @@ -8,6 +8,12 @@ #include "cbase.h" #include "tier0/vprof.h" #include "colorcorrectionmgr.h" +#ifdef MAPBASE // From Alien Swarm SDK +#include "clientmode_shared.h" //"clientmode.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" +#endif //------------------------------------------------------------------------------ @@ -16,6 +22,13 @@ static CColorCorrectionMgr s_ColorCorrectionMgr; CColorCorrectionMgr *g_pColorCorrectionMgr = &s_ColorCorrectionMgr; +#ifdef MAPBASE // From Alien Swarm SDK +static ConVar mat_colcorrection_editor( "mat_colcorrection_editor", "0" ); + +static CUtlVector g_ColorCorrectionList; +static CUtlVector g_ColorCorrectionVolumeList; +#endif + //------------------------------------------------------------------------------ // Constructor @@ -62,10 +75,89 @@ void CColorCorrectionMgr::RemoveColorCorrection( ClientCCHandle_t h ) } } +#ifdef MAPBASE // From Alien Swarm SDK +ClientCCHandle_t CColorCorrectionMgr::AddColorCorrectionEntity( C_ColorCorrection *pEntity, const char *pName, const char *pFileName ) +{ + ClientCCHandle_t h = AddColorCorrection(pName, pFileName); + if ( h != INVALID_CLIENT_CCHANDLE ) + { + Assert(g_ColorCorrectionList.Find(pEntity) == -1); + g_ColorCorrectionList.AddToTail(pEntity); + } + return h; +} + +void CColorCorrectionMgr::RemoveColorCorrectionEntity( C_ColorCorrection *pEntity, ClientCCHandle_t h) +{ + RemoveColorCorrection(h); + g_ColorCorrectionList.FindAndFastRemove(pEntity); +} + +ClientCCHandle_t CColorCorrectionMgr::AddColorCorrectionVolume( C_ColorCorrectionVolume *pVolume, const char *pName, const char *pFileName ) +{ + ClientCCHandle_t h = AddColorCorrection(pName, pFileName); + if ( h != INVALID_CLIENT_CCHANDLE ) + { + Assert(g_ColorCorrectionVolumeList.Find(pVolume) == -1); + g_ColorCorrectionVolumeList.AddToTail(pVolume); + } + return h; +} + +void CColorCorrectionMgr::RemoveColorCorrectionVolume( C_ColorCorrectionVolume *pVolume, ClientCCHandle_t h) +{ + RemoveColorCorrection(h); + g_ColorCorrectionVolumeList.FindAndFastRemove(pVolume); +} +#endif //------------------------------------------------------------------------------ // Modify color correction weights //------------------------------------------------------------------------------ +#ifdef MAPBASE // From Alien Swarm SDK +void CColorCorrectionMgr::SetColorCorrectionWeight( ClientCCHandle_t h, float flWeight, bool bExclusive ) +{ + if ( h != INVALID_CLIENT_CCHANDLE ) + { + SetWeightParams_t params = { h, flWeight, bExclusive }; + m_colorCorrectionWeights.AddToTail( params ); + if( bExclusive && m_bHaveExclusiveWeight && ( flWeight != 0.0f ) ) + { + DevWarning( "Found multiple active color_correction entities with exclusive setting enabled. This is invalid.\n" ); + } + if ( bExclusive ) + { + m_bHaveExclusiveWeight = true; + m_flExclusiveWeight = flWeight; + } + } +} + +void CColorCorrectionMgr::CommitColorCorrectionWeights() +{ + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + + for ( int i = 0; i < m_colorCorrectionWeights.Count(); i++ ) + { + ColorCorrectionHandle_t ccHandle = reinterpret_cast( m_colorCorrectionWeights[i].handle ); + float flWeight = m_colorCorrectionWeights[i].flWeight; + if ( !m_colorCorrectionWeights[i].bExclusive ) + { + flWeight = (1.0f - m_flExclusiveWeight ) * m_colorCorrectionWeights[i].flWeight; + } + pRenderContext->SetLookupWeight( ccHandle, flWeight ); + + // FIXME: NOTE! This doesn't work if the same handle has + // its weight set twice with no intervening calls to ResetColorCorrectionWeights + // which, at the moment, is true + if ( flWeight != 0.0f ) + { + ++m_nActiveWeightCount; + } + } + m_colorCorrectionWeights.RemoveAll(); +} +#else void CColorCorrectionMgr::SetColorCorrectionWeight( ClientCCHandle_t h, float flWeight ) { if ( h != INVALID_CLIENT_CCHANDLE ) @@ -83,6 +175,7 @@ void CColorCorrectionMgr::SetColorCorrectionWeight( ClientCCHandle_t h, float fl } } } +#endif void CColorCorrectionMgr::ResetColorCorrectionWeights() { @@ -93,6 +186,11 @@ void CColorCorrectionMgr::ResetColorCorrectionWeights() CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); pRenderContext->ResetLookupWeights(); m_nActiveWeightCount = 0; +#ifdef MAPBASE // From Alien Swarm SDK + m_bHaveExclusiveWeight = false; + m_flExclusiveWeight = 0.0f; + m_colorCorrectionWeights.RemoveAll(); +#endif } void CColorCorrectionMgr::SetResetable( ClientCCHandle_t h, bool bResetable ) @@ -113,7 +211,34 @@ void CColorCorrectionMgr::SetResetable( ClientCCHandle_t h, bool bResetable ) //------------------------------------------------------------------------------ // Is color correction active? //------------------------------------------------------------------------------ +#ifdef MAPBASE // From Alien Swarm SDK +bool CColorCorrectionMgr::HasNonZeroColorCorrectionWeights() const +{ + return ( m_nActiveWeightCount != 0 ) || mat_colcorrection_editor.GetBool(); +} + +void CColorCorrectionMgr::UpdateColorCorrection() +{ + ResetColorCorrectionWeights(); + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + IClientMode *pClientMode = GetClientModeNormal(); //GetClientMode(); + + Assert( pClientMode ); + if ( !pPlayer || !pClientMode ) + { + return; + } + + pClientMode->OnColorCorrectionWeightsReset(); + float ccScale = pClientMode->GetColorCorrectionScale(); + + UpdateColorCorrectionEntities( pPlayer, ccScale, g_ColorCorrectionList.Base(), g_ColorCorrectionList.Count() ); + UpdateColorCorrectionVolumes( pPlayer, ccScale, g_ColorCorrectionVolumeList.Base(), g_ColorCorrectionVolumeList.Count() ); + CommitColorCorrectionWeights(); +} +#else bool CColorCorrectionMgr::HasNonZeroColorCorrectionWeights() const { return ( m_nActiveWeightCount != 0 ); } +#endif diff --git a/sp/src/game/client/colorcorrectionmgr.h b/sp/src/game/client/colorcorrectionmgr.h index 3d5271db6c..3eba0f8cb7 100644 --- a/sp/src/game/client/colorcorrectionmgr.h +++ b/sp/src/game/client/colorcorrectionmgr.h @@ -14,6 +14,10 @@ #include "igamesystem.h" +#ifdef MAPBASE // From Alien Swarm SDK +class C_ColorCorrection; +class C_ColorCorrectionVolume; +#endif //------------------------------------------------------------------------------ // Purpose : Singleton manager for color correction on the client @@ -35,8 +39,21 @@ class CColorCorrectionMgr : public CBaseGameSystem ClientCCHandle_t AddColorCorrection( const char *pName, const char *pFileName = NULL ); void RemoveColorCorrection( ClientCCHandle_t ); +#ifdef MAPBASE // From Alien Swarm SDK + ClientCCHandle_t AddColorCorrectionEntity( C_ColorCorrection *pEntity, const char *pName, const char *pFileName = NULL ); + void RemoveColorCorrectionEntity( C_ColorCorrection *pEntity, ClientCCHandle_t ); + + ClientCCHandle_t AddColorCorrectionVolume( C_ColorCorrectionVolume *pVolume, const char *pName, const char *pFileName = NULL ); + void RemoveColorCorrectionVolume( C_ColorCorrectionVolume *pVolume, ClientCCHandle_t ); +#endif + // Modify color correction weights +#ifdef MAPBASE // From Alien Swarm SDK + void SetColorCorrectionWeight( ClientCCHandle_t h, float flWeight, bool bExclusive = false ); + void UpdateColorCorrection(); +#else void SetColorCorrectionWeight( ClientCCHandle_t h, float flWeight ); +#endif void ResetColorCorrectionWeights(); void SetResetable( ClientCCHandle_t h, bool bResetable ); @@ -45,8 +62,27 @@ class CColorCorrectionMgr : public CBaseGameSystem private: int m_nActiveWeightCount; +#ifdef MAPBASE // From Alien Swarm SDK + bool m_bHaveExclusiveWeight; + float m_flExclusiveWeight; + + struct SetWeightParams_t + { + ClientCCHandle_t handle; + float flWeight; + bool bExclusive; + }; + + CUtlVector< SetWeightParams_t > m_colorCorrectionWeights; + + void CommitColorCorrectionWeights(); +#endif }; +#ifdef MAPBASE // From Alien Swarm SDK +void UpdateColorCorrectionEntities( C_BasePlayer *pPlayer, float ccScale, C_ColorCorrection **pList, int listCount ); +void UpdateColorCorrectionVolumes( C_BasePlayer *pPlayer, float ccScale, C_ColorCorrectionVolume **pList, int listCount ); +#endif //------------------------------------------------------------------------------ // Singleton access diff --git a/sp/src/game/client/convarproxy.cpp b/sp/src/game/client/convarproxy.cpp new file mode 100644 index 0000000000..6b08bafc4c --- /dev/null +++ b/sp/src/game/client/convarproxy.cpp @@ -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 ); diff --git a/sp/src/game/client/episodic/c_prop_scalable.cpp b/sp/src/game/client/episodic/c_prop_scalable.cpp index d3902db3b3..b3134460ab 100644 --- a/sp/src/game/client/episodic/c_prop_scalable.cpp +++ b/sp/src/game/client/episodic/c_prop_scalable.cpp @@ -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 diff --git a/sp/src/game/client/flashlighteffect.cpp b/sp/src/game/client/flashlighteffect.cpp index 6733fc56e4..9a4af817fd 100644 --- a/sp/src/game/client/flashlighteffect.cpp +++ b/sp/src/game/client/flashlighteffect.cpp @@ -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 ); diff --git a/sp/src/game/client/fx_impact.cpp b/sp/src/game/client/fx_impact.cpp index 682d5435d2..236e39b4dd 100644 --- a/sp/src/game/client/fx_impact.cpp +++ b/sp/src/game/client/fx_impact.cpp @@ -25,6 +25,13 @@ extern ConVar r_drawmodeldecals; ImpactSoundRouteFn g_pImpactSoundRouteFn = NULL; +#ifdef MAPBASE +ConVar g_ragdoll_steal_impacts_client( "g_ragdoll_steal_impacts_client", "1", FCVAR_NONE, "Allows clientside death ragdolls to \"steal\" impacts from their source entities. This fixes issues with NPCs dying before decals are applied." ); +ConVar g_ragdoll_steal_impacts_server( "g_ragdoll_steal_impacts_server", "1", FCVAR_NONE, "Allows serverside death ragdolls to \"steal\" impacts from their source entities. This fixes issues with NPCs dying before decals are applied." ); + +ConVar g_ragdoll_client_impact_decals( "g_ragdoll_client_impact_decals", "1", FCVAR_NONE, "Applies decals to clientside ragdolls when they are hit." ); +#endif + //========================================================================================================================== // RAGDOLL ENUMERATOR //========================================================================================================================== @@ -32,7 +39,11 @@ CRagdollEnumerator::CRagdollEnumerator( Ray_t& shot, int iDamageType ) { m_rayShot = shot; m_iDamageType = iDamageType; +#ifdef MAPBASE + m_pHitEnt = NULL; +#else m_bHit = false; +#endif } IterationRetval_t CRagdollEnumerator::EnumElement( IHandleEntity *pHandleEntity ) @@ -57,7 +68,11 @@ IterationRetval_t CRagdollEnumerator::EnumElement( IHandleEntity *pHandleEntity if ( tr.fraction < 1.0 ) { pModel->ImpactTrace( &tr, m_iDamageType, NULL ); +#ifdef MAPBASE + m_pHitEnt = pModel; +#else m_bHit = true; +#endif //FIXME: Yes? No? return ITERATION_STOP; @@ -84,6 +99,22 @@ bool FX_AffectRagdolls( Vector vecOrigin, Vector vecStart, int iDamageType ) return ragdollEnum.Hit(); } +#ifdef MAPBASE +C_BaseAnimating *FX_AffectRagdolls_GetHit( Vector vecOrigin, Vector vecStart, int iDamageType ) +{ + // don't do this when lots of ragdolls are simulating + if ( s_RagdollLRU.CountRagdolls(true) > 1 ) + return NULL; + Ray_t shotRay; + shotRay.Init( vecStart, vecOrigin ); + + CRagdollEnumerator ragdollEnum( shotRay, iDamageType ); + partition->EnumerateElementsAlongRay( PARTITION_CLIENT_RESPONSIVE_EDICTS, shotRay, false, &ragdollEnum ); + + return ragdollEnum.GetHit(); +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : &data - @@ -104,6 +135,22 @@ bool Impact( Vector &vecOrigin, Vector &vecStart, int iMaterial, int iDamageType Assert ( pEntity ); +#ifdef MAPBASE + // If the entity already has a ragdoll that was created on the current tick, use that ragdoll instead. + // This allows the killing damage's decals to show up on the ragdoll. + if (C_BaseAnimating *pAnimating = pEntity->GetBaseAnimating()) + { + if (pAnimating->m_pClientsideRagdoll && WasRagdollCreatedOnCurrentTick( pAnimating->m_pClientsideRagdoll ) && g_ragdoll_steal_impacts_client.GetBool()) + { + pEntity = pAnimating->m_pClientsideRagdoll; + } + else if (pAnimating->m_pServerRagdoll && WasRagdollCreatedOnCurrentTick( pAnimating->m_pServerRagdoll ) && g_ragdoll_steal_impacts_server.GetBool()) + { + pEntity = pAnimating->m_pServerRagdoll; + } + } +#endif + // Clear out the trace memset( &tr, 0, sizeof(trace_t)); tr.fraction = 1.0f; @@ -115,13 +162,52 @@ bool Impact( Vector &vecOrigin, Vector &vecStart, int iMaterial, int iDamageType VectorMA( vecStart, flLength + 8.0f, shotDir, traceExt ); // Attempt to hit ragdolls - + bool bHitRagdoll = false; - + +#ifdef MAPBASE + if ( !pEntity->IsClientCreated() ) + { + C_BaseAnimating *pRagdoll = FX_AffectRagdolls_GetHit( vecOrigin, vecStart, iDamageType ); + if (pRagdoll) + { + bHitRagdoll = true; + + if (g_ragdoll_client_impact_decals.GetBool() && pRagdoll->IsRagdoll()) + { + pEntity = pRagdoll; + + // HACKHACK: Get the ragdoll's nearest bone for its material + int iNearestMaterial = 0; + float flNearestDistSqr = FLT_MAX; + + IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + int count = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); + for ( int i = 0; i < count; i++ ) + { + Vector vecPosition; + QAngle angAngles; + pList[i]->GetPosition( &vecPosition, &angAngles ); + float flDistSqr = (vecStart - vecPosition).LengthSqr(); + if (flDistSqr < flNearestDistSqr) + { + iNearestMaterial = pList[i]->GetMaterialIndex(); + flNearestDistSqr = flDistSqr; + } + } + + // Get the material from the surfaceprop + surfacedata_t *psurfaceData = physprops->GetSurfaceData( iNearestMaterial ); + iMaterial = psurfaceData->game.material; + } + } + } +#else if ( !pEntity->IsClientCreated() ) { bHitRagdoll = FX_AffectRagdolls( vecOrigin, vecStart, iDamageType ); } +#endif if ( (nFlags & IMPACT_NODECAL) == 0 ) { diff --git a/sp/src/game/client/fx_impact.h b/sp/src/game/client/fx_impact.h index ad57f7e7cb..9c6cb875fc 100644 --- a/sp/src/game/client/fx_impact.h +++ b/sp/src/game/client/fx_impact.h @@ -58,12 +58,21 @@ class CRagdollEnumerator : public IPartitionEnumerator // Actual work code virtual IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ); +#ifdef MAPBASE + bool Hit( void ) const { return m_pHitEnt != NULL; } + C_BaseAnimating *GetHit( void ) { return m_pHitEnt; } +#else bool Hit( void ) const { return m_bHit; } +#endif private: Ray_t m_rayShot; int m_iDamageType; +#ifdef MAPBASE + C_BaseAnimating *m_pHitEnt; +#else bool m_bHit; +#endif }; #endif // FX_IMPACT_H diff --git a/sp/src/game/client/game_controls/baseviewport.cpp b/sp/src/game/client/game_controls/baseviewport.cpp index 0b99ef6348..a098662094 100644 --- a/sp/src/game/client/game_controls/baseviewport.cpp +++ b/sp/src/game/client/game_controls/baseviewport.cpp @@ -146,6 +146,32 @@ bool CBaseViewport::LoadHudAnimations( void ) return true; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Reloads HUD animations after loading a map-specific HUD animations file. +//----------------------------------------------------------------------------- +void CBaseViewport::ReloadHudAnimations( void ) +{ + // Force a reload + if ( LoadHudAnimations() == false ) + { + // Fall back to just the main + if ( m_pAnimController->SetScriptFile( GetVPanel(), "scripts/HudAnimations.txt", true ) == false ) + { + Assert(0); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Loads a map-specific HUD animations file. +//----------------------------------------------------------------------------- +bool CBaseViewport::LoadCustomHudAnimations( const char *pszFile ) +{ + return m_pAnimController->SetScriptFile( GetVPanel(), pszFile, true ); +} +#endif + //================================================================ CBaseViewport::CBaseViewport() : vgui::EditablePanel( NULL, "CBaseViewport") { diff --git a/sp/src/game/client/game_controls/baseviewport.h b/sp/src/game/client/game_controls/baseviewport.h index 4e4c64e804..cbc602a0ed 100644 --- a/sp/src/game/client/game_controls/baseviewport.h +++ b/sp/src/game/client/game_controls/baseviewport.h @@ -73,6 +73,10 @@ class CBaseViewport : public vgui::EditablePanel, public IViewPort, public IGame public: // IGameEventListener: virtual void FireGameEvent( IGameEvent * event); +#ifdef MAPBASE + bool LoadCustomHudAnimations( const char *pszFile ); + void ReloadHudAnimations( void ); +#endif protected: diff --git a/sp/src/game/client/glow_outline_effect.h b/sp/src/game/client/glow_outline_effect.h index aac399d734..111328620d 100644 --- a/sp/src/game/client/glow_outline_effect.h +++ b/sp/src/game/client/glow_outline_effect.h @@ -150,6 +150,10 @@ class CGlowObjectManager static const int ENTRY_IN_USE = -2; }; +#ifdef MAPBASE_VSCRIPT + // For unregistration boundary check +public: +#endif CUtlVector< GlowObjectDefinition_t > m_GlowObjectDefinitions; int m_nFirstFreeSlot; }; diff --git a/sp/src/game/client/hl2/c_basehlplayer.cpp b/sp/src/game/client/hl2/c_basehlplayer.cpp index 17b3cf537e..15a33ea8c2 100644 --- a/sp/src/game/client/hl2/c_basehlplayer.cpp +++ b/sp/src/game/client/hl2/c_basehlplayer.cpp @@ -31,6 +31,9 @@ ConVar cl_npc_speedmod_outtime( "cl_npc_speedmod_outtime", "1.5", FCVAR_CLIENTDL IMPLEMENT_CLIENTCLASS_DT(C_BaseHLPlayer, DT_HL2_Player, CHL2_Player) RecvPropDataTable( RECVINFO_DT(m_HL2Local),0, &REFERENCE_RECV_TABLE(DT_HL2Local) ), RecvPropBool( RECVINFO( m_fIsSprinting ) ), +#ifdef SP_ANIM_STATE + RecvPropFloat( RECVINFO( m_flAnimRenderYaw ) ), +#endif END_RECV_TABLE() BEGIN_PREDICTION_DATA( C_BaseHLPlayer ) @@ -90,6 +93,13 @@ void C_BaseHLPlayer::OnDataChanged( DataUpdateType_t updateType ) SetNextClientThink( CLIENT_THINK_ALWAYS ); } +#ifdef SP_ANIM_STATE + if (m_flAnimRenderYaw != FLT_MAX) + { + m_angAnimRender = QAngle( 0, m_flAnimRenderYaw, 0 ); + } +#endif + BaseClass::OnDataChanged( updateType ); } @@ -657,3 +667,21 @@ void C_BaseHLPlayer::BuildTransformations( CStudioHdr *hdr, Vector *pos, Quatern BuildFirstPersonMeathookTransformations( hdr, pos, q, cameraTransform, boneMask, boneComputed, "ValveBiped.Bip01_Head1" ); } + +#ifdef SP_ANIM_STATE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const QAngle& C_BaseHLPlayer::GetRenderAngles( void ) +{ + if ( m_flAnimRenderYaw != FLT_MAX ) + { + return m_angAnimRender; + } + else + { + return BaseClass::GetRenderAngles(); + } +} +#endif + diff --git a/sp/src/game/client/hl2/c_basehlplayer.h b/sp/src/game/client/hl2/c_basehlplayer.h index fe838cad76..368c9bb12c 100644 --- a/sp/src/game/client/hl2/c_basehlplayer.h +++ b/sp/src/game/client/hl2/c_basehlplayer.h @@ -15,6 +15,10 @@ #include "c_baseplayer.h" #include "c_hl2_playerlocaldata.h" +#if !defined( HL2MP ) && defined ( MAPBASE ) +#include "mapbase/singleplayer_animstate.h" +#endif + class C_BaseHLPlayer : public C_BasePlayer { public: @@ -58,6 +62,10 @@ class C_BaseHLPlayer : public C_BasePlayer bool IsWeaponLowered( void ) { return m_HL2Local.m_bWeaponLowered; } +#ifdef SP_ANIM_STATE + virtual const QAngle& GetRenderAngles( void ); +#endif + public: C_HL2PlayerLocalData m_HL2Local; @@ -78,7 +86,13 @@ class C_BaseHLPlayer : public C_BasePlayer bool m_bPlayUseDenySound; // Signaled by PlayerUse, but can be unset by HL2 ladder code... float m_flSpeedMod; float m_flExitSpeedMod; - + +#ifdef SP_ANIM_STATE + // At the moment, we network the render angles since almost none of the player anim stuff is done on the client in SP. + // If any of this is ever adapted for MP, this method should be replaced with replicating/moving the anim state to the client. + float m_flAnimRenderYaw; + QAngle m_angAnimRender; +#endif friend class CHL2GameMovement; }; diff --git a/sp/src/game/client/hl2/hud_credits.cpp b/sp/src/game/client/hl2/hud_credits.cpp index 280e8a44de..6520e6b4f8 100644 --- a/sp/src/game/client/hl2/hud_credits.cpp +++ b/sp/src/game/client/hl2/hud_credits.cpp @@ -32,6 +32,16 @@ struct creditname_t float flTimeAdd; float flTimeStart; int iSlot; + +#ifdef MAPBASE + // New credits stuff + + Color cColorOverride; + + // Images + int iImageID = -1; + float flImageScale = 1.0f; +#endif }; #define CREDITS_FILE "scripts/credits.txt" @@ -93,6 +103,10 @@ class CHudCredits : public CHudElement, public vgui::Panel void DrawOutroCreditsName( void ); void DrawIntroCreditsName( void ); void DrawLogo( void ); +#ifdef MAPBASE + void DrawOutroCreditFont( const char *pCreditName, float flYPos, vgui::HFont hTFont, const Color &cColor, int iScreenWidth, int iDivisor = 2 ); + void DrawOutroCreditTexture( int iImageID, float flYPos, float flImageScale, const Color &cColor, int iScreenWidth, int iDivisor = 2 ); +#endif void PrepareLogo( float flTime ); void PrepareOutroCredits( void ); @@ -102,6 +116,10 @@ class CHudCredits : public CHudElement, public vgui::Panel void PrepareLine( vgui::HFont hFont, char const *pchLine ); +#ifdef MAPBASE + int GetOrAllocateImageID( const char *szFileName ); +#endif + CPanelAnimationVar( vgui::HFont, m_hTextFont, "TextFont", "Default" ); CPanelAnimationVar( Color, m_TextColor, "TextColor", "FgColor" ); @@ -110,7 +128,10 @@ class CHudCredits : public CHudElement, public vgui::Panel float m_flScrollTime; float m_flSeparation; #ifdef MAPBASE - int m_iEndLines; + int m_iEndLines; + float m_flEndLinesFadeHoldTime; + bool m_bAllowColumns; + CUtlDict m_ImageDict; #endif float m_flFadeTime; bool m_bLastOneInPlace; @@ -202,6 +223,20 @@ void CHudCredits::Clear( void ) m_bLastOneInPlace = false; m_Alpha = m_TextColor[3]; m_iLogoState = LOGO_FADEOFF; + +#ifdef MAPBASE + if ( surface() ) + { + for (int i = m_ImageDict.Count()-1; i >= 0; i--) + { + if (m_ImageDict[i] != -1) + { + surface()->DestroyTextureID( m_ImageDict[i] ); + m_ImageDict.RemoveAt( i ); + } + } + } +#endif } //----------------------------------------------------------------------------- @@ -229,7 +264,11 @@ void CHudCredits::ReadNames( KeyValues *pKeyValue ) { creditname_t Credits; V_strcpy_safe( Credits.szCreditName, pKVNames->GetName() ); +#ifdef MAPBASE + V_strcpy_safe( Credits.szFontName, pKVNames->GetString( (const char *)NULL, "Default" ) ); +#else V_strcpy_safe( Credits.szFontName, pKeyValue->GetString( Credits.szCreditName, "Default" ) ); +#endif m_CreditsList.AddToTail( Credits ); pKVNames = pKVNames->GetNextKey(); @@ -248,6 +287,8 @@ void CHudCredits::ReadParams( KeyValues *pKeyValue ) m_flSeparation = pKeyValue->GetFloat( "separation", 5 ); #ifdef MAPBASE m_iEndLines = pKeyValue->GetInt( "endlines", 1 ); + m_flEndLinesFadeHoldTime = pKeyValue->GetFloat( "endlines_fadeholdtime", ( IsConsole() ? 2.0f : 10.0f ) ); // "360 certification requires that we not hold a static image too long." + m_bAllowColumns = pKeyValue->GetBool( "allow_columns", false ); #endif m_flFadeInTime = pKeyValue->GetFloat( "fadeintime", 1 ); @@ -300,10 +341,46 @@ void CHudCredits::DrawOutroCreditsName( void ) if ( pCredit == NULL ) continue; +#ifdef MAPBASE + vgui::HScheme scheme = GetScheme(); +#else vgui::HScheme scheme = vgui::scheme()->GetScheme( "ClientScheme" ); - vgui::HFont m_hTFont = vgui::scheme()->GetIScheme(scheme)->GetFont( pCredit->szFontName, true ); +#endif + vgui::HFont m_hTFont = INVALID_FONT; + + int iFontTall = 1; + +#ifdef MAPBASE + if (pCredit->iImageID != -1) + { + // Get the size of the tallest image if there's multiple + int iFontWide; + if (m_bAllowColumns && V_strstr( pCredit->szCreditName, "\t" )) + { + CUtlStringList outStrings; + V_SplitString( pCredit->szCreditName, "\t", outStrings ); + FOR_EACH_VEC( outStrings, i ) + { + int iTempTall; + surface()->DrawGetTextureSize( GetOrAllocateImageID( outStrings[i] ), iFontWide, iTempTall ); + if (iTempTall > iFontTall) + iFontTall = iTempTall; + } + outStrings.PurgeAndDeleteElements(); + } + else + { + surface()->DrawGetTextureSize( GetOrAllocateImageID( pCredit->szCreditName ), iFontWide, iFontTall ); + } - int iFontTall = surface()->GetFontTall ( m_hTFont ); + iFontTall = ((float)iFontTall * pCredit->flImageScale); + } + else +#endif + { + m_hTFont = vgui::scheme()->GetIScheme( scheme )->GetFont( pCredit->szFontName, true ); + iFontTall = surface()->GetFontTall( m_hTFont ); + } if ( pCredit->flYPos < -iFontTall || pCredit->flYPos > iTall ) { @@ -317,6 +394,9 @@ void CHudCredits::DrawOutroCreditsName( void ) Color cColor = m_TextColor; #ifdef MAPBASE + if (pCredit->cColorOverride.a() > 0) + cColor = pCredit->cColorOverride; + // Some lines should stick around and fade out if ( i >= m_CreditsList.Count()-m_iEndLines ) #else @@ -333,8 +413,12 @@ void CHudCredits::DrawOutroCreditsName( void ) { m_bLastOneInPlace = true; +#ifdef MAPBASE + m_flFadeTime = gpGlobals->curtime + m_flEndLinesFadeHoldTime; +#else // 360 certification requires that we not hold a static image too long. m_flFadeTime = gpGlobals->curtime + ( IsConsole() ? 2.0f : 10.0f ); +#endif } } else @@ -364,6 +448,50 @@ void CHudCredits::DrawOutroCreditsName( void ) if ( pCredit->bActive == false ) continue; +#ifdef MAPBASE + // Credits separated by tabs should appear divided + if (m_bAllowColumns && V_strstr( pCredit->szCreditName, "\t" )) + { + CUtlStringList outStrings; + V_SplitString( pCredit->szCreditName, "\t", outStrings ); + int iDivisor = 1 + outStrings.Count(); + if (pCredit->iImageID != -1) + { + FOR_EACH_VEC( outStrings, i ) + { + int iImageID = GetOrAllocateImageID( outStrings[i] ); + + // Center the image if needed + int iImageWide, iImageTall = 1; + surface()->DrawGetTextureSize( iImageID, iImageWide, iImageTall ); + if (iImageTall < iFontTall) + { + DrawOutroCreditTexture( iImageID, pCredit->flYPos + ((iFontTall * 0.5f) - (iImageTall * 0.5f)), pCredit->flImageScale, cColor, iWidth*(i + 1), iDivisor ); + } + else + { + DrawOutroCreditTexture( iImageID, pCredit->flYPos, pCredit->flImageScale, cColor, iWidth*(i + 1), iDivisor ); + } + } + } + else + { + FOR_EACH_VEC( outStrings, i ) + { + DrawOutroCreditFont( outStrings[i], pCredit->flYPos, m_hTFont, cColor, iWidth*(i + 1), iDivisor ); + } + } + outStrings.PurgeAndDeleteElements(); + } + else if (pCredit->iImageID != -1) + { + DrawOutroCreditTexture( pCredit->iImageID, pCredit->flYPos, pCredit->flImageScale, cColor, iWidth, 2 ); + } + else + { + DrawOutroCreditFont( pCredit->szCreditName, pCredit->flYPos, m_hTFont, cColor, iWidth, 2 ); + } +#else surface()->DrawSetTextFont( m_hTFont ); surface()->DrawSetTextColor( cColor[0], cColor[1], cColor[2], cColor[3] ); @@ -382,9 +510,56 @@ void CHudCredits::DrawOutroCreditsName( void ) surface()->DrawSetTextPos( ( iWidth / 2 ) - ( iStringWidth / 2 ), pCredit->flYPos ); surface()->DrawUnicodeString( unicode ); +#endif } } +#ifdef MAPBASE +void CHudCredits::DrawOutroCreditFont( const char *pCreditName, float flYPos, vgui::HFont hTFont, const Color &cColor, int iScreenWidth, int iDivisor ) +{ + surface()->DrawSetTextFont( hTFont ); + surface()->DrawSetTextColor( cColor[0], cColor[1], cColor[2], cColor[3] ); + + wchar_t unicode[256]; + + if ( pCreditName[0] == '#' ) + { + g_pVGuiLocalize->ConstructString( unicode, sizeof(unicode), g_pVGuiLocalize->Find(pCreditName), 0 ); + } + else + { + g_pVGuiLocalize->ConvertANSIToUnicode( pCreditName, unicode, sizeof( unicode ) ); + } + + int iStringWidth = GetStringPixelWidth( unicode, hTFont ); + + // ((iScreenWidth*iMultiplier) / iDivisor) + // When needed, just multiply iScreenWidth before sending to the function + surface()->DrawSetTextPos( (iScreenWidth / iDivisor) - (iStringWidth / 2), flYPos ); + surface()->DrawUnicodeString( unicode ); +} + +void CHudCredits::DrawOutroCreditTexture( int iImageID, float flYPos, float flImageScale, const Color &cColor, int iScreenWidth, int iDivisor ) +{ + int iImageWide, iImageTall; + surface()->DrawGetTextureSize( iImageID, iImageWide, iImageTall ); + + // Scale for resolution + flImageScale *= ((float)GetTall() / 900.0f); + + iImageWide = ((float)(iImageWide) * flImageScale); + iImageTall = ((float)(iImageTall) * flImageScale); + + iImageWide /= 2; + //iImageTall /= 2; + iScreenWidth /= iDivisor; + + surface()->DrawSetColor( cColor ); + surface()->DrawSetTexture( iImageID ); + surface()->DrawTexturedRect( iScreenWidth - iImageWide, flYPos, iScreenWidth + iImageWide, flYPos + iImageTall ); +} +#endif + void CHudCredits::DrawLogo( void ) { if( m_iLogoState == LOGO_FADEOFF ) @@ -464,7 +639,11 @@ void CHudCredits::DrawLogo( void ) Q_snprintf( szLogoFont, sizeof( szLogoFont ), "WeaponIcons" ); } +#ifdef MAPBASE + vgui::HScheme scheme = GetScheme(); +#else vgui::HScheme scheme = vgui::scheme()->GetScheme( "ClientScheme" ); +#endif vgui::HFont m_hTFont = vgui::scheme()->GetIScheme(scheme)->GetFont( szLogoFont ); int iFontTall = surface()->GetFontTall ( m_hTFont ); @@ -544,14 +723,26 @@ void CHudCredits::DrawIntroCreditsName( void ) if ( pCredit->bActive == false ) continue; - + +#ifdef MAPBASE + vgui::HScheme scheme = GetScheme(); +#else vgui::HScheme scheme = vgui::scheme()->GetScheme( "ClientScheme" ); +#endif vgui::HFont m_hTFont = vgui::scheme()->GetIScheme(scheme)->GetFont( pCredit->szFontName ); float localTime = gpGlobals->curtime - pCredit->flTimeStart; surface()->DrawSetTextFont( m_hTFont ); +#ifdef MAPBASE + Color cColor = m_cColor; + if (pCredit->cColorOverride.a() > 0) + cColor = pCredit->cColorOverride; + + surface()->DrawSetTextColor( cColor[0], cColor[1], cColor[2], FadeBlend( m_flFadeInTime, m_flFadeOutTime, m_flFadeHoldTime + pCredit->flTimeAdd, localTime ) * cColor[3] ); +#else surface()->DrawSetTextColor( m_cColor[0], m_cColor[1], m_cColor[2], FadeBlend( m_flFadeInTime, m_flFadeOutTime, m_flFadeHoldTime + pCredit->flTimeAdd, localTime ) * m_cColor[3] ); +#endif wchar_t unicode[256]; g_pVGuiLocalize->ConvertANSIToUnicode( pCredit->szCreditName, unicode, sizeof( unicode ) ); @@ -692,17 +883,119 @@ void CHudCredits::PrepareOutroCredits( void ) if ( pCredit == NULL ) continue; +#ifdef MAPBASE + vgui::HScheme scheme = GetScheme(); +#else vgui::HScheme scheme = vgui::scheme()->GetScheme( "ClientScheme" ); - vgui::HFont m_hTFont = vgui::scheme()->GetIScheme(scheme)->GetFont( pCredit->szFontName, true ); +#endif - pCredit->flYPos = iHeight; - pCredit->bActive = false; +#ifdef MAPBASE + if (pCredit->szFontName[0] == '$') + { + if (V_strncmp( pCredit->szFontName + 1, "Image", 5 ) == 0) + { + if (pCredit->szFontName[6] == ';') + { + CUtlStringList outStrings; + V_SplitString( pCredit->szFontName, ";", outStrings ); + FOR_EACH_VEC( outStrings, i ) + { + switch (i) + { + // Get scale + case 1: + pCredit->flImageScale = atof( outStrings[i] ); + break; + + // Get color + case 2: + int tmp[4]; + UTIL_StringToIntArray( tmp, 4, outStrings[i] ); + pCredit->cColorOverride = Color( tmp[0], tmp[1], tmp[2], tmp[3] ); + break; + } + } + outStrings.PurgeAndDeleteElements(); + } + + // Get the size of the tallest image if there's multiple + int iFontWide, iFontTall = 1; + if (m_bAllowColumns && V_strstr( pCredit->szCreditName, "\t" )) + { + CUtlStringList outStrings; + V_SplitString( pCredit->szCreditName, "\t", outStrings ); + FOR_EACH_VEC( outStrings, i ) + { + pCredit->iImageID = GetOrAllocateImageID( outStrings[i] ); - iHeight += surface()->GetFontTall ( m_hTFont ) + m_flSeparation; + int iTempTall; + surface()->DrawGetTextureSize( pCredit->iImageID, iFontWide, iTempTall ); + if (iTempTall > iFontTall) + iFontTall = iTempTall; + } + outStrings.PurgeAndDeleteElements(); + } + else + { + pCredit->iImageID = GetOrAllocateImageID( pCredit->szCreditName ); + surface()->DrawGetTextureSize( pCredit->iImageID, iFontWide, iFontTall ); + } - PrepareLine( m_hTFont, pCredit->szCreditName ); + pCredit->flYPos = iHeight; + pCredit->bActive = false; + + iHeight += ((float)iFontTall * pCredit->flImageScale * ((float)GetTall() / 900.0f)) + m_flSeparation; + + Msg( "'%s' is image type (image scale is %f)\n", pCredit->szCreditName, pCredit->flImageScale ); + } + else + { + //Msg( "'%s' is not an image type\n", pCredit->szFontName + 1 ); + } + } + else +#endif + { +#ifdef MAPBASE + if (V_strstr( pCredit->szFontName, ";" )) + { + CUtlStringList outStrings; + V_SplitString( pCredit->szFontName, ";", outStrings ); + FOR_EACH_VEC( outStrings, i ) + { + switch (i) + { + // Get color + case 1: + int tmp[4]; + UTIL_StringToIntArray( tmp, 4, outStrings[i] ); + pCredit->cColorOverride = Color( tmp[0], tmp[1], tmp[2], tmp[3] ); + break; + } + } + + Q_strncpy( pCredit->szFontName, outStrings[0], sizeof( pCredit->szFontName ) ); + outStrings.PurgeAndDeleteElements(); + } +#endif + + vgui::HFont m_hTFont = vgui::scheme()->GetIScheme( scheme )->GetFont( pCredit->szFontName, true ); + + pCredit->flYPos = iHeight; + pCredit->bActive = false; + + iHeight += surface()->GetFontTall ( m_hTFont ) + m_flSeparation; + + PrepareLine( m_hTFont, pCredit->szCreditName ); + } } +#ifdef MAPBASE + // Check if the last line has a color override. If it does, use that as the alpha for the fadeout + if (m_CreditsList.Tail().cColorOverride.a() != 0) + m_Alpha = m_CreditsList.Tail().cColorOverride.a(); +#endif + SetActive( true ); g_iCreditsPixelHeight = iHeight; @@ -721,7 +1014,34 @@ void CHudCredits::PrepareIntroCredits( void ) if ( pCredit == NULL ) continue; +#ifdef MAPBASE + if (V_strstr( pCredit->szFontName, ";" )) + { + CUtlStringList outStrings; + V_SplitString( pCredit->szFontName, ";", outStrings ); + FOR_EACH_VEC( outStrings, i ) + { + switch (i) + { + // Get color + case 1: + int tmp[4]; + UTIL_StringToIntArray( tmp, 4, outStrings[i] ); + pCredit->cColorOverride = Color( tmp[0], tmp[1], tmp[2], tmp[3] ); + break; + } + } + + Q_strncpy( pCredit->szFontName, outStrings[0], sizeof( pCredit->szFontName ) ); + outStrings.PurgeAndDeleteElements(); + } +#endif + +#ifdef MAPBASE + vgui::HScheme scheme = GetScheme(); +#else vgui::HScheme scheme = vgui::scheme()->GetScheme( "ClientScheme" ); +#endif vgui::HFont m_hTFont = vgui::scheme()->GetIScheme(scheme)->GetFont( pCredit->szFontName ); pCredit->flYPos = m_flY + ( iSlot * surface()->GetFontTall ( m_hTFont ) ); @@ -749,6 +1069,21 @@ void CHudCredits::PrepareIntroCredits( void ) SetActive( true ); } +#ifdef MAPBASE +int CHudCredits::GetOrAllocateImageID( const char *szFileName ) +{ + int iIndex = m_ImageDict.Find( szFileName ); + if (iIndex == m_ImageDict.InvalidIndex()) + { + iIndex = surface()->CreateNewTextureID(); + m_ImageDict.Insert( szFileName, iIndex ); + surface()->DrawSetTextureFile( iIndex, szFileName, true, false ); + return iIndex; + } + return m_ImageDict[iIndex]; +} +#endif + void CHudCredits::MsgFunc_CreditsMsg( bf_read &msg ) { m_iCreditsType = msg.ReadByte(); diff --git a/sp/src/game/client/hud_closecaption.cpp b/sp/src/game/client/hud_closecaption.cpp index 8a5ddd551c..e2d718c987 100644 --- a/sp/src/game/client/hud_closecaption.cpp +++ b/sp/src/game/client/hud_closecaption.cpp @@ -1302,7 +1302,7 @@ void CHudCloseCaption::Reset( void ) Unlock(); } -bool CHudCloseCaption::SplitCommand( wchar_t const **ppIn, wchar_t *cmd, wchar_t *args ) const +bool CHudCloseCaption::SplitCommand( wchar_t const **ppIn, wchar_t *cmd, wchar_t *args, int size ) const { const wchar_t *in = *ppIn; const wchar_t *oldin = in; @@ -1317,8 +1317,11 @@ bool CHudCloseCaption::SplitCommand( wchar_t const **ppIn, wchar_t *cmd, wchar_t cmd[ 0 ]= 0; wchar_t *out = cmd; in++; - while ( *in != L'\0' && *in != L':' && *in != L'>' && !isspace( *in ) ) + while ( *in != L'\0' && *in != L':' && *in != L'>' && !V_isspace( *in ) ) { + if ( (int)( out - cmd ) + (int)sizeof( wchar_t ) >= size ) + break; + *out++ = *in++; } *out = L'\0'; @@ -1333,6 +1336,9 @@ bool CHudCloseCaption::SplitCommand( wchar_t const **ppIn, wchar_t *cmd, wchar_t out = args; while ( *in != L'\0' && *in != L'>' ) { + if ( (int)( out - args ) + (int)sizeof( wchar_t ) >= size ) + break; + *out++ = *in++; } *out = L'\0'; @@ -1360,7 +1366,7 @@ bool CHudCloseCaption::GetFloatCommandValue( const wchar_t *stream, const wchar_ wchar_t cmd[ 256 ]; wchar_t args[ 256 ]; - if ( SplitCommand( &curpos, cmd, args ) ) + if ( SplitCommand( &curpos, cmd, args, sizeof( cmd ) ) ) { if ( !wcscmp( cmd, findcmd ) ) { @@ -1384,7 +1390,7 @@ bool CHudCloseCaption::StreamHasCommand( const wchar_t *stream, const wchar_t *f wchar_t cmd[ 256 ]; wchar_t args[ 256 ]; - if ( SplitCommand( &curpos, cmd, args ) ) + if ( SplitCommand( &curpos, cmd, args, sizeof( cmd ) ) ) { if ( !wcscmp( cmd, findcmd ) ) { @@ -1423,7 +1429,7 @@ bool CHudCloseCaption::StreamHasCommand( const wchar_t *stream, const wchar_t *s wchar_t cmd[ 256 ]; wchar_t args[ 256 ]; - if ( SplitCommand( &curpos, cmd, args ) ) + if ( SplitCommand( &curpos, cmd, args, sizeof( cmd ) ) ) { if ( !wcscmp( cmd, search ) ) { @@ -1515,7 +1521,7 @@ void CHudCloseCaption::Process( const wchar_t *stream, float duration, const cha const wchar_t *prevpos = curpos; - if ( SplitCommand( &curpos, cmd, args ) ) + if ( SplitCommand( &curpos, cmd, args, sizeof( cmd ) ) ) { if ( !wcscmp( cmd, L"delay" ) ) { @@ -1612,6 +1618,9 @@ struct WorkUnitParams clr = Color( 255, 255, 255, 255 ); newline = false; font = 0; +#ifdef MAPBASE + customFont = false; +#endif } ~WorkUnitParams() @@ -1657,6 +1666,9 @@ struct WorkUnitParams Color clr; bool newline; vgui::HFont font; +#ifdef MAPBASE + bool customFont; +#endif }; void CHudCloseCaption::AddWorkUnit( CCloseCaptionItem *item, @@ -1716,7 +1728,7 @@ void CHudCloseCaption::ComputeStreamWork( int available_width, CCloseCaptionItem wchar_t cmd[ 256 ]; wchar_t args[ 256 ]; - if ( SplitCommand( &curpos, cmd, args ) ) + if ( SplitCommand( &curpos, cmd, args, sizeof( cmd ) ) ) { if ( !wcscmp( cmd, L"cr" ) ) { @@ -1771,27 +1783,58 @@ void CHudCloseCaption::ComputeStreamWork( int available_width, CCloseCaptionItem { AddWorkUnit( item, params ); params.italic = !params.italic; +#ifdef MAPBASE + params.customFont = false; +#endif } else if ( !wcscmp( cmd, L"B" ) ) { AddWorkUnit( item, params ); params.bold = !params.bold; +#ifdef MAPBASE + params.customFont = false; +#endif } +#ifdef MAPBASE + else if ( !wcscmp( cmd, L"font" ) ) + { + AddWorkUnit( item, params ); + vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); + + if ( args[0] != 0 ) + { + char font[64]; + g_pVGuiLocalize->ConvertUnicodeToANSI( args, font, sizeof( font ) ); + params.font = pScheme->GetFont( font ); + params.customFont = true; + } + else + { + params.customFont = false; + } + } +#endif continue; } - int font; - if ( IsPC() ) - { - font = params.GetFontNumber(); - } - else + vgui::HFont useF = params.font; +#ifdef MAPBASE + if (params.customFont == false) +#endif { - font = streamlen >= cc_smallfontlength.GetInt() ? CCFONT_SMALL : CCFONT_NORMAL; + int font; + if ( IsPC() ) + { + font = params.GetFontNumber(); + } + else + { + font = streamlen >= cc_smallfontlength.GetInt() ? CCFONT_SMALL : CCFONT_NORMAL; + } + useF = m_hFonts[font]; + params.font = useF; } - vgui::HFont useF = m_hFonts[font]; - params.font = useF; int w, h; @@ -1939,7 +1982,7 @@ bool CHudCloseCaption::GetNoRepeatValue( const wchar_t *caption, float &retval ) wchar_t cmd[ 256 ]; wchar_t args[ 256 ]; - if ( SplitCommand( &curpos, cmd, args ) ) + if ( SplitCommand( &curpos, cmd, args, sizeof( cmd ) ) ) { if ( !wcscmp( cmd, L"norepeat" ) ) { @@ -2630,6 +2673,124 @@ void CHudCloseCaption::InitCaptionDictionary( const char *dbfile ) g_AsyncCaptionResourceManager.SetDbInfo( m_AsyncCaptions ); } +#ifdef MAPBASE +void CHudCloseCaption::AddAdditionalCaptionDictionary( const char *dbfile, CUtlVector &outPathSymbols ) +{ + CGMsg( 1, CON_GROUP_MAPBASE_MISC, "Adding additional caption dictionary \"%s\"\n", dbfile ); + + g_AsyncCaptionResourceManager.Clear(); + + char searchPaths[4096]; + filesystem->GetSearchPath( "MOD", true, searchPaths, sizeof( searchPaths ) ); + + for ( char *path = strtok( searchPaths, ";" ); path; path = strtok( NULL, ";" ) ) + { + if ( IsX360() && ( filesystem->GetDVDMode() == DVDMODE_STRICT ) && !V_stristr( path, ".zip" ) ) + { + // only want zip paths + continue; + } + + char fullpath[MAX_PATH]; + Q_snprintf( fullpath, sizeof( fullpath ), "%s%s", path, dbfile ); + Q_FixSlashes( fullpath ); + + if ( IsX360() ) + { + char fullpath360[MAX_PATH]; + UpdateOrCreateCaptionFile( fullpath, fullpath360, sizeof( fullpath360 ) ); + Q_strncpy( fullpath, fullpath360, sizeof( fullpath ) ); + } + + // Seach for this dictionary. If it already exists, remove it. + for (int i = 0; i < m_AsyncCaptions.Count(); ++i) + { + if (FStrEq( m_AsyncCaptions[i].m_DataBaseFile.String(), fullpath )) + { + m_AsyncCaptions.Remove( i ); + break; + } + } + + FileHandle_t fh = filesystem->Open( fullpath, "rb" ); + if ( FILESYSTEM_INVALID_HANDLE != fh ) + { + MEM_ALLOC_CREDIT(); + + CUtlBuffer dirbuffer; + + AsyncCaption_t& entry = m_AsyncCaptions[ m_AsyncCaptions.AddToTail() ]; + + // Read the header + filesystem->Read( &entry.m_Header, sizeof( entry.m_Header ), fh ); + if ( entry.m_Header.magic != COMPILED_CAPTION_FILEID ) + Error( "Invalid file id for %s\n", fullpath ); + if ( entry.m_Header.version != COMPILED_CAPTION_VERSION ) + Error( "Invalid file version for %s\n", fullpath ); + if ( entry.m_Header.directorysize < 0 || entry.m_Header.directorysize > 64 * 1024 ) + Error( "Invalid directory size %d for %s\n", entry.m_Header.directorysize, fullpath ); + //if ( entry.m_Header.blocksize != MAX_BLOCK_SIZE ) + // Error( "Invalid block size %d, expecting %d for %s\n", entry.m_Header.blocksize, MAX_BLOCK_SIZE, fullpath ); + + int directoryBytes = entry.m_Header.directorysize * sizeof( CaptionLookup_t ); + entry.m_CaptionDirectory.EnsureCapacity( entry.m_Header.directorysize ); + dirbuffer.EnsureCapacity( directoryBytes ); + + filesystem->Read( dirbuffer.Base(), directoryBytes, fh ); + filesystem->Close( fh ); + + entry.m_CaptionDirectory.CopyArray( (const CaptionLookup_t *)dirbuffer.PeekGet(), entry.m_Header.directorysize ); + entry.m_CaptionDirectory.RedoSort( true ); + + entry.m_DataBaseFile = fullpath; + outPathSymbols.AddToTail( entry.m_DataBaseFile ); + } + } + + g_AsyncCaptionResourceManager.SetDbInfo( m_AsyncCaptions ); +} + +void CHudCloseCaption::AddCustomCaptionFile( char const *file, CUtlVector &outPathSymbols ) +{ + // + // 'file' should be something like "maps/mapbase_demo01_closecaption_%language%" + // + + CGMsg( 1, CON_GROUP_MAPBASE_MISC, "Adding custom caption file \"%s\"\n", file ); + + if (!IsX360()) + { + g_pVGuiLocalize->AddFile( file, "MOD", true ); + } + + char uilanguage[64]; + engine->GetUILanguage( uilanguage, sizeof( uilanguage ) ); + + char dbfile[512]; + V_StrSubst( file, "%language%", uilanguage, dbfile, sizeof( dbfile ) ); + V_SetExtension( dbfile, ".dat", sizeof( dbfile ) ); + AddAdditionalCaptionDictionary( dbfile, outPathSymbols ); +} + +void CHudCloseCaption::RemoveCaptionDictionary( const CUtlSymbol &dbFileSymbol ) +{ + // + // 'file' should be something like "maps/mapbase_demo01_closecaption_%language%" + // + + CGMsg( 1, CON_GROUP_MAPBASE_MISC, "Removing custom caption file \"%s\"\n", dbFileSymbol.String() ); + + for (int i = 0; i < m_AsyncCaptions.Count(); ++i) + { + if ( m_AsyncCaptions[i].m_DataBaseFile == dbFileSymbol ) + { + m_AsyncCaptions.Remove( i ); + break; + } + } +} +#endif + void CHudCloseCaption::OnFinishAsyncLoad( int nFileIndex, int nBlockNum, AsyncCaptionData_t *pData ) { // Fill in data for all users of pData->m_nBlockNum diff --git a/sp/src/game/client/hud_closecaption.h b/sp/src/game/client/hud_closecaption.h index 180afe9d53..8688e60e04 100644 --- a/sp/src/game/client/hud_closecaption.h +++ b/sp/src/game/client/hud_closecaption.h @@ -111,6 +111,11 @@ class CHudCloseCaption : public CHudElement, public vgui::Panel void PlayRandomCaption(); void InitCaptionDictionary( char const *dbfile ); +#ifdef MAPBASE + void AddAdditionalCaptionDictionary( char const *dbfile, CUtlVector &outPathSymbols ); + void AddCustomCaptionFile( char const *file, CUtlVector &outPathSymbols ); + void RemoveCaptionDictionary( const CUtlSymbol &dbFileSymbol ); +#endif void OnFinishAsyncLoad( int nFileIndex, int nBlockNum, AsyncCaptionData_t *pData ); void Flush(); @@ -133,6 +138,11 @@ class CHudCloseCaption : public CHudElement, public vgui::Panel void FindSound( char const *pchANSI ); +#ifdef MAPBASE + inline bool IsUsingCommentaryDimensions() const { return m_bUsingCommentaryDimensions; } + inline void SetUsingCommentaryDimensions( bool bToggle ) { m_bUsingCommentaryDimensions = bToggle; } +#endif + public: struct CaptionRepeat @@ -169,7 +179,7 @@ class CHudCloseCaption : public CHudElement, public vgui::Panel void DrawStream( wrect_t& rect, wrect_t &rcWindow, CCloseCaptionItem *item, int iFadeLine, float flFadeLineAlpha ); void ComputeStreamWork( int available_width, CCloseCaptionItem *item ); - bool SplitCommand( wchar_t const **ppIn, wchar_t *cmd, wchar_t *args ) const; + bool SplitCommand( wchar_t const **ppIn, wchar_t *cmd, wchar_t *args, int size ) const; bool StreamHasCommand( const wchar_t *stream, const wchar_t *findcmd ) const; bool GetFloatCommandValue( const wchar_t *stream, const wchar_t *findcmd, float& value ) const; @@ -212,6 +222,10 @@ class CHudCloseCaption : public CHudElement, public vgui::Panel bool m_bVisibleDueToDirect; bool m_bPaintDebugInfo; CUtlSymbol m_CurrentLanguage; + +#ifdef MAPBASE + bool m_bUsingCommentaryDimensions; +#endif }; #endif // HUD_CLOSECAPTION_H diff --git a/sp/src/game/client/hud_lcd.cpp b/sp/src/game/client/hud_lcd.cpp index 0f0609d669..d0425d6b80 100644 --- a/sp/src/game/client/hud_lcd.cpp +++ b/sp/src/game/client/hud_lcd.cpp @@ -528,15 +528,15 @@ void CLCD::ShowItems_R( CLCDPage *page, unsigned int dwCurTime, CUtlVector< CLCD { CLCDItem *newItem = NULL; - CLCDItem *item = ag->m_Definition[ r ]; - switch ( item->m_Type ) + CLCDItem *itemLocl = ag->m_Definition[ r ]; + switch ( itemLocl->m_Type ) { default: break; case LCDITEM_TEXT: { - CLCDItemText *text = static_cast< CLCDItemText * >( item ); + CLCDItemText *text = static_cast< CLCDItemText * >( itemLocl ); CUtlString s; s = text->m_OriginalText; Replace( s, prefix, s1 ); @@ -551,7 +551,7 @@ void CLCD::ShowItems_R( CLCDPage *page, unsigned int dwCurTime, CUtlVector< CLCD // text->m_OriginalText = s; - CLCDItemText *copy = static_cast< CLCDItemText * >( page->Alloc( item->m_Type ) ); + CLCDItemText *copy = static_cast< CLCDItemText * >( page->Alloc( itemLocl->m_Type ) ); *copy = *text; copy->m_bActive = true; copy->m_OriginalText = s; @@ -564,8 +564,8 @@ void CLCD::ShowItems_R( CLCDPage *page, unsigned int dwCurTime, CUtlVector< CLCD break; case LCDITEM_ICON: { - CLCDItemIcon *icon = static_cast< CLCDItemIcon * >( item ); - CLCDItemIcon *copy = static_cast< CLCDItemIcon * >( page->Alloc( item->m_Type ) ); + CLCDItemIcon *icon = static_cast< CLCDItemIcon * >( itemLocl ); + CLCDItemIcon *copy = static_cast< CLCDItemIcon * >( page->Alloc( itemLocl->m_Type ) ); *copy = *icon; copy->m_bActive = true; copy->Create( m_lcd ); @@ -1186,16 +1186,16 @@ void CLCD::DumpPlayer() C_Team *team = player->GetTeam(); if ( team ) { - CDescribeData helper( team ); - helper.DumpDescription( team->GetPredDescMap() ); + CDescribeData helperLocl( team ); + helperLocl.DumpDescription( team->GetPredDescMap() ); } Msg( "(playerresource)\n\n" ); if ( g_PR ) { - CDescribeData helper( g_PR ); - helper.DumpDescription( g_PR->GetPredDescMap() ); + CDescribeData helperLocl( g_PR ); + helperLocl.DumpDescription( g_PR->GetPredDescMap() ); } Msg( "(localplayerweapon)\n\n" ); @@ -1203,8 +1203,8 @@ void CLCD::DumpPlayer() C_BaseCombatWeapon *active = player->GetActiveWeapon(); if ( active ) { - CDescribeData helper( active ); - helper.DumpDescription( active->GetPredDescMap() ); + CDescribeData helperLocl( active ); + helperLocl.DumpDescription( active->GetPredDescMap() ); } Msg( "Other replacements:\n\n" ); diff --git a/sp/src/game/client/hud_locator_target.cpp b/sp/src/game/client/hud_locator_target.cpp index 8f0d4c0c0a..8479b07e92 100644 --- a/sp/src/game/client/hud_locator_target.cpp +++ b/sp/src/game/client/hud_locator_target.cpp @@ -10,7 +10,7 @@ #include "iclientmode.h" #include #include -#include +#include #include #include #include diff --git a/sp/src/game/client/hud_locator_target.h b/sp/src/game/client/hud_locator_target.h index 06799325b5..69939dbb84 100644 --- a/sp/src/game/client/hud_locator_target.h +++ b/sp/src/game/client/hud_locator_target.h @@ -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 ); diff --git a/sp/src/game/client/hud_pdump.cpp b/sp/src/game/client/hud_pdump.cpp index 612a18d7e3..56ac16f4d5 100644 --- a/sp/src/game/client/hud_pdump.cpp +++ b/sp/src/game/client/hud_pdump.cpp @@ -21,9 +21,15 @@ static CPDumpPanel *g_pPDumpPanel = NULL; // we pragma'd away in platform.h, so this little compiler specific hack will eliminate those warnings while // retaining our own warning setup...ywb #ifdef WIN32 + +#if _MSC_VER < 1900 #pragma warning( push ) #include #pragma warning( pop ) +#else +#include +#endif + #endif using namespace vgui; diff --git a/sp/src/game/client/iclientmode.h b/sp/src/game/client/iclientmode.h index 2b74f62538..38b60047aa 100644 --- a/sp/src/game/client/iclientmode.h +++ b/sp/src/game/client/iclientmode.h @@ -112,6 +112,11 @@ abstract_class IClientMode virtual bool CanRecordDemo( char *errorMsg, int length ) const = 0; +#ifdef MAPBASE // From Alien Swarm SDK + virtual void OnColorCorrectionWeightsReset( void ) = 0; + virtual float GetColorCorrectionScale( void ) const = 0; +#endif + virtual void ComputeVguiResConditions( KeyValues *pkvConditions ) = 0; //============================================================================= diff --git a/sp/src/game/client/in_joystick.cpp b/sp/src/game/client/in_joystick.cpp index 71d03a0806..a2f73ee54e 100644 --- a/sp/src/game/client/in_joystick.cpp +++ b/sp/src/game/client/in_joystick.cpp @@ -25,7 +25,9 @@ #include "tier0/icommandline.h" #include "inputsystem/iinputsystem.h" #include "inputsystem/ButtonCode.h" +#if _MSC_VER < 1900 #include "math.h" +#endif #include "tier1/convar_serverbounded.h" #include "cam_thirdperson.h" diff --git a/sp/src/game/client/iviewrender.h b/sp/src/game/client/iviewrender.h index c66061aed9..8d797dea53 100644 --- a/sp/src/game/client/iviewrender.h +++ b/sp/src/game/client/iviewrender.h @@ -115,6 +115,13 @@ abstract_class IViewRender virtual void SetScreenOverlayMaterial( IMaterial *pMaterial ) = 0; virtual IMaterial *GetScreenOverlayMaterial( ) = 0; +#ifdef MAPBASE + virtual void SetIndexedScreenOverlayMaterial( int i, IMaterial *pMaterial ) = 0; + virtual IMaterial *GetIndexedScreenOverlayMaterial( int i ) = 0; + virtual void ResetIndexedScreenOverlays() = 0; + virtual int GetMaxIndexedScreenOverlays() const = 0; +#endif + virtual void WriteSaveGameScreenshot( const char *pFilename ) = 0; virtual void WriteSaveGameScreenshotOfSize( const char *pFilename, int width, int height, bool bCreatePowerOf2Padded = false, bool bWriteVTF = false ) = 0; diff --git a/sp/src/game/client/mapbase/c_func_fake_worldportal.cpp b/sp/src/game/client/mapbase/c_func_fake_worldportal.cpp index 9d92675e1d..1cae8ebe56 100644 --- a/sp/src/game/client/mapbase/c_func_fake_worldportal.cpp +++ b/sp/src/game/client/mapbase/c_func_fake_worldportal.cpp @@ -1,6 +1,6 @@ //========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// // -// Purpose: Recreates Portal 2 linked_portal_door functionality using SDK code only. +// Purpose: Recreates Portal 2 linked_portal_door visual functionality using SDK code only. // (basically a combination of point_camera and func_reflective_glass) // // $NoKeywords: $ @@ -120,8 +120,8 @@ C_FuncFakeWorldPortal *IsFakeWorldPortalInView( const CViewSetup& view, cplane_t //----------------------------------------------------------------------------- // Iterates through fake world portals instead of just picking one //----------------------------------------------------------------------------- -C_FuncFakeWorldPortal *NextFakeWorldPortal( C_FuncFakeWorldPortal *pStart, const CViewSetup& view, cplane_t &plane, - const Frustum_t &frustum ) +C_FuncFakeWorldPortal *NextFakeWorldPortal( C_FuncFakeWorldPortal *pStart, const CViewSetup& view, + cplane_t &plane, Vector &vecPlaneOrigin, const Frustum_t &frustum ) { // Early out if no cameras C_FuncFakeWorldPortal *pReflectiveGlass = NULL; @@ -167,6 +167,7 @@ C_FuncFakeWorldPortal *NextFakeWorldPortal( C_FuncFakeWorldPortal *pStart, const if ( !pReflectiveGlass->m_hTargetPlane ) continue; + vecPlaneOrigin = vecOrigin; return pReflectiveGlass; } } diff --git a/sp/src/game/client/mapbase/c_func_fake_worldportal.h b/sp/src/game/client/mapbase/c_func_fake_worldportal.h index 3fc4418b61..1bae2de004 100644 --- a/sp/src/game/client/mapbase/c_func_fake_worldportal.h +++ b/sp/src/game/client/mapbase/c_func_fake_worldportal.h @@ -55,8 +55,8 @@ class C_FuncFakeWorldPortal : public C_BaseEntity //----------------------------------------------------------------------------- C_FuncFakeWorldPortal *IsFakeWorldPortalInView( const CViewSetup& view, cplane_t &plane ); -C_FuncFakeWorldPortal *NextFakeWorldPortal( C_FuncFakeWorldPortal *pStart, const CViewSetup& view, cplane_t &plane, - const Frustum_t &frustum ); +C_FuncFakeWorldPortal *NextFakeWorldPortal( C_FuncFakeWorldPortal *pStart, const CViewSetup& view, + cplane_t &plane, Vector &vecPlaneOrigin, const Frustum_t &frustum ); #endif // C_FUNC_FAKE_WORLDPORTAL diff --git a/sp/src/game/client/mapbase/c_vgui_text_display.cpp b/sp/src/game/client/mapbase/c_vgui_text_display.cpp index c3847ac057..f0d2032da9 100644 --- a/sp/src/game/client/mapbase/c_vgui_text_display.cpp +++ b/sp/src/game/client/mapbase/c_vgui_text_display.cpp @@ -157,11 +157,34 @@ void C_TextDisplayPanel::UpdateText() m_pDisplayTextLabel->SetText( m_hScreenEntity->GetDisplayText() ); //SetSize( m_hScreenEntity->GetTextSize(), m_hScreenEntity->GetTextSize() ); - SetSize( m_hScreenEntity->GetResolution(), m_hScreenEntity->GetResolution() ); - m_pDisplayTextLabel->SetSize( m_hScreenEntity->GetResolution(), m_hScreenEntity->GetResolution() ); //m_pDisplayTextLabel->SetSize( m_hScreenEntity->GetTextSize(), m_hScreenEntity->GetTextSize() ); Label::Alignment iAlignment = m_hScreenEntity->GetContentAlignment(); + + switch (iAlignment) + { + // Use a special scaling method when using a south alignment + case Label::Alignment::a_southwest: + case Label::Alignment::a_south: + case Label::Alignment::a_southeast: + int lW, lT; + m_pDisplayTextLabel->GetContentSize( lW, lT ); + SetSize( m_hScreenEntity->GetResolution(), lT ); + m_pDisplayTextLabel->SetSize( m_hScreenEntity->GetResolution(), lT ); + + float sW, sT; + m_hVGUIScreen->GetSize( sW, sT ); + //Msg( "Screen width: %f, new height: %f\n", sW, sW * (lT / m_hScreenEntity->GetResolution()) ); + m_hVGUIScreen->SetHeight( sW * ((float)lT / (float)m_hScreenEntity->GetResolution()) ); + m_hVGUIScreen->SetPixelHeight( lT ); + break; + + default: + SetSize( m_hScreenEntity->GetResolution(), m_hScreenEntity->GetResolution() ); + m_pDisplayTextLabel->SetSize( m_hScreenEntity->GetResolution(), m_hScreenEntity->GetResolution() ); + break; + } + m_pDisplayTextLabel->SetContentAlignment( iAlignment ); bool bWrap = true; diff --git a/sp/src/game/client/mapbase/mapbase_autocubemap.cpp b/sp/src/game/client/mapbase/mapbase_autocubemap.cpp new file mode 100644 index 0000000000..03602ef2b4 --- /dev/null +++ b/sp/src/game/client/mapbase/mapbase_autocubemap.cpp @@ -0,0 +1,280 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: A utility which automatically generates HDR and LDR cubemaps. +// This has the following purposes: +// +// 1. Allow both HDR and LDR cubemaps to be generated automatically after a map is compiled +// 2. Have a way to batch build cubemaps for several levels at once +// +// Author: Blixibon +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "tier0/icommandline.h" +#include "igamesystem.h" +#include "filesystem.h" +#include "utlbuffer.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern const char *g_MapName; + +ConVar autocubemap_hdr_do_both( "autocubemap_hdr_do_both", "1" ); +ConVar autocubemap_hdr_value( "autocubemap_hdr_value", "2" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CAutoCubemapSystem : public CAutoGameSystem +{ +public: + CAutoCubemapSystem() : CAutoGameSystem( "CAutoCubemapSystem" ) + { + } + + virtual bool Init() + { + const char *pszFile = NULL; + if (CommandLine()->CheckParm( "-autocubemap", &pszFile )) + { + if (!pszFile || pszFile[0] == '\0') + { + // Assume that we just want to autocubemap the first map we load + // (no code here for now) + } + else + { + LoadFile( pszFile ); + } + + // Begin autocubemap with the first level we load + m_bAutoCubemapOnFirstLevel = true; + } + + return true; + } + + virtual void LevelInitPostEntity() + { + if (m_bAutoCubemapActive) + { + if (m_bAutoCubemapBuildingCubemaps) + { + // Check if we need to do the other HDR level + if (autocubemap_hdr_do_both.GetBool() && !m_bAutoCubemapDoingBoth) + { + m_bAutoCubemapBuildingCubemaps = false; + m_bAutoCubemapDoingBoth = true; + + // Change the HDR level and restart the map + //ConVarRef mat_hdr_level( "mat_hdr_level" ); + engine->ClientCmd_Unrestricted( VarArgs( "toggle mat_hdr_level 0 %i; restart", autocubemap_hdr_value.GetInt() ) ); + } + else + { + // Go to the next map + m_bAutoCubemapBuildingCubemaps = false; + m_bAutoCubemapDoingBoth = false; + + m_AutoCubemapMapsIndex++; + if (m_AutoCubemapMapsIndex < m_AutoCubemapMaps.Count()) + { + engine->ClientCmd_Unrestricted( VarArgs( "map %s", m_AutoCubemapMaps[m_AutoCubemapMapsIndex] ) ); + } + else + { + // CUBEMAPPER FINISHED + m_AutoCubemapMaps.PurgeAndDeleteElements(); + m_AutoCubemapMapsIndex = 0; + m_bAutoCubemapActive = false; + + Msg( "CUBEMAPPER FINISHED\n" ); + + if (autocubemap_hdr_do_both.GetBool()) + { + engine->ClientCmd_Unrestricted( VarArgs( "mat_hdr_level %i", m_iAutoCubemapUserHDRLevel ) ); + } + } + } + } + else + { + // Build cubemaps for this map + m_bAutoCubemapBuildingCubemaps = true; + engine->ClientCmd_Unrestricted( "exec buildcubemaps_prep; buildcubemaps" ); + } + } + else if (m_bAutoCubemapOnFirstLevel) + { + // Start autocubemap now + StartAutoCubemap(); + m_bAutoCubemapOnFirstLevel = false; + } + } + + //------------------------------------------------------------------------------------- + + void StartAutoCubemap() + { + if (m_AutoCubemapMaps.Count() <= 0) + { + //Msg("No maps to cubemap with!\n"); + //return; + + // Just do this map + m_AutoCubemapMaps.AddToTail( strdup( g_MapName ) ); + } + + if (autocubemap_hdr_do_both.GetBool()) + { + // Save the user's HDR level + ConVarRef mat_hdr_level( "mat_hdr_level" ); + m_iAutoCubemapUserHDRLevel = mat_hdr_level.GetInt(); + } + + m_bAutoCubemapActive = true; + m_AutoCubemapMapsIndex = 0; + + if (FStrEq( m_AutoCubemapMaps[m_AutoCubemapMapsIndex], g_MapName )) + { + // Build cubemaps right here, right now + m_bAutoCubemapBuildingCubemaps = true; + engine->ClientCmd_Unrestricted( "exec buildcubemaps_prep; buildcubemaps" ); + } + else + { + // Go to that map + engine->ClientCmd_Unrestricted( VarArgs( "map %s", m_AutoCubemapMaps[m_AutoCubemapMapsIndex] ) ); + } + } + + void LoadFile( const char *pszFile ) + { + KeyValues *pKV = new KeyValues( "AutoCubemap" ); + + if ( pKV->LoadFromFile( filesystem, pszFile, NULL ) ) + { + KeyValues *pSubKey = pKV->GetFirstSubKey(); + + while ( pSubKey ) + { + m_AutoCubemapMaps.AddToTail( strdup(pSubKey->GetName()) ); + pSubKey = pSubKey->GetNextKey(); + } + + Msg( "Initted autocubemap\n" ); + } + else + { + Warning( "Unable to load autocubemap file \"%s\"\n", pszFile ); + } + + pKV->deleteThis(); + } + + void Clear() + { + m_bAutoCubemapActive = false; + m_bAutoCubemapBuildingCubemaps = false; + m_bAutoCubemapDoingBoth = false; + + m_AutoCubemapMaps.PurgeAndDeleteElements(); + m_AutoCubemapMapsIndex = 0; + } + + void PrintState() + { + char szCmd[1024] = { 0 }; + + if (m_AutoCubemapMaps.Count() > 0) + { + Q_strncpy( szCmd, "=== CUBEMAPPER MAP LIST ===\n", sizeof( szCmd ) ); + + FOR_EACH_VEC( m_AutoCubemapMaps, i ) + { + Q_snprintf( szCmd, sizeof( szCmd ), "%s%s\n", szCmd, m_AutoCubemapMaps[i] ); + } + + Q_strncat( szCmd, "========================", sizeof( szCmd ), COPY_ALL_CHARACTERS ); + + Q_snprintf( szCmd, sizeof( szCmd ), "%s\nNumber of maps: %i (starting at %i)\n", szCmd, m_AutoCubemapMaps.Count(), m_AutoCubemapMapsIndex ); + } + else + { + Q_strncat( szCmd, "========================\n", sizeof( szCmd ), COPY_ALL_CHARACTERS ); + Q_strncat( szCmd, "There are no maps selected. Use 'autocubemap_init' to load a map list.\nIf 'autocubemap_start' is executed while no maps are selected, only the current map will have cubemaps generated.\n", sizeof( szCmd ), COPY_ALL_CHARACTERS ); + Q_strncat( szCmd, "========================\n", sizeof( szCmd ), COPY_ALL_CHARACTERS ); + } + + Msg( "%s", szCmd ); + } + + //------------------------------------------------------------------------------------- + + bool m_bAutoCubemapActive = false; + bool m_bAutoCubemapBuildingCubemaps = false; + bool m_bAutoCubemapDoingBoth = false; + int m_iAutoCubemapUserHDRLevel; // For setting the user back to the right HDR level when we're finished + + // Start autocubemap with the first level we load (used for launch parameter) + bool m_bAutoCubemapOnFirstLevel = false; + + CUtlVector m_AutoCubemapMaps; + int m_AutoCubemapMapsIndex; +}; + +CAutoCubemapSystem g_AutoCubemapSystem; + +CON_COMMAND( autocubemap_init, "Inits autocubemap" ) +{ + if (gpGlobals->maxClients > 1) + { + Msg( "Can't run autocubemap in multiplayer\n" ); + return; + } + + if (args.ArgC() <= 1) + { + Msg("Format: autocubemap_init \n"); + return; + } + + g_AutoCubemapSystem.LoadFile( args.Arg( 1 ) ); +} + +CON_COMMAND( autocubemap_print, "Prints current autocubemap information" ) +{ + if (gpGlobals->maxClients > 1) + { + Msg("Can't run autocubemap in multiplayer\n"); + return; + } + + g_AutoCubemapSystem.PrintState(); +} + +CON_COMMAND( autocubemap_clear, "Clears autocubemap stuff" ) +{ + if (gpGlobals->maxClients > 1) + { + Msg("Can't run autocubemap in multiplayer\n"); + return; + } + + g_AutoCubemapSystem.Clear(); +} + +CON_COMMAND( autocubemap_start, "Begins the autocubemap (it's recommended to check 'autocubemap_print' before running this command)" ) +{ + if (gpGlobals->maxClients > 1) + { + Msg("Can't run autocubemap in multiplayer\n"); + return; + } + + g_AutoCubemapSystem.StartAutoCubemap(); +} diff --git a/sp/src/game/client/particlemgr.h b/sp/src/game/client/particlemgr.h index 3214a55fd8..ee86e515c3 100644 --- a/sp/src/game/client/particlemgr.h +++ b/sp/src/game/client/particlemgr.h @@ -119,7 +119,7 @@ entities. Each one is useful under different conditions. #include "tier0/fasttimer.h" #include "utllinkedlist.h" #include "utldict.h" -#ifdef WIN32 +#if defined(WIN32) && _MSC_VER < 1900 #include #else #include diff --git a/sp/src/game/client/physics_main_client.cpp b/sp/src/game/client/physics_main_client.cpp index b1af5b23a9..ebc392e2b3 100644 --- a/sp/src/game/client/physics_main_client.cpp +++ b/sp/src/game/client/physics_main_client.cpp @@ -6,7 +6,7 @@ //=============================================================================// #include "cbase.h" #include "c_baseentity.h" -#ifdef WIN32 +#if defined(WIN32) && _MSC_VER < 1900 #include #endif #include "tier0/vprof.h" diff --git a/sp/src/game/client/physpropclientside.cpp b/sp/src/game/client/physpropclientside.cpp index 3dd2d7b5ba..3db90618a3 100644 --- a/sp/src/game/client/physpropclientside.cpp +++ b/sp/src/game/client/physpropclientside.cpp @@ -704,9 +704,157 @@ void C_PhysPropClientside::ParseAllEntities(const char *pMapData) } } +#ifdef MAPBASE +CBaseAnimating *BreakModelCreate_Ragdoll( CBaseEntity *pOwnerEnt, breakmodel_t *pModel, const Vector &position, const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity ) +{ + C_BaseAnimating *pOwner = dynamic_cast( pOwnerEnt ); + if ( !pOwner ) + return NULL; + + C_ClientRagdoll *pRagdoll = new C_ClientRagdoll( false ); + if ( pRagdoll == NULL ) + return NULL; + + const char *pModelName = pModel->modelName; + if ( pRagdoll->InitializeAsClientEntity( pModelName, RENDER_GROUP_OPAQUE_ENTITY ) == false ) + { + pRagdoll->Release(); + return NULL; + } + + pRagdoll->SetAbsOrigin( position ); + pRagdoll->SetAbsAngles( angles ); + + matrix3x4_t boneDelta0[MAXSTUDIOBONES]; + matrix3x4_t boneDelta1[MAXSTUDIOBONES]; + matrix3x4_t currentBones[MAXSTUDIOBONES]; + const float boneDt = 0.1f; + + pRagdoll->SetParent( pOwner ); + pRagdoll->ForceSetupBonesAtTime( boneDelta0, gpGlobals->curtime - boneDt ); + pRagdoll->ForceSetupBonesAtTime( boneDelta1, gpGlobals->curtime ); + pRagdoll->ForceSetupBonesAtTime( currentBones, gpGlobals->curtime ); + pRagdoll->SetParent( NULL ); + + // We need to take these from the entity + //pRagdoll->SetAbsOrigin( position ); + //pRagdoll->SetAbsAngles( angles ); + + pRagdoll->IgniteRagdoll( pOwner ); + pRagdoll->TransferDissolveFrom( pOwner ); + pRagdoll->InitModelEffects(); + + if ( pOwner->IsEffectActive( EF_NOSHADOW ) ) + { + pRagdoll->AddEffects( EF_NOSHADOW ); + } + + pRagdoll->m_nRenderFX = kRenderFxRagdoll; + pRagdoll->SetRenderMode( pOwner->GetRenderMode() ); + pRagdoll->SetRenderColor( pOwner->GetRenderColor().r, pOwner->GetRenderColor().g, pOwner->GetRenderColor().b, pOwner->GetRenderColor().a ); + //pRagdoll->SetGlobalFadeScale( pOwner->GetGlobalFadeScale() ); + + pRagdoll->SetSkin( pOwner->GetSkin() ); + //pRagdoll->m_vecForce = pOwner->m_vecForce; + //pRagdoll->m_nForceBone = 0; //pOwner->m_nForceBone; + pRagdoll->SetNextClientThink( CLIENT_THINK_ALWAYS ); + + pRagdoll->SetModelName( AllocPooledString( pModelName ) ); + pRagdoll->ResetSequence( 0 ); + pRagdoll->SetModelScale( pOwner->GetModelScale() ); + pRagdoll->SetCollisionGroup( COLLISION_GROUP_DEBRIS ); + //pRagdoll->m_builtRagdoll = true; + + CStudioHdr *hdr = pRagdoll->GetModelPtr(); + if ( !hdr ) + { + pRagdoll->Release(); + Warning( "Couldn't create ragdoll gib for %s (no model pointer)\n", pModel->modelName ); + return NULL; + } + + pRagdoll->m_pRagdoll = CreateRagdoll( + pRagdoll, + hdr, + vec3_origin, + 0, + boneDelta0, + boneDelta1, + currentBones, + boneDt ); + + if ( !pRagdoll->m_pRagdoll ) + { + pRagdoll->Release(); + Warning( "Couldn't create ragdoll gib for %s\n", pModel->modelName ); + return NULL; + } + + IPhysicsObject *pPhysicsObject = pRagdoll->VPhysicsGetObject(); + if ( pPhysicsObject ) + { + // randomize velocity by 5% + float rndf = RandomFloat( -0.025, 0.025 ); + Vector rndVel = velocity + rndf*velocity; + + pPhysicsObject->AddVelocity( &rndVel, &angVelocity ); + } + pRagdoll->ApplyLocalAngularVelocityImpulse( angVelocity ); + + if ( pRagdoll->m_pRagdoll ) + { + pRagdoll->m_bImportant = false; + pRagdoll->m_flForcedRetireTime = pModel->fadeTime > 0.0f ? gpGlobals->curtime + pModel->fadeTime : 0.0f; + s_RagdollLRU.MoveToTopOfLRU( pRagdoll, pRagdoll->m_bImportant, pRagdoll->m_flForcedRetireTime ); + pRagdoll->m_bFadeOut = true; + } + + // Cause the entity to recompute its shadow type and make a + // version which only updates when physics state changes + // NOTE: We have to do this after m_pRagdoll is assigned above + // because that's what ShadowCastType uses to figure out which type of shadow to use. + pRagdoll->DestroyShadow(); + pRagdoll->CreateShadow(); + + pRagdoll->SetAbsOrigin( position ); + pRagdoll->SetAbsAngles( angles ); + + pRagdoll->SetPlaybackRate( 0 ); + pRagdoll->SetCycle( 0 ); + + // put into ACT_DIERAGDOLL if it exists, otherwise use sequence 0 + int nSequence = pRagdoll->SelectWeightedSequence( ACT_DIERAGDOLL ); + if ( nSequence < 0 ) + { + pRagdoll->ResetSequence( 0 ); + } + else + { + pRagdoll->ResetSequence( nSequence ); + } + + pRagdoll->UpdatePartitionListEntry(); + pRagdoll->MarkRenderHandleDirty(); + + NoteRagdollCreationTick( pRagdoll ); + + //pRagdoll->InitAsClientRagdoll( boneDelta0, boneDelta1, currentBones, boneDt ); + + return pRagdoll; +} +#endif + CBaseEntity *BreakModelCreateSingle( CBaseEntity *pOwner, breakmodel_t *pModel, const Vector &position, const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, int nSkin, const breakablepropparams_t ¶ms ) { +#ifdef MAPBASE + if ( pModel->isRagdoll ) + { + CBaseEntity *pEntity = BreakModelCreate_Ragdoll( pOwner, pModel, position, angles, velocity, angVelocity ); + return pEntity; + } +#endif + C_PhysPropClientside *pEntity = C_PhysPropClientside::CreateNew(); if ( !pEntity ) @@ -778,10 +926,12 @@ CBaseEntity *BreakModelCreateSingle( CBaseEntity *pOwner, breakmodel_t *pModel, pEntity->SetFadeMinMax( pModel->fadeMinDist, pModel->fadeMaxDist ); } +#ifndef MAPBASE if ( pModel->isRagdoll ) { DevMsg( "BreakModelCreateSingle: clientside doesn't support ragdoll breakmodels.\n" ); } +#endif IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject(); diff --git a/sp/src/game/client/proxyplayer.cpp b/sp/src/game/client/proxyplayer.cpp index d6521cc90b..5fa7f1bcc9 100644 --- a/sp/src/game/client/proxyplayer.cpp +++ b/sp/src/game/client/proxyplayer.cpp @@ -12,6 +12,9 @@ #include "materialsystem/imaterialsystem.h" #include "functionproxy.h" #include "toolframework_client.h" +#ifdef MAPBASE +#include "view.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -521,4 +524,100 @@ void CPlayerLogoOnModelProxy::OnBind( void *pC_BaseEntity ) } EXPOSE_INTERFACE( CPlayerLogoOnModelProxy, IMaterialProxy, "PlayerLogoOnModel" IMATERIAL_PROXY_INTERFACE_VERSION ); -*/ \ No newline at end of file +*/ + + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Returns the proximity of the current view to the entity +//----------------------------------------------------------------------------- +class CViewProximityProxy : public CResultProxy +{ +public: + bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + void OnBind( void *pC_BaseEntity ); + +private: + float m_Factor; +}; + +bool CViewProximityProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + if (!CResultProxy::Init( pMaterial, pKeyValues )) + return false; + + m_Factor = pKeyValues->GetFloat( "scale", 0.002 ); + return true; +} + +void CViewProximityProxy::OnBind( void *pC_BaseEntity ) +{ + if (!pC_BaseEntity) + return; + + // Find the distance between the player and this entity.... + C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity ); + + Vector delta; + VectorSubtract( pEntity->WorldSpaceCenter(), CurrentViewOrigin(), delta ); + + Assert( m_pResult ); + SetFloatResult( delta.Length() * m_Factor ); + + if ( ToolsEnabled() ) + { + ToolFramework_RecordMaterialParams( GetMaterial() ); + } +} + +EXPOSE_INTERFACE( CViewProximityProxy, IMaterialProxy, "ViewProximity" IMATERIAL_PROXY_INTERFACE_VERSION ); + +//----------------------------------------------------------------------------- +// Returns the current view direction +//----------------------------------------------------------------------------- +class CViewDirectionProxy : public CResultProxy +{ +public: + bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + void OnBind( void *pC_BaseEntity ); + +private: + float m_Factor; +}; + +bool CViewDirectionProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + if (!CResultProxy::Init( pMaterial, pKeyValues )) + return false; + + m_Factor = pKeyValues->GetFloat( "scale", 2 ); + return true; +} + +void CViewDirectionProxy::OnBind( void *pC_BaseEntity ) +{ + if (!pC_BaseEntity) + return; + + // Find the view angle between the player and this entity.... + C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity ); + + Vector delta; + Vector forward; + + VectorSubtract( pEntity->WorldSpaceCenter(), CurrentViewOrigin(), delta ); + VectorNormalize( delta ); + + forward = CurrentViewForward(); + + Assert( m_pResult ); + SetFloatResult( DotProduct( forward, delta ) * m_Factor ); + + if ( ToolsEnabled() ) + { + ToolFramework_RecordMaterialParams( GetMaterial() ); + } +} + +EXPOSE_INTERFACE( CViewDirectionProxy, IMaterialProxy, "ViewDirection" IMATERIAL_PROXY_INTERFACE_VERSION ); +#endif diff --git a/sp/src/game/client/ragdoll.cpp b/sp/src/game/client/ragdoll.cpp index 3457b05420..1e044a1d76 100644 --- a/sp/src/game/client/ragdoll.cpp +++ b/sp/src/game/client/ragdoll.cpp @@ -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() @@ -477,6 +487,9 @@ int C_ServerRagdoll::InternalDrawModel( int flags ) return ret; } +#ifdef MAPBASE +static ConVar g_ragdoll_server_snatch_instance( "g_ragdoll_server_snatch_instance", "1", FCVAR_NONE, "Allows serverside ragdolls to snatch their source entities' model instances in the same way clientside ragdolls do, thereby retaining decals." ); +#endif CStudioHdr *C_ServerRagdoll::OnNewModel( void ) { @@ -499,6 +512,26 @@ CStudioHdr *C_ServerRagdoll::OnNewModel( void ) m_iv_ragAngles.SetMaxCount( m_elementCount ); } +#ifdef MAPBASE + if ( GetOwnerEntity() ) + { + if (GetOwnerEntity()->GetModelName() == GetModelName()) + { + // TODO: Is there a better place for this? + if (GetOwnerEntity()->GetBaseAnimating()) + GetOwnerEntity()->GetBaseAnimating()->m_pServerRagdoll = this; + + if (g_ragdoll_server_snatch_instance.GetBool()) + { + GetOwnerEntity()->SnatchModelInstance( this ); + } + } + } + + // Add server ragdolls to the creation tick list + NoteRagdollCreationTick( this ); +#endif + return hdr; } diff --git a/sp/src/game/client/text_message.cpp b/sp/src/game/client/text_message.cpp index 2251556ba3..33bef81fe9 100644 --- a/sp/src/game/client/text_message.cpp +++ b/sp/src/game/client/text_message.cpp @@ -121,7 +121,7 @@ char *CHudTextMessage::BufferedLocaliseTextString( const char *msg ) char *CHudTextMessage::LookupString( const char *msg, int *msg_dest ) { if ( !msg ) - return ""; + return (char*)""; // '#' character indicates this is a reference to a string in titles.txt, and not the string itself if ( msg[0] == '#' ) diff --git a/sp/src/game/client/vgui_debugoverlaypanel.cpp b/sp/src/game/client/vgui_debugoverlaypanel.cpp index c3734147af..2460ffa086 100644 --- a/sp/src/game/client/vgui_debugoverlaypanel.cpp +++ b/sp/src/game/client/vgui_debugoverlaypanel.cpp @@ -129,7 +129,7 @@ void CDebugOverlay::Paint() { float xPos = screenPos[0]; float yPos = screenPos[1]+ (pCurrText->lineOffset*13); // Line spacing; - g_pMatSystemSurface->DrawColoredText( m_hFont, xPos, yPos, r, g, b, a, pCurrText->text ); + g_pMatSystemSurface->DrawColoredText( m_hFont, xPos, yPos, r, g, b, a, "%s", pCurrText->text ); } } else @@ -138,7 +138,7 @@ void CDebugOverlay::Paint() { float xPos = screenPos[0]; float yPos = screenPos[1]+ (pCurrText->lineOffset*13); // Line spacing; - g_pMatSystemSurface->DrawColoredText( m_hFont, xPos, yPos, r, g, b, a, pCurrText->text ); + g_pMatSystemSurface->DrawColoredText( m_hFont, xPos, yPos, r, g, b, a, "%s", pCurrText->text ); } } } diff --git a/sp/src/game/client/vgui_movie_display.cpp b/sp/src/game/client/vgui_movie_display.cpp index 0f47432381..7bc5bb6238 100644 --- a/sp/src/game/client/vgui_movie_display.cpp +++ b/sp/src/game/client/vgui_movie_display.cpp @@ -7,16 +7,16 @@ #include "cbase.h" #include "c_vguiscreen.h" #include "vgui_controls/Label.h" -#include "vgui_BitmapPanel.h" -#include +#include "vgui_bitmappanel.h" +#include #include "c_slideshow_display.h" #include "ienginevgui.h" #include "fmtstr.h" #include "vgui_controls/ImagePanel.h" #include #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 @@ class CMovieDisplayScreen : public CVGuiScreenPanel 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); + } } } @@ -365,25 +368,27 @@ bool CMovieDisplayScreen::BeginPlayback( const char *pFilename ) } else { - Q_strncpy( szMaterialName, pFilename, sizeof(szMaterialName) ); + Q_snprintf( szMaterialName, sizeof(szMaterialName), "%s_%s", pFilename, m_hScreenEntity->GetEntityName() ); } - const char *pszMaterialName = CFmtStrN<128>( "VideoMaterial_", m_hScreenEntity->GetEntityName() ); - m_VideoMaterial = g_pVideo->CreateVideoMaterial( pszMaterialName, pFilename, "GAME", + m_VideoMaterial = g_pVideo->CreateVideoMaterial( szMaterialName, pFilename, "GAME", VideoPlaybackFlags::DEFAULT_MATERIAL_OPTIONS, VideoSystem::DETERMINE_FROM_FILE_EXTENSION/*, m_bAllowAlternateMedia*/ ); 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(); diff --git a/sp/src/game/client/view.cpp b/sp/src/game/client/view.cpp index 212947357c..8cd5293d40 100644 --- a/sp/src/game/client/view.cpp +++ b/sp/src/game/client/view.cpp @@ -107,10 +107,11 @@ extern ConVar cl_forwardspeed; static ConVar v_centermove( "v_centermove", "0.15"); static ConVar v_centerspeed( "v_centerspeed","500" ); -#ifdef TF_CLIENT_DLL +#if defined(TF_CLIENT_DLL) || defined(MAPBASE) // 54 degrees approximates a 35mm camera - we determined that this makes the viewmodels // and motions look the most natural. ConVar v_viewmodel_fov( "viewmodel_fov", "54", FCVAR_ARCHIVE ); +ConVar v_viewmodel_fov_script_override( "viewmodel_fov_script_override", "0", FCVAR_NONE, "If nonzero, overrides the viewmodel FOV of weapon scripts which override the viewmodel FOV." ); #else ConVar v_viewmodel_fov( "viewmodel_fov", "54", FCVAR_CHEAT ); #endif @@ -675,6 +676,10 @@ void CViewRender::SetUpViews() Vector ViewModelOrigin; QAngle ViewModelAngles; +#ifdef MAPBASE + view.fovViewmodel = g_pClientMode->GetViewModelFOV(); +#endif + if ( engine->IsHLTV() ) { HLTVCamera()->CalcView( view.origin, view.angles, view.fov ); @@ -710,6 +715,18 @@ void CViewRender::SetUpViews() bCalcViewModelView = true; ViewModelOrigin = view.origin; ViewModelAngles = view.angles; + +#ifdef MAPBASE + // Allow weapons to override viewmodel FOV + C_BaseCombatWeapon *pWeapon = pPlayer->GetActiveWeapon(); + if (pWeapon && pWeapon->GetViewmodelFOVOverride() != 0.0f) + { + if (v_viewmodel_fov_script_override.GetFloat() > 0.0f) + view.fovViewmodel = v_viewmodel_fov_script_override.GetFloat(); + else + view.fovViewmodel = pWeapon->GetViewmodelFOVOverride(); + } +#endif } else { @@ -745,7 +762,7 @@ void CViewRender::SetUpViews() //Adjust the viewmodel's FOV to move with any FOV offsets on the viewer's end #ifdef MAPBASE - view.fovViewmodel = fabs(g_pClientMode->GetViewModelFOV()) - flFOVOffset; + view.fovViewmodel = max(0.001f, view.fovViewmodel - flFOVOffset); #else view.fovViewmodel = g_pClientMode->GetViewModelFOV() - flFOVOffset; #endif diff --git a/sp/src/game/client/viewdebug.cpp b/sp/src/game/client/viewdebug.cpp index 619993fa8f..94988d9f84 100644 --- a/sp/src/game/client/viewdebug.cpp +++ b/sp/src/game/client/viewdebug.cpp @@ -29,6 +29,9 @@ static ConVar mat_wateroverlaysize( "mat_wateroverlaysize", "256" ); static ConVar mat_showframebuffertexture( "mat_showframebuffertexture", "0", FCVAR_CHEAT ); static ConVar mat_framebuffercopyoverlaysize( "mat_framebuffercopyoverlaysize", "256" ); static ConVar mat_showcamerarendertarget( "mat_showcamerarendertarget", "0", FCVAR_CHEAT ); +#ifdef MAPBASE +static ConVar mat_showcamerarendertarget_all( "mat_showcamerarendertarget_all", "0", FCVAR_CHEAT ); +#endif static ConVar mat_camerarendertargetoverlaysize( "mat_camerarendertargetoverlaysize", "256", FCVAR_CHEAT ); static ConVar mat_hsv( "mat_hsv", "0", FCVAR_CHEAT ); static ConVar mat_yuv( "mat_yuv", "0", FCVAR_CHEAT ); @@ -178,6 +181,11 @@ void OverlayCameraRenderTarget( const char *pszMaterialName, float flX, float fl pMaterial = materials->FindMaterial( pszMaterialName, TEXTURE_GROUP_OTHER, true ); if( !IsErrorMaterial( pMaterial ) ) { +#ifdef MAPBASE + // HACKHACK + pMaterial->IncrementReferenceCount(); +#endif + CMatRenderContextPtr pRenderContext( materials ); pRenderContext->Bind( pMaterial ); IMesh* pMesh = pRenderContext->GetDynamicMesh( true ); @@ -203,6 +211,11 @@ void OverlayCameraRenderTarget( const char *pszMaterialName, float flX, float fl meshBuilder.End(); pMesh->Draw(); + +#ifdef MAPBASE + // HACKHACK + pMaterial->DecrementReferenceCount(); +#endif } } @@ -214,7 +227,11 @@ static void OverlayFrameBufferTexture( int nFrameBufferIndex ) IMaterial *pMaterial; char buf[MAX_PATH]; Q_snprintf( buf, MAX_PATH, "debug/debugfbtexture%d", nFrameBufferIndex ); +#ifdef MAPBASE + pMaterial = materials->FindMaterial( buf, NULL, true ); +#else pMaterial = materials->FindMaterial( buf, TEXTURE_GROUP_OTHER, true ); +#endif if( !IsErrorMaterial( pMaterial ) ) { CMatRenderContextPtr pRenderContext( materials ); @@ -586,12 +603,52 @@ void CDebugViewRender::Draw2DDebuggingInfo( const CViewSetup &view ) if ( mat_showcamerarendertarget.GetBool() ) { +#ifdef MAPBASE + float w = mat_camerarendertargetoverlaysize.GetFloat(); + float h = mat_camerarendertargetoverlaysize.GetFloat(); +#else float w = mat_wateroverlaysize.GetFloat(); float h = mat_wateroverlaysize.GetFloat(); +#endif #ifdef PORTAL g_pPortalRender->OverlayPortalRenderTargets( w, h ); #else + +#ifdef MAPBASE + int iCameraNum = mat_showcamerarendertarget.GetInt(); + + if (iCameraNum == 1) // Display the default camera + { + OverlayCameraRenderTarget( "debug/debugcamerarendertarget", 0, 0, w, h ); + } + else if (mat_showcamerarendertarget_all.GetBool()) // Display all cameras + { + OverlayCameraRenderTarget( "debug/debugcamerarendertarget", 0, 0, w, h ); + + // Already showed one camera + iCameraNum--; + + // Show Mapbase's cameras + char szTextureName[48]; + for (int i = 0; i < iCameraNum; i++) + { + V_snprintf( szTextureName, sizeof( szTextureName ), "debug/debugcamerarendertarget_camera%i", i ); + + // Show them vertically if the cvar is set to 2 + if (mat_showcamerarendertarget_all.GetInt() == 2) + OverlayCameraRenderTarget( szTextureName, 0, h * (i + 1), w, h ); + else + OverlayCameraRenderTarget( szTextureName, w * (i + 1), 0, w, h ); + } + } + else // Display one of the new cameras + { + OverlayCameraRenderTarget( VarArgs( "debug/debugcamerarendertarget_camera%i", iCameraNum-2 ), 0, 0, w, h ); + } +#else OverlayCameraRenderTarget( "debug/debugcamerarendertarget", 0, 0, w, h ); +#endif + #endif } @@ -655,6 +712,57 @@ CON_COMMAND_F( r_screenoverlay, "Draw specified material as an overlay", FCVAR_C } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// The same as above, but using the new indexed overlays +//----------------------------------------------------------------------------- +CON_COMMAND_F( r_screenoverlay_indexed, "Draw specified material as an overlay in the specified index", FCVAR_CHEAT|FCVAR_SERVER_CAN_EXECUTE ) +{ + if( args.ArgC() == 3 ) + { + int index = atoi( args[1] ); + if (index < 0 || index >= MAX_SCREEN_OVERLAYS) + { + Warning( "r_screenoverlay_indexed: '%i' is out of range (should be 0-9)\n", index ); + return; + } + + if ( !Q_stricmp( "off", args[2] ) ) + { + view->SetIndexedScreenOverlayMaterial( index, NULL ); + } + else + { + IMaterial *pMaterial = materials->FindMaterial( args[2], TEXTURE_GROUP_OTHER, false ); + if ( !IsErrorMaterial( pMaterial ) ) + { + view->SetIndexedScreenOverlayMaterial( index, pMaterial ); + } + else + { + view->SetIndexedScreenOverlayMaterial( index, NULL ); + } + } + } + else if ( args.ArgC() == 2 ) + { + int index = atoi( args[1] ); + if (index < 0 || index >= MAX_SCREEN_OVERLAYS) + { + Warning( "r_screenoverlay_indexed: '%i' is out of range (should be 0-9)\n", index ); + return; + } + + IMaterial *pMaterial = view->GetIndexedScreenOverlayMaterial( index ); + Warning( "r_screenoverlay_indexed %i: %s\n", index, pMaterial ? pMaterial->GetName() : "off" ); + } + else + { + Warning( "Format: r_screenoverlay_indexed []\n" ); + } +} +#endif + // Used to verify frame syncing. void CDebugViewRender::GenerateOverdrawForTesting() { diff --git a/sp/src/game/client/viewrender.cpp b/sp/src/game/client/viewrender.cpp index 29f9c65147..c88858d583 100644 --- a/sp/src/game/client/viewrender.cpp +++ b/sp/src/game/client/viewrender.cpp @@ -76,6 +76,7 @@ #ifdef MAPBASE #include "mapbase/c_func_fake_worldportal.h" +#include "colorcorrectionmgr.h" #endif // Projective textures @@ -1218,6 +1219,73 @@ IMaterial *CViewRender::GetScreenOverlayMaterial( ) } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Sets the screen space effect material (can't be done during rendering) +//----------------------------------------------------------------------------- +void CViewRender::SetIndexedScreenOverlayMaterial( int i, IMaterial *pMaterial ) +{ + if (i < 0 || i >= MAX_SCREEN_OVERLAYS) + return; + + m_IndexedScreenOverlayMaterials[i].Init( pMaterial ); + + if (pMaterial == NULL) + { + // Check if we should set to false + int i; + for (i = 0; i < MAX_SCREEN_OVERLAYS; i++) + { + if (m_IndexedScreenOverlayMaterials[i] != NULL) + break; + } + + if (i == MAX_SCREEN_OVERLAYS) + m_bUsingIndexedScreenOverlays = false; + } + else + { + m_bUsingIndexedScreenOverlays = true; + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +IMaterial *CViewRender::GetIndexedScreenOverlayMaterial( int i ) +{ + if (i < 0 || i >= MAX_SCREEN_OVERLAYS) + return NULL; + + return m_IndexedScreenOverlayMaterials[i]; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CViewRender::ResetIndexedScreenOverlays() +{ + for (int i = 0; i < MAX_SCREEN_OVERLAYS; i++) + { + m_IndexedScreenOverlayMaterials[i].Init( NULL ); + } + + m_bUsingIndexedScreenOverlays = false; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int CViewRender::GetMaxIndexedScreenOverlays( ) const +{ + return MAX_SCREEN_OVERLAYS; +} +#endif + + //----------------------------------------------------------------------------- // Purpose: Performs screen space effects, if any //----------------------------------------------------------------------------- @@ -1254,6 +1322,44 @@ void CViewRender::PerformScreenOverlay( int x, int y, int w, int h ) render->ViewDrawFade( color, m_ScreenOverlayMaterial ); } } + +#ifdef MAPBASE + if (m_bUsingIndexedScreenOverlays) + { + for (int i = 0; i < MAX_SCREEN_OVERLAYS; i++) + { + if (!m_IndexedScreenOverlayMaterials[i]) + continue; + + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + + if ( m_IndexedScreenOverlayMaterials[i]->NeedsFullFrameBufferTexture() ) + { + // FIXME: check with multi/sub-rect renders. Should this be 0,0,w,h instead? + DrawScreenEffectMaterial( m_IndexedScreenOverlayMaterials[i], x, y, w, h ); + } + else if ( m_IndexedScreenOverlayMaterials[i]->NeedsPowerOfTwoFrameBufferTexture() ) + { + // First copy the FB off to the offscreen texture + UpdateRefractTexture( x, y, w, h, true ); + + // Now draw the entire screen using the material... + CMatRenderContextPtr pRenderContext( materials ); + ITexture *pTexture = GetPowerOfTwoFrameBufferTexture( ); + int sw = pTexture->GetActualWidth(); + int sh = pTexture->GetActualHeight(); + // Note - don't offset by x,y - already done by the viewport. + pRenderContext->DrawScreenSpaceRectangle( m_IndexedScreenOverlayMaterials[i], 0, 0, w, h, + 0, 0, sw-1, sh-1, sw, sh ); + } + else + { + byte color[4] = { 255, 255, 255, 255 }; + render->ViewDrawFade( color, m_IndexedScreenOverlayMaterials[i] ); + } + } + } +#endif } void CViewRender::DrawUnderwaterOverlay( void ) @@ -2005,18 +2111,19 @@ void CViewRender::RenderView( const CViewSetup &view, int nClearFlags, int whatT GeneratePerspectiveFrustum( view.origin, view.angles, view.zNear, view.zFar, view.fov, view.m_flAspectRatio, frustum ); cplane_t portalPlane; + Vector vecPlaneOrigin; //C_FuncFakeWorldPortal *pPortalEnt = IsFakeWorldPortalInView( view, portalPlane ); //if ( pPortalEnt ) - C_FuncFakeWorldPortal *pPortalEnt = NextFakeWorldPortal( NULL, view, portalPlane, frustum ); + C_FuncFakeWorldPortal *pPortalEnt = NextFakeWorldPortal( NULL, view, portalPlane, vecPlaneOrigin, frustum ); while ( pPortalEnt != NULL ) { ITexture *pCameraTarget = pPortalEnt->RenderTarget(); int width = pCameraTarget->GetActualWidth(); int height = pCameraTarget->GetActualHeight(); - DrawFakeWorldPortal( pCameraTarget, pPortalEnt, viewMiddle, C_BasePlayer::GetLocalPlayer(), 0, 0, width, height, view, portalPlane ); + DrawFakeWorldPortal( pCameraTarget, pPortalEnt, viewMiddle, C_BasePlayer::GetLocalPlayer(), 0, 0, width, height, view, portalPlane, vecPlaneOrigin ); - pPortalEnt = NextFakeWorldPortal( pPortalEnt, view, portalPlane, frustum ); + pPortalEnt = NextFakeWorldPortal( pPortalEnt, view, portalPlane, vecPlaneOrigin, frustum ); } #endif } @@ -2027,6 +2134,10 @@ void CViewRender::RenderView( const CViewSetup &view, int nClearFlags, int whatT // Must be first render->SceneBegin(); +#ifdef MAPBASE // From Alien Swarm SDK + g_pColorCorrectionMgr->UpdateColorCorrection(); +#endif + pRenderContext.GetFrom( materials ); pRenderContext->TurnOnToneMapping(); pRenderContext.SafeRelease(); @@ -3434,8 +3545,7 @@ ConVar r_fakeworldportal_debug("r_fakeworldportal_debug", "0"); //----------------------------------------------------------------------------- // Purpose: Sets up scene and renders WIP fake world portal view. -// Based on code from monitors, mirrors, and 3D skyboxes. -// It's also terrible right now. +// Based on code from monitors, mirrors, and logic_measure_movement. // // Input : cameraNum - // &cameraView @@ -3449,7 +3559,7 @@ ConVar r_fakeworldportal_debug("r_fakeworldportal_debug", "0"); //----------------------------------------------------------------------------- bool CViewRender::DrawFakeWorldPortal( ITexture *pRenderTarget, C_FuncFakeWorldPortal *pCameraEnt, const CViewSetup &cameraView, C_BasePlayer *localPlayer, int x, int y, int width, int height, - const CViewSetup &mainView, cplane_t &ourPlane ) + const CViewSetup &mainView, cplane_t &ourPlane, const Vector &vecPlaneOrigin ) { #ifdef USE_MONITORS VPROF_INCREMENT_COUNTER( "cameras rendered", 1 ); @@ -3488,97 +3598,89 @@ bool CViewRender::DrawFakeWorldPortal( ITexture *pRenderTarget, C_FuncFakeWorldP monitorView.origin = mainView.origin; monitorView.angles = mainView.angles; - // Temporary debug stuff + // Debug stuff static float flLastDebugTime = 0.0f; bool bDebug = r_fakeworldportal_debug.GetBool() && gpGlobals->curtime > flLastDebugTime; - QAngle angTargetAngles = pCameraEnt->m_hTargetPlane->GetAbsAngles() + pCameraEnt->m_PlaneAngles; + // + // Calculate the angles for the fake portal plane + // + QAngle angTargetAngles = pCameraEnt->m_hTargetPlane->GetAbsAngles() - pCameraEnt->m_PlaneAngles; + QAngle angFakePortalAngles; + + // Get vectors from our original angles. + Vector vOurForward, vOurRight, vOurUp; + AngleVectors( pCameraEnt->GetAbsAngles(), &vOurForward, &vOurRight, &vOurUp ); + + Quaternion quat; + BasisToQuaternion( ourPlane.normal, vOurRight, vOurUp, quat ); + QuaternionAngles( quat, angFakePortalAngles ); - // RED - First origin if (bDebug) + { + // RED - Initial player origin debugoverlay->AddBoxOverlay( monitorView.origin, Vector(-32,-32,-32), Vector(32,32,32), monitorView.angles, 255, 0, 0, 128, 10.0f ); - // Make sure the origin and angles are relative to the target plane - monitorView.origin -= pCameraEnt->GetAbsOrigin(); + // YELLOW - Portal origin + debugoverlay->AddBoxOverlay( pCameraEnt->GetAbsOrigin(), Vector(-32,-32,-32), Vector(32,32,32), angFakePortalAngles, 255, 224, 0, 128, 10.0f ); + } - // scale origin by sky scale + // + // Translate the actual portal view position to be relative to the target + // + matrix3x4_t matPlayer, matPortal, matPlayerToPortal; + AngleIMatrix( monitorView.angles, monitorView.origin, matPlayer ); + AngleMatrix( angFakePortalAngles, pCameraEnt->GetAbsOrigin(), matPortal ); + ConcatTransforms( matPlayer, matPortal, matPlayerToPortal ); + + // Apply the scale factor if ( pCameraEnt->m_flScale > 0 ) { - float scale = 1.0f / pCameraEnt->m_flScale; - VectorScale( monitorView.origin, scale, monitorView.origin ); + Vector vecTranslation; + MatrixGetColumn( matPlayerToPortal, 3, vecTranslation ); + vecTranslation /= pCameraEnt->m_flScale; + MatrixSetColumn( vecTranslation, 3, matPlayerToPortal ); } - // YELLOW - Main origin - if (bDebug) - debugoverlay->AddBoxOverlay( pCameraEnt->GetAbsOrigin(), Vector(-32,-32,-32), Vector(32,32,32), monitorView.angles, 255, 224, 0, 128, 10.0f ); + matrix3x4_t matTarget; + AngleMatrix( angTargetAngles, pCameraEnt->m_hTargetPlane->GetAbsOrigin(), matTarget ); - // Make sure our angles are relative to the main plane, just like the origin - QAngle angOurAngles; - VectorAngles( ourPlane.normal * -1, angOurAngles ); - //angles -= angOurAngles; + // Now apply the new matrix to the new reference point + matrix3x4_t matPortalToPlayer, matNewPlayerPosition; + MatrixInvert( matPlayerToPortal, matPortalToPlayer ); - // First, create a matrix for the sky's angles. - matrix3x4_t matSkyAngles; - AngleMatrix( angTargetAngles - angOurAngles, matSkyAngles ); + ConcatTransforms( matTarget, matPortalToPlayer, matNewPlayerPosition ); - Vector vecSkyForward, vecSkyRight, vecSkyUp; + MatrixAngles( matNewPlayerPosition, monitorView.angles, monitorView.origin ); - // Get vectors from our original angles. - Vector vPlayerForward, vPlayerRight, vPlayerUp; - AngleVectors( monitorView.angles, &vPlayerForward, &vPlayerRight, &vPlayerUp ); - - VectorTransform( vPlayerForward, matSkyAngles, vecSkyForward ); - VectorTransform( vPlayerRight, matSkyAngles, vecSkyRight ); - VectorTransform( vPlayerUp, matSkyAngles, vecSkyUp ); - - // Normalize them. - VectorNormalize( vecSkyForward ); - VectorNormalize( vecSkyRight ); - VectorNormalize( vecSkyUp ); - - Quaternion quat; - BasisToQuaternion( vecSkyForward, vecSkyRight, vecSkyUp, quat ); - QuaternionAngles( quat, monitorView.angles ); - - // End of code mostly lifted from projected texture screenspace stuff - // ---------------------------------------------------------------------- - - // Now just rotate our origin with that matrix. - // We create a copy of the origin since VectorRotate doesn't want in1 to be the same variable as the destination. - VectorRotate(Vector(monitorView.origin), matSkyAngles, monitorView.origin); - - // BLUE - Target origin - if (bDebug) - debugoverlay->AddBoxOverlay( pCameraEnt->m_hTargetPlane->GetAbsOrigin(), Vector(-32,-32,-32), Vector(32,32,32), monitorView.angles, 0, 0, 255, 128, 10.0f ); - - monitorView.origin += pCameraEnt->m_hTargetPlane->GetAbsOrigin(); - - // GREEN - Final origin if (bDebug) { + // BLUE - Target origin + debugoverlay->AddBoxOverlay( pCameraEnt->m_hTargetPlane->GetAbsOrigin(), Vector(-32,-32,-32), Vector(32,32,32), angTargetAngles, 0, 0, 255, 128, 10.0f ); + + // GREEN - Final origin debugoverlay->AddBoxOverlay( monitorView.origin, Vector(-32,-32,-32), Vector(32,32,32), monitorView.angles, 0, 255, 0, 128, 10.0f ); flLastDebugTime = gpGlobals->curtime + 5.0f; } - monitorView.fov = mainView.fov; - monitorView.m_bOrtho = false; - monitorView.m_flAspectRatio = 0.0f; + monitorView.m_bOrtho = mainView.m_bOrtho; + monitorView.m_flAspectRatio = mainView.m_flAspectRatio; monitorView.m_bViewToProjectionOverride = false; // @MULTICORE (toml 8/11/2006): this should be a renderer.... int nClearFlags = (VIEW_CLEAR_DEPTH | VIEW_CLEAR_COLOR | VIEW_CLEAR_OBEY_STENCIL); bool bDrew3dSkybox = false; - SkyboxVisibility_t nSkyMode = pCameraEnt->SkyMode(); Frustum frustum; render->Push3DView( monitorView, nClearFlags, pRenderTarget, (VPlane *)frustum ); // - // Monitor sky handling + // Sky handling // - if ( pCameraEnt->SkyMode() == SKYBOX_3DSKYBOX_VISIBLE ) + SkyboxVisibility_t nSkyMode = pCameraEnt->SkyMode(); + if ( nSkyMode == SKYBOX_3DSKYBOX_VISIBLE ) { // if the 3d skybox world is drawn, then don't draw the normal skybox CSkyboxView *pSkyView = new CSkyboxView( this ); @@ -3589,16 +3691,21 @@ bool CViewRender::DrawFakeWorldPortal( ITexture *pRenderTarget, C_FuncFakeWorldP SafeRelease( pSkyView ); } + // + // Make a clipping plane for the target view + // Vector4D plane; - // Combine the target angles and the plane angles - Vector vecAnglesNormal( angTargetAngles.x, angTargetAngles.y, angTargetAngles.z ); + Vector vecAnglesNormal; + AngleVectors( angTargetAngles, &vecAnglesNormal ); VectorNormalize( vecAnglesNormal ); - VectorCopy( vecAnglesNormal, plane.AsVector3D() ); + VectorCopy( -vecAnglesNormal, plane.AsVector3D() ); + + // The portal plane's distance from the actual brush's origin + float flPlaneDist = vecPlaneOrigin.Length(); - // TODO: How do we get a good value for this!?!? - //plane.w = m_OurPlane.dist + 0.1f; - plane.w = -32.0f + 0.1f; + // The target's distance from world origin + plane.w = -((pCameraEnt->m_hTargetPlane->GetAbsOrigin() * vecAnglesNormal).Length() + flPlaneDist) + 0.1f; CMatRenderContextPtr pRenderContext( materials ); pRenderContext->PushCustomClipPlane( plane.Base() ); diff --git a/sp/src/game/client/viewrender.h b/sp/src/game/client/viewrender.h index 2312c4d532..936a282b5b 100644 --- a/sp/src/game/client/viewrender.h +++ b/sp/src/game/client/viewrender.h @@ -454,7 +454,7 @@ class CViewRender : public IViewRender, #ifdef MAPBASE bool DrawFakeWorldPortal( ITexture *pRenderTarget, C_FuncFakeWorldPortal *pCameraEnt, const CViewSetup &cameraView, C_BasePlayer *localPlayer, int x, int y, int width, int height, - const CViewSetup &mainView, cplane_t &ourPlane ); + const CViewSetup &mainView, cplane_t &ourPlane, const Vector &vecPlaneOrigin ); #endif // Drawing primitives @@ -468,6 +468,13 @@ class CViewRender : public IViewRender, IMaterial *GetScreenOverlayMaterial( ); void PerformScreenOverlay( int x, int y, int w, int h ); +#ifdef MAPBASE + void SetIndexedScreenOverlayMaterial( int i, IMaterial *pMaterial ); + IMaterial *GetIndexedScreenOverlayMaterial( int i ); + void ResetIndexedScreenOverlays(); + int GetMaxIndexedScreenOverlays() const; +#endif + void DrawUnderwaterOverlay( void ); // Water-related methods @@ -511,6 +518,10 @@ class CViewRender : public IViewRender, CMaterialReference m_TranslucentSingleColor; CMaterialReference m_ModulateSingleColor; CMaterialReference m_ScreenOverlayMaterial; +#ifdef MAPBASE + CMaterialReference m_IndexedScreenOverlayMaterials[MAX_SCREEN_OVERLAYS]; + bool m_bUsingIndexedScreenOverlays; +#endif CMaterialReference m_UnderWaterOverlayMaterial; Vector m_vecLastFacing; diff --git a/sp/src/game/client/vscript_client.cpp b/sp/src/game/client/vscript_client.cpp index 50c66a923a..a01ba4d290 100644 --- a/sp/src/game/client/vscript_client.cpp +++ b/sp/src/game/client/vscript_client.cpp @@ -15,11 +15,12 @@ #include "gamerules.h" #include "vscript_client.nut" #ifdef MAPBASE_VSCRIPT -#include "mapbase/matchers.h" +#include "view.h" #include "c_world.h" #include "proxyentity.h" #include "materialsystem/imaterial.h" #include "materialsystem/imaterialvar.h" +#include "mapbase/matchers.h" #include "mapbase/vscript_singletons.h" #endif @@ -41,10 +42,13 @@ extern ScriptClassDesc_t * GetScriptDesc( CBaseEntity * ); #endif // VMPROFILE #ifdef MAPBASE_VSCRIPT +static ScriptHook_t g_Hook_OnEntityCreated; +static ScriptHook_t g_Hook_OnEntityDeleted; + //----------------------------------------------------------------------------- // Purpose: A clientside variant of CScriptEntityIterator. //----------------------------------------------------------------------------- -class CScriptClientEntityIterator +class CScriptClientEntityIterator : public IClientEntityListener { public: HSCRIPT GetLocalPlayer() @@ -96,6 +100,38 @@ class CScriptClientEntityIterator return NULL; } + void EnableEntityListening() + { + // Start getting entity updates! + ClientEntityList().AddListenerEntity( this ); + } + + void DisableEntityListening() + { + // Stop getting entity updates! + ClientEntityList().RemoveListenerEntity( this ); + } + + void OnEntityCreated( CBaseEntity *pEntity ) + { + if ( g_pScriptVM ) + { + // entity + ScriptVariant_t args[] = { ScriptVariant_t( pEntity->GetScriptInstance() ) }; + g_Hook_OnEntityCreated.Call( NULL, NULL, args ); + } + }; + + void OnEntityDeleted( CBaseEntity *pEntity ) + { + if ( g_pScriptVM ) + { + // entity + ScriptVariant_t args[] = { ScriptVariant_t( pEntity->GetScriptInstance() ) }; + g_Hook_OnEntityDeleted.Call( NULL, NULL, args ); + } + }; + private: } g_ScriptEntityIterator; @@ -106,6 +142,17 @@ BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptClientEntityIterator, "CEntities", SCRIPT_SI DEFINE_SCRIPTFUNC( CreateByClassname, "Creates an entity by classname" ) DEFINE_SCRIPTFUNC( FindByClassname, "Find entities by class name. Pass 'null' to start an iteration, or reference to a previously found entity to continue a search" ) DEFINE_SCRIPTFUNC( FindByName, "Find entities by name. Pass 'null' to start an iteration, or reference to a previously found entity to continue a search" ) + + DEFINE_SCRIPTFUNC( EnableEntityListening, "Enables the 'OnEntity' hooks. This function must be called before using them." ) + DEFINE_SCRIPTFUNC( DisableEntityListening, "Disables the 'OnEntity' hooks." ) + + BEGIN_SCRIPTHOOK( g_Hook_OnEntityCreated, "OnEntityCreated", FIELD_VOID, "Called when an entity is created. Requires EnableEntityListening() to be fired beforehand." ) + DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( g_Hook_OnEntityDeleted, "OnEntityDeleted", FIELD_VOID, "Called when an entity is deleted. Requires EnableEntityListening() to be fired beforehand." ) + DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT ) + END_SCRIPTHOOK() END_SCRIPTDESC(); //----------------------------------------------------------------------------- @@ -165,6 +212,8 @@ class CScriptMaterialProxy : public IMaterialProxy, public IClientScriptPersista 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 +243,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 +372,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 +472,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 ) @@ -597,6 +663,20 @@ bool VScriptClientInit() ScriptRegisterFunction( g_pScriptVM, IsWindowedMode, "" ); ScriptRegisterFunctionNamed( g_pScriptVM, ScriptScreenTransform, "ScreenTransform", "Get the x & y positions of a world position in screen space. Returns true if it's onscreen" ); + ScriptRegisterFunction( g_pScriptVM, MainViewOrigin, "" ); + ScriptRegisterFunction( g_pScriptVM, MainViewAngles, "" ); + ScriptRegisterFunction( g_pScriptVM, PrevMainViewOrigin, "" ); + ScriptRegisterFunction( g_pScriptVM, PrevMainViewAngles, "" ); + ScriptRegisterFunction( g_pScriptVM, MainViewForward, "" ); + ScriptRegisterFunction( g_pScriptVM, MainViewRight, "" ); + ScriptRegisterFunction( g_pScriptVM, MainViewUp, "" ); + + ScriptRegisterFunction( g_pScriptVM, CurrentViewOrigin, "" ); + ScriptRegisterFunction( g_pScriptVM, CurrentViewAngles, "" ); + ScriptRegisterFunction( g_pScriptVM, CurrentViewForward, "" ); + ScriptRegisterFunction( g_pScriptVM, CurrentViewRight, "" ); + ScriptRegisterFunction( g_pScriptVM, CurrentViewUp, "" ); + ScriptRegisterFunction( g_pScriptVM, CreateProp, "Create an animating prop" ); #endif diff --git a/sp/src/game/client/vscript_client.nut b/sp/src/game/client/vscript_client.nut index 505395da74..7b4f281088 100644 --- a/sp/src/game/client/vscript_client.nut +++ b/sp/src/game/client/vscript_client.nut @@ -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"; \ No newline at end of file diff --git a/sp/src/game/client/worldlight.cpp b/sp/src/game/client/worldlight.cpp index 66488b11a4..29e383a71f 100644 --- a/sp/src/game/client/worldlight.cpp +++ b/sp/src/game/client/worldlight.cpp @@ -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 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 clusterIndexList; + CUtlVector 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 diff --git a/sp/src/game/client/worldlight.h b/sp/src/game/client/worldlight.h index 65a13ea76c..b621ce1f11 100644 --- a/sp/src/game/client/worldlight.h +++ b/sp/src/game/client/worldlight.h @@ -27,7 +27,9 @@ class CWorldLights : public CAutoGameSystem // 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 @@ class CWorldLights : public CAutoGameSystem 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 m_WorldLightsInCluster; + CUtlVector m_WorldLightsIndexList; +#endif }; //----------------------------------------------------------------------------- diff --git a/sp/src/game/server/AI_Criteria.h b/sp/src/game/server/AI_Criteria.h index 81ddc16959..5327785810 100644 --- a/sp/src/game/server/AI_Criteria.h +++ b/sp/src/game/server/AI_Criteria.h @@ -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 @@ class AI_Response }; #endif // AI_CRITERIA_H +#endif diff --git a/sp/src/game/server/AI_ResponseSystem.h b/sp/src/game/server/AI_ResponseSystem.h index a34255b26a..0b0b633405 100644 --- a/sp/src/game/server/AI_ResponseSystem.h +++ b/sp/src/game/server/AI_ResponseSystem.h @@ -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 diff --git a/sp/src/game/server/BaseAnimatingOverlay.cpp b/sp/src/game/server/BaseAnimatingOverlay.cpp index c1d7b88341..06bf690e0a 100644 --- a/sp/src/game/server/BaseAnimatingOverlay.cpp +++ b/sp/src/game/server/BaseAnimatingOverlay.cpp @@ -57,6 +57,45 @@ BEGIN_DATADESC( CBaseAnimatingOverlay ) END_DATADESC() +#ifdef MAPBASE_VSCRIPT +BEGIN_ENT_SCRIPTDESC( CBaseAnimatingOverlay, CBaseAnimating, "Animating models which support dynamic animation layers/overlays." ) + + DEFINE_SCRIPTFUNC( GetNumAnimOverlays, "Gets the current number of animation layers." ) + DEFINE_SCRIPTFUNC( RemoveAllGestures, "Removes all animation layers." ) + + DEFINE_SCRIPTFUNC( IsValidLayer, "Returns true if the specified layer index is valid." ) + DEFINE_SCRIPTFUNC( HasActiveLayer, "Returns true if there is currently an active layer." ) + DEFINE_SCRIPTFUNC( RemoveLayer, "Removes the specified layer index with the specified kill rate and delay." ) + DEFINE_SCRIPTFUNC( FastRemoveLayer, "Removes the specified layer index immediately." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptAddGesture, "AddGesture", "Adds a new animation layer using the specified activity name." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptAddGestureID, "AddGestureID", "Adds a new animation layer using the specified activity index." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptAddGestureSequence, "AddGestureSequence", "Adds a new animation layer using the specified activity name." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptAddGestureSequenceID, "AddGestureSequenceID", "Adds a new animation layer using the specified sequence index." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptFindGestureLayer, "FindGestureLayer", "Finds and returns the first active animation layer which uses the specified activity name." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptFindGestureLayerByID, "FindGestureLayerByID", "Finds and returns the first active animation layer which uses the specified activity index." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetLayerActivity, "GetLayerActivity", "Gets the activity name of the specified layer index." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetLayerActivityID, "GetLayerActivityID", "Gets the activity index of the specified layer index." ) + DEFINE_SCRIPTFUNC( GetLayerSequence, "Gets the sequence index of the specified layer index." ) + DEFINE_SCRIPTFUNC( SetLayerDuration, "Sets the duration of the specified layer index." ) + DEFINE_SCRIPTFUNC( GetLayerDuration, "Gets the duration of the specified layer index." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetLayerCycle, "SetLayerCycle", "Sets the cycle of the specified layer index." ) + DEFINE_SCRIPTFUNC( GetLayerCycle, "Gets the cycle of the specified layer index." ) + DEFINE_SCRIPTFUNC( SetLayerPlaybackRate, "Sets the playback rate of the specified layer index." ) + DEFINE_SCRIPTFUNC( SetLayerWeight, "Sets the weight of the specified layer index." ) + DEFINE_SCRIPTFUNC( GetLayerWeight, "Gets the weight of the specified layer index." ) + DEFINE_SCRIPTFUNC( SetLayerBlendIn, "Sets the fade-in of the specified layer index, with the fade being a 0-1 fraction of the cycle." ) + DEFINE_SCRIPTFUNC( SetLayerBlendOut, "Sets the fade-out of the specified layer index, with the fade being a 0-1 fraction of the cycle." ) + DEFINE_SCRIPTFUNC( SetLayerAutokill, "Sets whether or not the specified layer index should remove itself when it's finished playing." ) + DEFINE_SCRIPTFUNC( SetLayerLooping, "Sets whether or not the specified layer index should loop." ) + DEFINE_SCRIPTFUNC( SetLayerNoRestore, "Sets whether or not the specified layer index should restore after a save is loaded." ) + DEFINE_SCRIPTFUNC( SetLayerNoEvents, "Sets whether or not the specified layer index should fire animation events." ) + +END_SCRIPTDESC(); +#endif + #define ORDER_BITS 4 #define WEIGHT_BITS 8 @@ -354,7 +393,11 @@ void CBaseAnimatingOverlay::DispatchAnimEvents ( CBaseAnimating *eventHandler ) for ( int i = 0; i < m_AnimOverlay.Count(); i++ ) { +#ifdef MAPBASE // From Alien Swarm SDK + if (m_AnimOverlay[ i ].IsActive() && !m_AnimOverlay[ i ].NoEvents()) +#else if (m_AnimOverlay[ i ].IsActive()) +#endif { m_AnimOverlay[ i ].DispatchAnimEvents( eventHandler, this ); } @@ -1052,6 +1095,38 @@ void CBaseAnimatingOverlay::SetLayerNoRestore( int iLayer, bool bNoRestore ) } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// From Alien Swarm SDK +//----------------------------------------------------------------------------- +void CBaseAnimatingOverlay::SetLayerNoEvents( int iLayer, bool bNoEvents ) +{ + if (!IsValidLayer( iLayer )) + return; + + if (bNoEvents) + { + m_AnimOverlay[iLayer].m_fFlags |= ANIM_LAYER_NOEVENTS; + } + else + { + m_AnimOverlay[iLayer].m_fFlags &= ~ANIM_LAYER_NOEVENTS; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseAnimatingOverlay::IsLayerFinished( int iLayer ) +{ + if (!IsValidLayer( iLayer )) + return true; + + return m_AnimOverlay[iLayer].m_bSequenceFinished; +} +#endif + + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -1149,4 +1224,36 @@ bool CBaseAnimatingOverlay::HasActiveLayer( void ) return false; } +#ifdef MAPBASE_VSCRIPT +int CBaseAnimatingOverlay::ScriptAddGesture( const char *pszActivity, bool autokill ) +{ + return AddGesture( (Activity)CAI_BaseNPC::GetActivityID( pszActivity ), autokill ); +} + +int CBaseAnimatingOverlay::ScriptAddGestureID( int iActivity, bool autokill ) +{ + return AddGesture( (Activity)iActivity, autokill ); +} + +int CBaseAnimatingOverlay::ScriptFindGestureLayer( const char *pszActivity ) +{ + return FindGestureLayer( (Activity)CAI_BaseNPC::GetActivityID( pszActivity ) ); +} + +int CBaseAnimatingOverlay::ScriptFindGestureLayerByID( int iActivity ) +{ + return FindGestureLayer( (Activity)iActivity ); +} + +const char *CBaseAnimatingOverlay::ScriptGetLayerActivity( int iLayer ) +{ + return CAI_BaseNPC::GetActivityName( GetLayerActivity( iLayer ) ); +} + +int CBaseAnimatingOverlay::ScriptGetLayerActivityID( int iLayer ) +{ + return GetLayerActivity( iLayer ); +} +#endif + //----------------------------------------------------------------------------- diff --git a/sp/src/game/server/BaseAnimatingOverlay.h b/sp/src/game/server/BaseAnimatingOverlay.h index ac112cbd0c..38ee7ed15c 100644 --- a/sp/src/game/server/BaseAnimatingOverlay.h +++ b/sp/src/game/server/BaseAnimatingOverlay.h @@ -43,6 +43,9 @@ class CAnimationLayer #define ANIM_LAYER_DONTRESTORE 0x0008 #define ANIM_LAYER_CHECKACCESS 0x0010 #define ANIM_LAYER_DYING 0x0020 +#ifdef MAPBASE // From Alien Swarm SDK +#define ANIM_LAYER_NOEVENTS 0x0040 +#endif int m_fFlags; @@ -80,6 +83,9 @@ class CAnimationLayer void Dying( void ) { m_fFlags |= ANIM_LAYER_DYING; } bool IsDying( void ) { return ((m_fFlags & ANIM_LAYER_DYING) != 0); } void Dead( void ) { m_fFlags &= ~ANIM_LAYER_DYING; } +#ifdef MAPBASE // From Alien Swarm SDK + bool NoEvents( void ) { return ((m_fFlags & ANIM_LAYER_NOEVENTS) != 0); } +#endif bool IsAbandoned( void ); void MarkActive( void ); @@ -175,6 +181,11 @@ class CBaseAnimatingOverlay : public CBaseAnimating void SetLayerAutokill( int iLayer, bool bAutokill ); void SetLayerLooping( int iLayer, bool bLooping ); void SetLayerNoRestore( int iLayer, bool bNoRestore ); +#ifdef MAPBASE + void SetLayerNoEvents( int iLayer, bool bNoEvents ); // From Alien Swarm SDK + + bool IsLayerFinished( int iLayer ); +#endif Activity GetLayerActivity( int iLayer ); int GetLayerSequence( int iLayer ); @@ -195,9 +206,26 @@ class CBaseAnimatingOverlay : public CBaseAnimating private: int AllocateLayer( int iPriority = 0 ); // lower priorities are processed first +#ifdef MAPBASE_VSCRIPT + int ScriptAddGesture( const char *pszActivity, bool autokill ); + int ScriptAddGestureID( int iActivity, bool autokill ); + int ScriptAddGestureSequence( const char *pszSequence, bool autokill ) { return AddGestureSequence( LookupSequence( pszSequence ), autokill ); } + int ScriptAddGestureSequenceID( int iSequence, bool autokill ) { return AddGestureSequence( iSequence, autokill ); } + + int ScriptFindGestureLayer( const char *pszActivity ); + int ScriptFindGestureLayerByID( int iActivity ); + const char *ScriptGetLayerActivity( int iLayer ); + int ScriptGetLayerActivityID( int iLayer ); + + void ScriptSetLayerCycle( int iLayer, float flCycle ) { SetLayerCycle( iLayer, flCycle ); } +#endif + DECLARE_SERVERCLASS(); DECLARE_DATADESC(); DECLARE_PREDICTABLE(); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); +#endif }; EXTERN_SEND_TABLE(DT_BaseAnimatingOverlay); diff --git a/sp/src/game/server/BasePropDoor.h b/sp/src/game/server/BasePropDoor.h index d96c75c5b6..2557bec360 100644 --- a/sp/src/game/server/BasePropDoor.h +++ b/sp/src/game/server/BasePropDoor.h @@ -103,6 +103,12 @@ abstract_class CBasePropDoor : public CDynamicProp inline CBaseEntity *GetActivator(); +#ifdef MAPBASE + inline float GetNPCOpenDistance() { return m_flNPCOpenDistance; } + inline Activity GetNPCOpenFrontActivity() { return m_eNPCOpenFrontActivity; } + inline Activity GetNPCOpenBackActivity() { return m_eNPCOpenBackActivity; } +#endif + private: // Implement these in your leaf class. @@ -196,6 +202,12 @@ abstract_class CBasePropDoor : public CDynamicProp string_t m_SoundOpen; string_t m_SoundClose; +#ifdef MAPBASE + float m_flNPCOpenDistance; + Activity m_eNPCOpenFrontActivity; + Activity m_eNPCOpenBackActivity; +#endif + // dvs: FIXME: can we remove m_flSpeed from CBaseEntity? //float m_flSpeed; // Rotation speed when opening or closing in degrees per second. diff --git a/sp/src/game/server/CommentarySystem.cpp b/sp/src/game/server/CommentarySystem.cpp index 4d01015127..3b4ef12ba5 100644 --- a/sp/src/game/server/CommentarySystem.cpp +++ b/sp/src/game/server/CommentarySystem.cpp @@ -72,8 +72,23 @@ class CPointCommentaryNode : public CBaseAnimating DECLARE_CLASS( CPointCommentaryNode, CBaseAnimating ); public: DECLARE_DATADESC(); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); +#endif DECLARE_SERVERCLASS(); + CPointCommentaryNode() + { +#ifdef MAPBASE + m_flViewTargetSpeedScale = 1.0f; + m_flViewPositionSpeedScale = 1.0f; + m_flReturnSpeedScale = 0.0f; + m_flPanelScale = 1.0f; + m_flPanelX = -1.0f; + m_flPanelY = -1.0f; +#endif + } + void Spawn( void ); void Precache( void ); void Activate( void ); @@ -102,11 +117,37 @@ class CPointCommentaryNode : public CBaseAnimating void TeleportTo( CBasePlayer *pPlayer ); bool CanTeleportTo( void ); +#ifdef MAPBASE + bool IsActive() { return m_bActive; } + bool IsDisabled() { return m_bDisabled; } + + int GetCommentaryType() { return m_iCommentaryType; } + void SetCommentaryType( int iType ) { m_iCommentaryType = iType; } + + const char *GetCommentaryFile() { return STRING( m_iszCommentaryFile.Get() ); } + void SetCommentaryFile( const char *pszNewFile ) { m_iszCommentaryFile.Set( AllocPooledString( pszNewFile ) ); } + const char *GetSpeakers() { return STRING( m_iszSpeakers.Get() ); } + void SetSpeakers( const char *pszSpeakers ) { m_iszSpeakers.Set( AllocPooledString( pszSpeakers ) ); } + const char *GetPrintName() { return STRING( m_iszPrintName.Get() ); } + void SetPrintName( const char *pszPrintName ) { m_iszPrintName.Set( AllocPooledString( pszPrintName ) ); } +#endif + // Inputs void InputStartCommentary( inputdata_t &inputdata ); void InputStartUnstoppableCommentary( inputdata_t &inputdata ); void InputEnable( inputdata_t &inputdata ); void InputDisable( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetViewTarget( inputdata_t &inputdata ); + void InputSetViewPosition( inputdata_t &inputdata ); + void InputSetViewTargetSpeed( inputdata_t &inputdata ); + void InputSetViewPositionSpeed( inputdata_t &inputdata ); + void InputSetReturnSpeed( inputdata_t &inputdata ); +#endif + +#ifdef MAPBASE_VSCRIPT + static ScriptHook_t g_Hook_PreStartCommentary; +#endif private: string_t m_iszPreCommands; @@ -119,6 +160,13 @@ class CPointCommentaryNode : public CBaseAnimating string_t m_iszViewPosition; CNetworkVar( EHANDLE, m_hViewPosition ); EHANDLE m_hViewPositionMover; // Entity used to blend the view to the viewposition entity +#ifdef MAPBASE + float m_flViewTargetSpeedScale; + float m_flViewPositionSpeedScale; + float m_flReturnSpeedScale; + CNetworkVar( string_t, m_iszPrintName ); + float m_flViewPositionChangedTime; // View position now blends relative to this value. Mainly needed for when SetViewPosition is used +#endif bool m_bPreventMovement; bool m_bUnderCrosshair; bool m_bUnstoppable; @@ -138,6 +186,13 @@ class CPointCommentaryNode : public CBaseAnimating CNetworkVar( string_t, m_iszSpeakers ); CNetworkVar( int, m_iNodeNumber ); CNetworkVar( int, m_iNodeNumberMax ); + +#ifdef MAPBASE + CNetworkVar( int, m_iCommentaryType ); + CNetworkVar( float, m_flPanelScale ); + CNetworkVar( float, m_flPanelX ); + CNetworkVar( float, m_flPanelY ); +#endif }; BEGIN_DATADESC( CPointCommentaryNode ) @@ -166,6 +221,17 @@ BEGIN_DATADESC( CPointCommentaryNode ) DEFINE_FIELD( m_bPreventChangesWhileMoving, FIELD_BOOLEAN ), DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "start_disabled" ), DEFINE_KEYFIELD( m_vecTeleportOrigin, FIELD_VECTOR, "teleport_origin" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_flViewTargetSpeedScale, FIELD_FLOAT, "viewtarget_speed" ), + DEFINE_KEYFIELD( m_flViewPositionSpeedScale, FIELD_FLOAT, "viewposition_speed" ), + DEFINE_KEYFIELD( m_flReturnSpeedScale, FIELD_FLOAT, "return_speed" ), + DEFINE_KEYFIELD( m_iszPrintName, FIELD_STRING, "printname" ), + DEFINE_FIELD( m_flViewPositionChangedTime, FIELD_TIME ), + DEFINE_KEYFIELD( m_iCommentaryType, FIELD_INTEGER, "type" ), + DEFINE_KEYFIELD( m_flPanelScale, FIELD_FLOAT, "panelscale" ), + DEFINE_KEYFIELD( m_flPanelX, FIELD_FLOAT, "x" ), + DEFINE_KEYFIELD( m_flPanelY, FIELD_FLOAT, "y" ), +#endif // Outputs DEFINE_OUTPUT( m_pOnCommentaryStarted, "OnCommentaryStarted" ), @@ -176,6 +242,13 @@ BEGIN_DATADESC( CPointCommentaryNode ) DEFINE_INPUTFUNC( FIELD_VOID, "StartUnstoppableCommentary", InputStartUnstoppableCommentary ), DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_EHANDLE, "SetViewTarget", InputSetViewTarget ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "SetViewPosition", InputSetViewPosition ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetViewTargetSpeed", InputSetViewTargetSpeed ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetViewPositionSpeed", InputSetViewPositionSpeed ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetReturnSpeed", InputSetReturnSpeed ), +#endif // Functions DEFINE_THINKFUNC( SpinThink ), @@ -183,6 +256,35 @@ BEGIN_DATADESC( CPointCommentaryNode ) DEFINE_THINKFUNC( UpdateViewPostThink ), END_DATADESC() +#ifdef MAPBASE_VSCRIPT + +ScriptHook_t CPointCommentaryNode::g_Hook_PreStartCommentary; + +BEGIN_ENT_SCRIPTDESC( CPointCommentaryNode, CBaseAnimating, "Commentary nodes which play commentary in commentary mode." ) + DEFINE_SCRIPTFUNC( IsDisabled, "" ) + DEFINE_SCRIPTFUNC( SetDisabled, "" ) + + DEFINE_SCRIPTFUNC( IsActive, "" ) + DEFINE_SCRIPTFUNC( GetCommentaryFile, "" ) + DEFINE_SCRIPTFUNC( SetCommentaryFile, "" ) + DEFINE_SCRIPTFUNC( GetSpeakers, "" ) + DEFINE_SCRIPTFUNC( SetSpeakers, "" ) + DEFINE_SCRIPTFUNC( GetPrintName, "" ) + DEFINE_SCRIPTFUNC( SetPrintName, "" ) + DEFINE_SCRIPTFUNC( GetCommentaryType, "" ) + DEFINE_SCRIPTFUNC( SetCommentaryType, "" ) + + DEFINE_SCRIPTFUNC( HasViewTarget, "" ) + DEFINE_SCRIPTFUNC( PreventsMovement, "" ) + DEFINE_SCRIPTFUNC( CannotBeStopped, "" ) + + DEFINE_SCRIPTFUNC( AbortPlaying, "Stops playing the node and snaps out of its camera control immediately. The game uses this function to shut down commentary while in the middle of playing a node, as it can't smoothly blend out (since the commentary entities need to be removed)." ) + + DEFINE_SIMPLE_SCRIPTHOOK( CPointCommentaryNode::g_Hook_PreStartCommentary, "PreStartCommentary", FIELD_BOOLEAN, "Called just before commentary begins. Use this to modify variables or commentary behavior before it begins. Returning false will prevent the commentary from starting." ) +END_SCRIPTDESC(); + +#endif // MAPBASE_VSCRIPT + IMPLEMENT_SERVERCLASS_ST( CPointCommentaryNode, DT_PointCommentaryNode ) SendPropBool( SENDINFO(m_bActive) ), SendPropStringT( SENDINFO(m_iszCommentaryFile) ), @@ -192,6 +294,13 @@ IMPLEMENT_SERVERCLASS_ST( CPointCommentaryNode, DT_PointCommentaryNode ) SendPropInt( SENDINFO(m_iNodeNumber), 8, SPROP_UNSIGNED ), SendPropInt( SENDINFO(m_iNodeNumberMax), 8, SPROP_UNSIGNED ), SendPropEHandle( SENDINFO(m_hViewPosition) ), +#ifdef MAPBASE + SendPropStringT( SENDINFO( m_iszPrintName ) ), + SendPropInt( SENDINFO( m_iCommentaryType ), 2, SPROP_UNSIGNED ), + SendPropFloat( SENDINFO( m_flPanelScale ) ), + SendPropFloat( SENDINFO( m_flPanelX ) ), + SendPropFloat( SENDINFO( m_flPanelY ) ), +#endif END_SEND_TABLE() LINK_ENTITY_TO_CLASS( point_commentary_node, CPointCommentaryNode ); @@ -892,10 +1001,32 @@ bool IsListeningToCommentary( void ) void CPointCommentaryNode::Spawn( void ) { // No model specified? - char *szModel = (char *)STRING( GetModelName() ); + const char *szModel = STRING( GetModelName() ); if (!szModel || !*szModel) { +#ifdef MAPBASE + switch (m_iCommentaryType) + { + case COMMENTARY_TYPE_TEXT: + szModel = "models/extras/info_text.mdl"; + break; + + case COMMENTARY_TYPE_IMAGE: + szModel = "models/extras/info_image.mdl"; + break; + + case COMMENTARY_TYPE_SCENE: + szModel = "models/extras/info_scene.mdl"; + break; + + default: + case COMMENTARY_TYPE_AUDIO: + szModel = "models/extras/info_speech.mdl"; + break; + } +#else szModel = "models/extras/info_speech.mdl"; +#endif SetModelName( AllocPooledString(szModel) ); } @@ -906,6 +1037,12 @@ void CPointCommentaryNode::Spawn( void ) AddSolidFlags( FSOLID_CUSTOMRAYTEST | FSOLID_CUSTOMBOXTEST ); AddEffects( EF_NOSHADOW ); +#ifdef MAPBASE + // Default to view position speed scale (which in turn defaults to 1.0) + if (m_flReturnSpeedScale == 0.0f) + m_flReturnSpeedScale = m_flViewPositionSpeedScale; +#endif + // Setup for animation ResetSequence( LookupSequence("idle") ); SetThink( &CPointCommentaryNode::SpinThink ); @@ -1119,6 +1256,19 @@ void CPointCommentaryNode::StartCommentary( void ) if ( !pPlayer ) return; +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_PreStartCommentary.CanRunInScope( m_ScriptScope )) + { + ScriptVariant_t functionReturn; + if ( g_Hook_PreStartCommentary.Call( m_ScriptScope, &functionReturn, NULL ) && functionReturn.m_type == FIELD_BOOLEAN ) + { + // Don't play the commentary if it returned false + if (functionReturn.m_bool == false) + return; + } + } +#endif + m_bActive = true; m_flAnimTime = gpGlobals->curtime; @@ -1140,6 +1290,21 @@ void CPointCommentaryNode::StartCommentary( void ) // Start the commentary m_flStartTime = gpGlobals->curtime; +#ifdef MAPBASE + if (m_hViewPosition.Get()) + { + m_flViewPositionChangedTime = gpGlobals->curtime; + } + else + { + m_flViewPositionChangedTime = -1.0f; + } + + // This is now used in certain places to denote the "last blend to" origin + m_vecFinishOrigin = pPlayer->EyePosition(); + m_vecFinishAngles = pPlayer->EyeAngles(); +#endif + // If we have a view target, start blending towards it if ( m_hViewTarget || m_hViewPosition.Get() ) { @@ -1214,6 +1379,10 @@ void CPointCommentaryNode::UpdateViewThink( void ) float dx = AngleDiff( angGoal.x, angCurrent.x ); float dy = AngleDiff( angGoal.y, angCurrent.y ); float mod = 1.0 - ExponentialDecay( 0.5, 0.3, gpGlobals->frametime ); +#ifdef MAPBASE + if (m_flViewTargetSpeedScale != 1.0f) + mod *= m_flViewTargetSpeedScale; +#endif float dxmod = dx * mod; float dymod = dy * mod; @@ -1254,16 +1423,85 @@ void CPointCommentaryNode::UpdateViewThink( void ) } // Blend to the target position over time. - float flCurTime = (gpGlobals->curtime - m_flStartTime); +#ifdef MAPBASE + float flCurTime = (gpGlobals->curtime - m_flViewPositionChangedTime); + if (m_flViewPositionSpeedScale != 1.0f) + flCurTime *= m_flViewPositionSpeedScale; +#else + float flCurTime = (gpGlobals->curtime - m_flStartTime); +#endif float flBlendPerc = clamp( flCurTime * 0.5f, 0.f, 1.f ); // Figure out the current view position Vector vecCurEye; +#ifdef MAPBASE + VectorLerp( m_vecFinishOrigin, m_hViewPosition.Get()->GetAbsOrigin(), flBlendPerc, vecCurEye ); +#else VectorLerp( pPlayer->EyePosition(), m_hViewPosition.Get()->GetAbsOrigin(), flBlendPerc, vecCurEye ); +#endif m_hViewPositionMover->SetAbsOrigin( vecCurEye ); SetNextThink( gpGlobals->curtime, s_pCommentaryUpdateViewThink ); } +#ifdef MAPBASE + else if ( m_flViewPositionChangedTime != -1.0f && m_hViewPositionMover ) + { + // Blend back to the player's position over time. + float flCurTime = (gpGlobals->curtime - m_flViewPositionChangedTime); + if (m_flViewPositionSpeedScale != 1.0f) + flCurTime *= m_flViewPositionSpeedScale; + + //float flTimeToBlend = MIN( 2.0, m_flViewPositionChangedTime - m_flStartTime ); + //float flBlendPerc = 1.0f - clamp( flCurTime / flTimeToBlend, 0.f, 1.f ); + float flBlendPerc = 1.0f - clamp( flCurTime * 0.5f, 0.f, 1.f ); + + //Msg("OUT: CurTime %.2f, BlendTime: %.2f, Blend: %.3f\n", flCurTime, flTimeToBlend, flBlendPerc ); + + // Only do this while we're still moving + if ( flBlendPerc > 0 ) + { + // Figure out the current view position + Vector vecPlayerPos = pPlayer->EyePosition(); + Vector vecToPosition = (m_vecFinishOrigin - vecPlayerPos); + Vector vecCurEye = pPlayer->EyePosition() + (vecToPosition * flBlendPerc); + m_hViewPositionMover->SetAbsOrigin( vecCurEye ); + + if ( m_hViewTarget ) + { + Quaternion quatFinish; + Quaternion quatOriginal; + Quaternion quatCurrent; + AngleQuaternion( m_vecOriginalAngles, quatOriginal ); + AngleQuaternion( m_vecFinishAngles, quatFinish ); + QuaternionSlerp( quatFinish, quatOriginal, 1.0 - flBlendPerc, quatCurrent ); + QAngle angCurrent; + QuaternionAngles( quatCurrent, angCurrent ); + m_hViewPositionMover->SetAbsAngles( angCurrent ); + } + + SetNextThink( gpGlobals->curtime, s_pCommentaryUpdateViewThink ); + return; + } + else + { + pPlayer->SnapEyeAngles( m_hViewPositionMover->GetAbsAngles() ); + + // Try to clean up the view position stuff without ending the commentary + if ( !m_hViewTargetAngles && pPlayer->GetActiveWeapon() ) + { + pPlayer->GetActiveWeapon()->Deploy(); + } + + if (pPlayer->GetViewEntity() == m_hViewPositionMover) + { + pPlayer->SetViewEntity( NULL ); + } + UTIL_Remove( m_hViewPositionMover ); + + m_flViewPositionChangedTime = -1.0f; + } + } +#endif } //----------------------------------------------------------------------------- @@ -1279,6 +1517,10 @@ void CPointCommentaryNode::UpdateViewPostThink( void ) { // Blend back to the player's position over time. float flCurTime = (gpGlobals->curtime - m_flFinishedTime); +#ifdef MAPBASE + if (m_flReturnSpeedScale != 1.0f) + flCurTime *= m_flReturnSpeedScale; +#endif float flTimeToBlend = MIN( 2.0, m_flFinishedTime - m_flStartTime ); float flBlendPerc = 1.0f - clamp( flCurTime / flTimeToBlend, 0.f, 1.f ); @@ -1440,6 +1682,79 @@ void CPointCommentaryNode::InputDisable( inputdata_t &inputdata ) SetDisabled( true ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::InputSetViewTarget( inputdata_t &inputdata ) +{ + m_hViewTarget = inputdata.value.Entity(); + + // Do not let Activate() reassign this + m_iszViewTarget = NULL_STRING; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::InputSetViewPosition( inputdata_t &inputdata ) +{ + if (m_hViewPosition.Get() && m_hViewPositionMover) + { + // In case the view position is being cleared, assign the "finish" vectors + m_vecFinishOrigin = m_hViewPositionMover->GetAbsOrigin(); + m_vecFinishAngles = m_hViewPositionMover->GetAbsAngles(); + } + else + { + CBasePlayer *pPlayer = GetCommentaryPlayer(); + if (pPlayer) + { + // And in case it's a new view position coming from the player, assign the "finish" vectors to the player + m_vecFinishOrigin = pPlayer->EyePosition(); + m_vecFinishAngles = m_vecOriginalAngles = pPlayer->EyeAngles(); + } + } + + m_hViewPosition = inputdata.value.Entity(); + + // Do not let Activate() reassign this + m_iszViewPosition = NULL_STRING; + + m_flViewPositionChangedTime = gpGlobals->curtime; + + // If we have a view target, start blending towards it + if ( m_hViewPosition.Get() ) + { + SetContextThink( &CPointCommentaryNode::UpdateViewThink, gpGlobals->curtime, s_pCommentaryUpdateViewThink ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::InputSetViewTargetSpeed( inputdata_t &inputdata ) +{ + m_flViewTargetSpeedScale = inputdata.value.Float(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::InputSetViewPositionSpeed( inputdata_t &inputdata ) +{ + m_flViewPositionSpeedScale = inputdata.value.Float(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::InputSetReturnSpeed( inputdata_t &inputdata ) +{ + m_flReturnSpeedScale = inputdata.value.Float(); +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -1501,7 +1816,11 @@ void CPointCommentaryNode::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways bool CPointCommentaryNode::PreventsMovement( void ) { // If we're moving the player's view at all, prevent movement +#ifdef MAPBASE + if ( m_hViewPosition.Get() || m_flViewPositionChangedTime != -1.0f ) +#else if ( m_hViewPosition.Get() ) +#endif return true; return m_bPreventMovement; diff --git a/sp/src/game/server/EntityFlame.cpp b/sp/src/game/server/EntityFlame.cpp index d3a1be102e..80217efb17 100644 --- a/sp/src/game/server/EntityFlame.cpp +++ b/sp/src/game/server/EntityFlame.cpp @@ -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! diff --git a/sp/src/game/server/ai_activity.cpp b/sp/src/game/server/ai_activity.cpp index b88c114289..d23168577d 100644 --- a/sp/src/game/server/ai_activity.cpp +++ b/sp/src/game/server/ai_activity.cpp @@ -2180,15 +2180,22 @@ void CAI_BaseNPC::InitDefaultActivitySR(void) ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_AR2 ); ADD_ACTIVITY_TO_SR( ACT_RELOAD_AR2 ); - //ADD_ACTIVITY_TO_SR( ACT_RELOAD_AR2_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_AR2_LOW ); ADD_ACTIVITY_TO_SR( ACT_GESTURE_RELOAD_AR2 ); + + ADD_ACTIVITY_TO_SR( ACT_COVER_AR2_LOW ); #endif -#ifdef SHARED_COMBINE_ACTIVITIES +#if SHARED_COMBINE_ACTIVITIES ADD_ACTIVITY_TO_SR( ACT_COMBINE_THROW_GRENADE ); ADD_ACTIVITY_TO_SR( ACT_COMBINE_AR2_ALTFIRE ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_COMBINE_THROW_GRENADE ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_COMBINE_AR2_ALTFIRE ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_SPECIAL_ATTACK1 ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_SPECIAL_ATTACK2 ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_SIGNAL_ADVANCE ); ADD_ACTIVITY_TO_SR( ACT_GESTURE_SIGNAL_FORWARD ); ADD_ACTIVITY_TO_SR( ACT_GESTURE_SIGNAL_GROUP ); @@ -2198,8 +2205,479 @@ void CAI_BaseNPC::InitDefaultActivitySR(void) ADD_ACTIVITY_TO_SR( ACT_GESTURE_SIGNAL_TAKECOVER ); #endif -#ifdef COMPANION_HOLSTER_WORKAROUND +#if EXPANDED_HL2_WEAPON_ACTIVITIES + ADD_ACTIVITY_TO_SR( ACT_IDLE_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_ANGRY_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_WALK_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_RUN_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_REVOLVER_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_REVOLVER_LOW ); + ADD_ACTIVITY_TO_SR( ACT_COVER_REVOLVER_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_REVOLVER_LOW ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RANGE_ATTACK_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RELOAD_REVOLVER ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_CROSSBOW ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_ANGRY_CROSSBOW ); + ADD_ACTIVITY_TO_SR( ACT_WALK_CROSSBOW ); + ADD_ACTIVITY_TO_SR( ACT_RUN_CROSSBOW ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_CROSSBOW ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_CROSSBOW ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_CROSSBOW ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_CROSSBOW ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_CROSSBOW_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_CROSSBOW_LOW ); + ADD_ACTIVITY_TO_SR( ACT_COVER_CROSSBOW_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_CROSSBOW_LOW ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RANGE_ATTACK_CROSSBOW ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RELOAD_CROSSBOW ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_CROSSBOW_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_CROSSBOW_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_CROSSBOW_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_CROSSBOW_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_CROSSBOW_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_CROSSBOW_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_AIM_CROSSBOW_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_CROSSBOW_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_CROSSBOW_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_PISTOL_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_PISTOL_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_PISTOL_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_PISTOL_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_PISTOL_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_PISTOL_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_AIM_PISTOL_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_PISTOL_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_PISTOL_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_WALK_CROUCH_PISTOL ); + ADD_ACTIVITY_TO_SR( ACT_WALK_CROUCH_AIM_PISTOL ); + ADD_ACTIVITY_TO_SR( ACT_RUN_CROUCH_PISTOL ); + ADD_ACTIVITY_TO_SR( ACT_RUN_CROUCH_AIM_PISTOL ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_SHOTGUN ); + ADD_ACTIVITY_TO_SR( ACT_WALK_SHOTGUN ); + ADD_ACTIVITY_TO_SR( ACT_RUN_SHOTGUN ); + + ADD_ACTIVITY_TO_SR( ACT_COVER_SHOTGUN_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_SHOTGUN_LOW ); + + ADD_ACTIVITY_TO_SR( ACT_WALK_SHOTGUN_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_SHOTGUN_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_SHOTGUN_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_SHOTGUN_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_AIM_SHOTGUN_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_SHOTGUN_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_SHOTGUN_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_RPG_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_RPG_LOW ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RANGE_ATTACK_RPG ); + + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_ANNABELLE ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_ANNABELLE_LOW ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RANGE_ATTACK_ANNABELLE ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_ANNABELLE ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_ANNABELLE_LOW ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RELOAD_ANNABELLE ); + + ADD_ACTIVITY_TO_SR( ACT_WALK_MELEE ); + ADD_ACTIVITY_TO_SR( ACT_RUN_MELEE ); + + ADD_ACTIVITY_TO_SR( ACT_RUN_PACKAGE ); + ADD_ACTIVITY_TO_SR( ACT_RUN_SUITCASE ); + ADD_ACTIVITY_TO_SR( ACT_ARM_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_ARM_SHOTGUN ); + ADD_ACTIVITY_TO_SR( ACT_ARM_RPG ); + ADD_ACTIVITY_TO_SR( ACT_ARM_MELEE ); ADD_ACTIVITY_TO_SR( ACT_DISARM_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_DISARM_SHOTGUN ); + ADD_ACTIVITY_TO_SR( ACT_DISARM_RPG ); + ADD_ACTIVITY_TO_SR( ACT_DISARM_MELEE ); +#endif + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + ADD_ACTIVITY_TO_SR( ACT_IDLE_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_ANGRY_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_AR1 ); + //ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_AR1_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_AR1_LOW ); + ADD_ACTIVITY_TO_SR( ACT_COVER_AR1_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_AR1_LOW ); + //ADD_ACTIVITY_TO_SR( ACT_GESTURE_RANGE_ATTACK_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RELOAD_AR1 ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_AR1_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_AR1_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AR1_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AR1_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AR1_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AR1_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_AIM_AR1_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_AR1_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_AR1_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_ANGRY_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_WALK_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_RUN_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_SMG2 ); + //ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_SMG2_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_SMG2_LOW ); + ADD_ACTIVITY_TO_SR( ACT_COVER_SMG2_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_SMG2_LOW ); + //ADD_ACTIVITY_TO_SR( ACT_GESTURE_RANGE_ATTACK_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RELOAD_SMG2 ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_SMG2_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_SMG2_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_SMG2_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_SMG2_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_SMG2_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_SMG2_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_AIM_SMG2_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_SMG2_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_SMG2_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_ANGRY_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_WALK_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_RUN_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_SNIPER_RIFLE ); + //ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_SNIPER_RIFLE_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_SNIPER_RIFLE_LOW ); + ADD_ACTIVITY_TO_SR( ACT_COVER_SNIPER_RIFLE_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_SNIPER_RIFLE_LOW ); + //ADD_ACTIVITY_TO_SR( ACT_GESTURE_RANGE_ATTACK_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RELOAD_SNIPER_RIFLE ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_SNIPER_RIFLE_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_SNIPER_RIFLE_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_SNIPER_RIFLE_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_SNIPER_RIFLE_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_SNIPER_RIFLE_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_SNIPER_RIFLE_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_AIM_SNIPER_RIFLE_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_SNIPER_RIFLE_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_SNIPER_RIFLE_STIMULATED ); +#endif + +#if EXPANDED_NAVIGATION_ACTIVITIES + ADD_ACTIVITY_TO_SR( ACT_CLIMB_ALL ); + ADD_ACTIVITY_TO_SR( ACT_CLIMB_IDLE ); + + ADD_ACTIVITY_TO_SR( ACT_CLIMB_MOUNT_TOP ); + ADD_ACTIVITY_TO_SR( ACT_CLIMB_MOUNT_BOTTOM ); + ADD_ACTIVITY_TO_SR( ACT_CLIMB_DISMOUNT_BOTTOM ); +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK1_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK2_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_MED ); + + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_AR2_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_SMG1_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_SHOTGUN_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_PISTOL_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_RPG_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_REVOLVER_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_CROSSBOW_MED ); + + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_AR2_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_SMG1_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_SHOTGUN_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_PISTOL_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_RPG_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_REVOLVER_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_CROSSBOW_MED ); + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_AR1_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_AR1_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_SMG2_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_SMG2_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_SNIPER_RIFLE_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_SNIPER_RIFLE_MED ); +#endif + + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_R ); + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_L ); + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_LOW_R ); + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_LOW_L ); + + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_R_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_L_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_LOW_R_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_LOW_L_RIFLE ); + + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_R_PISTOL ); + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_L_PISTOL ); + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_LOW_R_PISTOL ); + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_LOW_L_PISTOL ); +#endif + +#if EXPANDED_HL2DM_ACTIVITIES + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_PISTOL ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_SHOTGUN ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_SMG1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_AR2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_PHYSGUN ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_GRENADE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_RPG ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_CROSSBOW ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_MELEE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_SLAM ); + + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_PISTOL ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SHOTGUN ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_PHYSGUN ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_GRENADE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_RPG ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_CROSSBOW ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_MELEE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SLAM ); + + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_RUN_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_CROUCH_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_CROUCH_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELOAD_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_REVOLVER ); + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_RUN_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_CROUCH_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_CROUCH_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELOAD_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_AR1 ); + + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_RUN_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_CROUCH_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_CROUCH_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELOAD_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_SMG2 ); + + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_RUN_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_CROUCH_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_CROUCH_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELOAD_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_SNIPER_RIFLE ); +#endif + + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_USE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_RUN_USE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_USE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_CROUCH_USE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_CROUCH_USE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_USE ); + + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_USE_HEAVY ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_RUN_USE_HEAVY ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_USE_HEAVY ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_CROUCH_USE_HEAVY ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_CROUCH_USE_HEAVY ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_USE_HEAVY ); #endif } + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: This is a multi-purpose table which links NPC activities to their gesture variants. +//----------------------------------------------------------------------------- +CAI_BaseNPC::actlink_t CAI_BaseNPC::gm_ActivityGestureLinks[] = +{ + { ACT_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK1 }, + { ACT_RANGE_ATTACK2, ACT_GESTURE_RANGE_ATTACK2 }, + { ACT_MELEE_ATTACK1, ACT_GESTURE_MELEE_ATTACK1 }, + { ACT_MELEE_ATTACK2, ACT_GESTURE_MELEE_ATTACK2 }, + { ACT_RELOAD, ACT_GESTURE_RELOAD }, + + { ACT_RANGE_ATTACK1_LOW, ACT_GESTURE_RANGE_ATTACK1 }, // NOTE: ACT_GESTURE_RANGE_ATTACK1_LOW exists, but isn't used + { ACT_RANGE_ATTACK2_LOW, ACT_GESTURE_RANGE_ATTACK2 }, // NOTE: ACT_GESTURE_RANGE_ATTACK2_LOW exists, but isn't used + { ACT_RELOAD_LOW, ACT_GESTURE_RELOAD }, + + { ACT_MELEE_ATTACK_SWING, ACT_GESTURE_MELEE_ATTACK_SWING }, + + // ----------------------------------------------------------- + + { ACT_RANGE_ATTACK_AR2, ACT_GESTURE_RANGE_ATTACK_AR2 }, + { ACT_RANGE_ATTACK_AR2_LOW, ACT_GESTURE_RANGE_ATTACK_AR2 }, + { ACT_RANGE_ATTACK_SMG1, ACT_GESTURE_RANGE_ATTACK_SMG1 }, + { ACT_RANGE_ATTACK_SMG1_LOW, ACT_GESTURE_RANGE_ATTACK_SMG1 }, + { ACT_RANGE_ATTACK_SHOTGUN, ACT_GESTURE_RANGE_ATTACK_SHOTGUN }, + { ACT_RANGE_ATTACK_SHOTGUN_LOW, ACT_GESTURE_RANGE_ATTACK_SHOTGUN }, + { ACT_RANGE_ATTACK_PISTOL, ACT_GESTURE_RANGE_ATTACK_PISTOL }, + { ACT_RANGE_ATTACK_PISTOL_LOW, ACT_GESTURE_RANGE_ATTACK_PISTOL }, + + // ----------------------------------------------------------- + + { ACT_SMALL_FLINCH, ACT_GESTURE_SMALL_FLINCH }, + { ACT_BIG_FLINCH, ACT_GESTURE_BIG_FLINCH }, + { ACT_FLINCH_HEAD, ACT_GESTURE_FLINCH_HEAD }, + { ACT_FLINCH_CHEST, ACT_GESTURE_FLINCH_CHEST }, + { ACT_FLINCH_STOMACH, ACT_GESTURE_FLINCH_STOMACH }, + { ACT_FLINCH_LEFTARM, ACT_GESTURE_FLINCH_LEFTARM }, + { ACT_FLINCH_RIGHTARM, ACT_GESTURE_FLINCH_RIGHTARM }, + { ACT_FLINCH_LEFTLEG, ACT_GESTURE_FLINCH_LEFTLEG }, + { ACT_FLINCH_RIGHTLEG, ACT_GESTURE_FLINCH_RIGHTLEG }, + + // ----------------------------------------------------------- + +#if AR2_ACTIVITY_FIX == 1 + { ACT_RELOAD_AR2, ACT_GESTURE_RELOAD_AR2 }, + { ACT_RELOAD_AR2_LOW, ACT_GESTURE_RELOAD_AR2 }, +#endif + { ACT_RELOAD_SMG1, ACT_GESTURE_RELOAD_SMG1 }, + { ACT_RELOAD_SMG1_LOW, ACT_GESTURE_RELOAD_SMG1 }, + { ACT_RELOAD_SHOTGUN, ACT_GESTURE_RELOAD_SHOTGUN }, + { ACT_RELOAD_SHOTGUN_LOW, ACT_GESTURE_RELOAD_SHOTGUN }, + { ACT_RELOAD_PISTOL, ACT_GESTURE_RELOAD_PISTOL }, + { ACT_RELOAD_PISTOL_LOW, ACT_GESTURE_RELOAD_PISTOL }, + +#if SHARED_COMBINE_ACTIVITIES + { ACT_SPECIAL_ATTACK1, ACT_GESTURE_SPECIAL_ATTACK1 }, + { ACT_SPECIAL_ATTACK2, ACT_GESTURE_SPECIAL_ATTACK2 }, + { ACT_COMBINE_THROW_GRENADE, ACT_GESTURE_COMBINE_THROW_GRENADE }, + { ACT_COMBINE_AR2_ALTFIRE, ACT_GESTURE_COMBINE_AR2_ALTFIRE }, + + { ACT_SIGNAL_ADVANCE, ACT_GESTURE_SIGNAL_ADVANCE }, + { ACT_SIGNAL_FORWARD, ACT_GESTURE_SIGNAL_FORWARD }, + { ACT_SIGNAL_GROUP, ACT_GESTURE_SIGNAL_GROUP }, + { ACT_SIGNAL_HALT, ACT_GESTURE_SIGNAL_HALT }, + { ACT_SIGNAL_LEFT, ACT_GESTURE_SIGNAL_LEFT }, + { ACT_SIGNAL_RIGHT, ACT_GESTURE_SIGNAL_RIGHT }, + { ACT_SIGNAL_TAKECOVER, ACT_GESTURE_SIGNAL_TAKECOVER }, +#endif + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_RANGE_ATTACK_REVOLVER, ACT_GESTURE_RANGE_ATTACK_REVOLVER }, + { ACT_RANGE_ATTACK_REVOLVER_LOW, ACT_GESTURE_RANGE_ATTACK_REVOLVER }, + { ACT_RANGE_ATTACK_CROSSBOW, ACT_GESTURE_RANGE_ATTACK_CROSSBOW }, + { ACT_RANGE_ATTACK_CROSSBOW_LOW, ACT_GESTURE_RANGE_ATTACK_CROSSBOW }, + { ACT_RANGE_ATTACK_RPG, ACT_GESTURE_RANGE_ATTACK_RPG }, + { ACT_RANGE_ATTACK_RPG_LOW, ACT_GESTURE_RANGE_ATTACK_RPG }, + { ACT_RANGE_ATTACK_ANNABELLE, ACT_GESTURE_RANGE_ATTACK_ANNABELLE }, + { ACT_RANGE_ATTACK_ANNABELLE_LOW, ACT_GESTURE_RANGE_ATTACK_ANNABELLE }, + + { ACT_RELOAD_REVOLVER, ACT_GESTURE_RELOAD_REVOLVER }, + { ACT_RELOAD_REVOLVER_LOW, ACT_GESTURE_RELOAD_REVOLVER }, + { ACT_RELOAD_CROSSBOW, ACT_GESTURE_RELOAD_CROSSBOW }, + { ACT_RELOAD_CROSSBOW_LOW, ACT_GESTURE_RELOAD_CROSSBOW }, + { ACT_RELOAD_ANNABELLE, ACT_GESTURE_RELOAD_ANNABELLE }, + { ACT_RELOAD_ANNABELLE_LOW, ACT_GESTURE_RELOAD_ANNABELLE }, +#endif + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + { ACT_RANGE_ATTACK_AR1, ACT_GESTURE_RANGE_ATTACK_AR1 }, + { ACT_RANGE_ATTACK_AR1_LOW, ACT_GESTURE_RANGE_ATTACK_AR1 }, + { ACT_RANGE_ATTACK_AR2_GRENADE, ACT_GESTURE_RANGE_ATTACK_AR2_GRENADE }, + { ACT_RANGE_ATTACK_HMG1, ACT_GESTURE_RANGE_ATTACK_HMG1 }, + { ACT_RANGE_ATTACK_ML, ACT_GESTURE_RANGE_ATTACK_ML }, + { ACT_RANGE_ATTACK_SMG2, ACT_GESTURE_RANGE_ATTACK_SMG2 }, + { ACT_RANGE_ATTACK_SMG2_LOW, ACT_GESTURE_RANGE_ATTACK_SMG2 }, + { ACT_RANGE_ATTACK_SLAM, ACT_GESTURE_RANGE_ATTACK_SLAM }, + { ACT_RANGE_ATTACK_TRIPWIRE, ACT_GESTURE_RANGE_ATTACK_TRIPWIRE }, + { ACT_RANGE_ATTACK_THROW, ACT_GESTURE_RANGE_ATTACK_THROW }, + { ACT_RANGE_ATTACK_SNIPER_RIFLE, ACT_GESTURE_RANGE_ATTACK_SNIPER_RIFLE }, + { ACT_RANGE_ATTACK_SNIPER_RIFLE_LOW, ACT_GESTURE_RANGE_ATTACK_SNIPER_RIFLE }, + + { ACT_RELOAD_AR1, ACT_GESTURE_RELOAD_AR1 }, + { ACT_RELOAD_AR1_LOW, ACT_GESTURE_RELOAD_AR1 }, + { ACT_RELOAD_SMG2, ACT_GESTURE_RELOAD_SMG2 }, + { ACT_RELOAD_SMG2_LOW, ACT_GESTURE_RELOAD_SMG2 }, + { ACT_RELOAD_SNIPER_RIFLE, ACT_GESTURE_RELOAD_SNIPER_RIFLE }, + { ACT_RELOAD_SNIPER_RIFLE_LOW, ACT_GESTURE_RELOAD_SNIPER_RIFLE }, +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_ATTACK1_MED, ACT_GESTURE_RANGE_ATTACK1 }, + { ACT_RANGE_ATTACK2_MED, ACT_GESTURE_RANGE_ATTACK2 }, + + { ACT_RANGE_ATTACK_AR2_MED, ACT_GESTURE_RANGE_ATTACK_AR2 }, + { ACT_RANGE_ATTACK_SMG1_MED, ACT_GESTURE_RANGE_ATTACK_SMG1 }, + { ACT_RANGE_ATTACK_SHOTGUN_MED, ACT_GESTURE_RANGE_ATTACK_SHOTGUN }, + { ACT_RANGE_ATTACK_PISTOL_MED, ACT_GESTURE_RANGE_ATTACK_PISTOL }, +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_RANGE_ATTACK_RPG_MED, ACT_GESTURE_RANGE_ATTACK_RPG }, + { ACT_RANGE_ATTACK_REVOLVER_MED, ACT_GESTURE_RANGE_ATTACK_REVOLVER }, + { ACT_RANGE_ATTACK_CROSSBOW_MED, ACT_GESTURE_RANGE_ATTACK_CROSSBOW }, +#endif +#endif +}; + +Activity CAI_BaseNPC::GetGestureVersionOfActivity( Activity inActivity ) +{ + actlink_t *pTable = gm_ActivityGestureLinks; + int actCount = ARRAYSIZE( gm_ActivityGestureLinks ); + + for ( int i = 0; i < actCount; i++, pTable++ ) + { + if ( inActivity == pTable->sequence ) + { + return pTable->gesture; + } + } + + return ACT_INVALID; +} + +Activity CAI_BaseNPC::GetSequenceVersionOfGesture( Activity inActivity ) +{ + actlink_t *pTable = gm_ActivityGestureLinks; + int actCount = ARRAYSIZE( gm_ActivityGestureLinks ); + + for (int i = 0; i < actCount; i++, pTable++) + { + if (inActivity == pTable->gesture) + { + return pTable->sequence; + } + } + + return ACT_INVALID; +} +#endif diff --git a/sp/src/game/server/ai_baseactor.cpp b/sp/src/game/server/ai_baseactor.cpp index 2ddc60f722..248c0841f2 100644 --- a/sp/src/game/server/ai_baseactor.cpp +++ b/sp/src/game/server/ai_baseactor.cpp @@ -101,8 +101,8 @@ END_DATADESC() #ifdef MAPBASE_VSCRIPT BEGIN_ENT_SCRIPTDESC( CAI_BaseActor, CAI_BaseNPC, "The base class for NPCs which act in complex choreo scenes." ) - DEFINE_SCRIPTFUNC_NAMED( ScriptAddLookTarget, "AddLookTarget", "Add a potential look target for this actor." ) - DEFINE_SCRIPTFUNC_NAMED( ScriptAddLookTargetPos, "AddLookTargetPos", "Add a potential look target position for this actor." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptAddLookTarget, "AddLookTarget", "Add a potential look target for this actor with the specified importance, duration, and ramp." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptAddLookTargetPos, "AddLookTargetPos", "Add a potential look target position for this actor with the specified importance, duration, and ramp." ) END_SCRIPTDESC(); #endif @@ -835,7 +835,11 @@ void CAI_BaseActor::UpdateLatchedValues( ) // set head latch m_fLatchedPositions |= HUMANOID_LATCHED_HEAD; +#ifdef MAPBASE // From Alien Swarm SDK + if ( CanSkipAnimation() || !GetAttachment( "eyes", m_latchedEyeOrigin, &m_latchedHeadDirection )) +#else if (!HasCondition( COND_IN_PVS ) || !GetAttachment( "eyes", m_latchedEyeOrigin, &m_latchedHeadDirection )) +#endif { m_latchedEyeOrigin = BaseClass::EyePosition( ); AngleVectors( GetLocalAngles(), &m_latchedHeadDirection ); @@ -1626,7 +1630,11 @@ void CAI_BaseActor::MaintainLookTargets( float flInterval ) } // don't bother with any of the rest if the player can't see you +#ifdef MAPBASE // From Alien Swarm SDK + if ( CanSkipAnimation() ) +#else if (!HasCondition( COND_IN_PVS )) +#endif { return; } @@ -2033,7 +2041,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; } diff --git a/sp/src/game/server/ai_basenpc.cpp b/sp/src/game/server/ai_basenpc.cpp index 0f4e6a3543..c5fa8c7bbc 100644 --- a/sp/src/game/server/ai_basenpc.cpp +++ b/sp/src/game/server/ai_basenpc.cpp @@ -97,6 +97,11 @@ #ifdef MAPBASE #include "mapbase/matchers.h" +#include "items.h" +#endif + +#ifdef MAPBASE_VSCRIPT +#include "mapbase/vscript_funcs_shared.h" #endif #include "env_debughistory.h" @@ -164,6 +169,8 @@ extern ConVar ai_vehicle_avoidance; extern ISoundEmitterSystemBase *soundemitterbase; ConVar ai_dynint_always_enabled( "ai_dynint_always_enabled", "0", FCVAR_NONE, "Makes the \"Don't Care\" setting equivalent to \"Yes\"." ); + +ConVar ai_debug_fake_sequence_gestures_always_play( "ai_debug_fake_sequence_gestures_always_play", "0", FCVAR_NONE, "Always plays fake sequence gestures." ); #endif #ifndef _RETAIL @@ -308,6 +315,8 @@ ScriptHook_t CAI_BaseNPC::g_Hook_QuerySeeEntity; ScriptHook_t CAI_BaseNPC::g_Hook_TranslateActivity; ScriptHook_t CAI_BaseNPC::g_Hook_TranslateSchedule; ScriptHook_t CAI_BaseNPC::g_Hook_GetActualShootPosition; +ScriptHook_t CAI_BaseNPC::g_Hook_OverrideMove; +ScriptHook_t CAI_BaseNPC::g_Hook_ShouldPlayFakeSequenceGesture; #endif // @@ -663,7 +672,7 @@ void CAI_BaseNPC::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bo #ifdef HL2_EPISODIC CBasePlayer *pPlayer = AI_GetSinglePlayer(); - if ( pPlayer->IRelationType( this ) != D_LI ) + if ( pPlayer && pPlayer->IRelationType( this ) != D_LI ) { CNPC_Alyx *alyx = CNPC_Alyx::GetAlyx(); @@ -728,7 +737,7 @@ HSCRIPT CAI_BaseNPC::VScriptFindEnemyMemory( HSCRIPT pEnemy ) AI_EnemyInfo_t *info = GetEnemies()->Find( ToEnt(pEnemy) ); if (info) { - hScript = g_pScriptVM->RegisterInstance( info ); + hScript = g_pScriptVM->RegisterInstance( reinterpret_cast(info) ); } return hScript; @@ -988,29 +997,27 @@ int CAI_BaseNPC::OnTakeDamage_Alive( const CTakeDamageInfo &info ) // only fire once per frame m_OnDamaged.FireOutput( info.GetAttacker(), this); - if( info.GetAttacker()->IsPlayer() ) + if ( info.GetAttacker() ) { - m_OnDamagedByPlayer.FireOutput( info.GetAttacker(), this ); + if( info.GetAttacker()->IsPlayer() ) + { + m_OnDamagedByPlayer.FireOutput( info.GetAttacker(), this ); - // This also counts as being harmed by player's squad. - m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this ); - } - else - { - // See if the person that injured me is an NPC. -#ifdef MAPBASE - // Sometimes I find these things and I can't just sit idly by. - CAI_BaseNPC *pAttacker = info.GetAttacker()->MyNPCPointer(); -#else - CAI_BaseNPC *pAttacker = dynamic_cast( info.GetAttacker() ); -#endif - CBasePlayer *pPlayer = AI_GetSinglePlayer(); - - if( pAttacker && pAttacker->IsAlive() && pPlayer ) + // This also counts as being harmed by player's squad. + m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this ); + } + else { - if( pAttacker->GetSquad() != NULL && pAttacker->IsInPlayerSquad() ) + // See if the person that injured me is an NPC. + CAI_BaseNPC *pAttacker = info.GetAttacker()->MyNPCPointer(); + CBasePlayer *pPlayer = AI_GetSinglePlayer(); + + if( pAttacker && pAttacker->IsAlive() && pPlayer ) { - m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this ); + if( pAttacker->GetSquad() != NULL && pAttacker->IsInPlayerSquad() ) + { + m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this ); + } } } } @@ -2591,7 +2598,11 @@ bool CAI_BaseNPC::FValidateHintType ( CAI_Hint *pHint ) Activity CAI_BaseNPC::GetHintActivity( short sHintType, Activity HintsActivity ) { if ( HintsActivity != ACT_INVALID ) +#ifdef MAPBASE + return TranslateActivity( HintsActivity ); // Always translate the activity +#else return HintsActivity; +#endif return ACT_IDLE; } @@ -6506,7 +6517,7 @@ CAI_BaseNPC *CAI_BaseNPC::CreateCustomTarget( const Vector &vecOrigin, float dur //----------------------------------------------------------------------------- Activity CAI_BaseNPC::TranslateCrouchActivity( Activity eNewActivity ) { - if (CapabilitiesGet() & bits_CAP_DUCK) + if (CapabilitiesGet() & bits_CAP_DUCK && CanTranslateCrouchActivity()) { // ======================================================================== // The main issue with cover hint nodes is that crouch activities are not translated at the right time @@ -6538,10 +6549,18 @@ Activity CAI_BaseNPC::TranslateCrouchActivity( Activity eNewActivity ) CAI_Hint *pHint = GetHintNode(); if (pHint) { - if (pHint->HintType() == HINT_TACTICAL_COVER_LOW || pHint->HintType() == HINT_TACTICAL_COVER_MED) + if (pHint->HintType() == HINT_TACTICAL_COVER_LOW) { nCoverActivity = ACT_RANGE_ATTACK1_LOW; } + else if (pHint->HintType() == HINT_TACTICAL_COVER_MED) + { +#if EXPANDED_HL2_COVER_ACTIVITIES + nCoverActivity = ACT_RANGE_ATTACK1_MED; +#else + nCoverActivity = ACT_RANGE_ATTACK1_LOW; +#endif + } } } } @@ -6583,15 +6602,36 @@ Activity CAI_BaseNPC::NPC_BackupActivity( Activity eNewActivity ) //if (eNewActivity == ACT_DROP_WEAPON) // return TranslateActivity(ACT_IDLE); + // --------------------------------------------- + // Accounts for certain act busy activities that aren't on all NPCs. if (eNewActivity == ACT_BUSY_QUEUE || eNewActivity == ACT_BUSY_STAND) return TranslateActivity(ACT_IDLE); + // --------------------------------------------- + if (eNewActivity == ACT_WALK_ANGRY) return TranslateActivity(ACT_WALK); - // GetCoverActivity() should have this covered. // --------------------------------------------- + + // If one climbing animation isn't available, use the other + if (eNewActivity == ACT_CLIMB_DOWN) + return ACT_CLIMB_UP; + else if (eNewActivity == ACT_CLIMB_UP) + return ACT_CLIMB_DOWN; + + // --------------------------------------------- + + switch (eNewActivity) + { + case ACT_COVER_MED: eNewActivity = ACT_COVER_LOW; break; +#if EXPANDED_HL2_COVER_ACTIVITIES + case ACT_RANGE_AIM_MED: eNewActivity = ACT_RANGE_AIM_LOW; break; + case ACT_RANGE_ATTACK1_MED: eNewActivity = ACT_RANGE_ATTACK1_LOW; break; +#endif + } + //if (eNewActivity == ACT_COVER) // return TranslateActivity(ACT_IDLE); @@ -6606,6 +6646,14 @@ Activity CAI_BaseNPC::NPC_BackupActivity( Activity eNewActivity ) //----------------------------------------------------------------------------- Activity CAI_BaseNPC::NPC_TranslateActivity( Activity eNewActivity ) { +#if EXPANDED_NAVIGATION_ACTIVITIES + if ( GetNavType() == NAV_CLIMB && eNewActivity == ACT_IDLE ) + { + // Schedules which break into idle activities should try to maintain the climbing animation. + return ACT_CLIMB_IDLE; + } +#endif + #ifdef MAPBASE Assert( eNewActivity != ACT_INVALID ); @@ -6615,15 +6663,7 @@ Activity CAI_BaseNPC::NPC_TranslateActivity( Activity eNewActivity ) { case ACT_RANGE_ATTACK1: eNewActivity = ACT_RANGE_ATTACK1_LOW; break; case ACT_RELOAD: eNewActivity = ACT_RELOAD_LOW; break; - case ACT_IDLE: eNewActivity = ACT_CROUCHIDLE; break; - - // ==== - // HACK : LEIPZIG 06 - The underlying problem is that the AR2 and SMG1 cannot map IDLE_ANGRY to a crouched equivalent automatically - // which causes the character to pop up and down in their idle state of firing while crouched. -- jdw - case ACT_IDLE_ANGRY_SMG1: - case ACT_IDLE_ANGRY_AR2: - eNewActivity = ACT_RANGE_AIM_LOW; - break; + case ACT_IDLE: eNewActivity = ACT_RANGE_AIM_LOW; break; // ACT_CROUCHIDLE is more-or-less deprecated and not friendly to weapon translation } } @@ -6836,7 +6876,12 @@ void CAI_BaseNPC::ResolveActivityToSequence(Activity NewActivity, int &iSequence translatedActivity = TranslateActivity( NewActivity, &weaponActivity ); +#ifdef MAPBASE + // Cover cases where TranslateActivity() returns a sequence by using ACT_SCRIPT_CUSTOM_MOVE + if ( NewActivity == ACT_SCRIPT_CUSTOM_MOVE || translatedActivity == ACT_SCRIPT_CUSTOM_MOVE ) +#else if ( NewActivity == ACT_SCRIPT_CUSTOM_MOVE ) +#endif { iSequence = GetScriptCustomMoveSequence(); } @@ -7022,6 +7067,23 @@ void CAI_BaseNPC::SetIdealActivity( Activity NewActivity ) // Perform translation in case we need to change sequences within a single activity, // such as between a standing idle and a crouching idle. ResolveActivityToSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity); + +#ifdef MAPBASE + // Check if we need a gesture to imitate this sequence + if ( ShouldPlayFakeSequenceGesture( m_IdealActivity, m_IdealTranslatedActivity ) ) + { + Activity nGesture = SelectFakeSequenceGesture( m_IdealActivity, m_IdealTranslatedActivity ); + if (nGesture != -1) + { + PlayFakeSequenceGesture( nGesture, m_IdealActivity, m_IdealTranslatedActivity ); + } + } + else if (GetFakeSequenceGesture() != -1) + { + // Reset the current gesture sequence if there is one + ResetFakeSequenceGesture(); + } +#endif } @@ -7070,6 +7132,14 @@ void CAI_BaseNPC::AdvanceToIdealActivity(void) //DevMsg("%s: Unable to get from sequence %s to %s!\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(m_nIdealSequence)); SetActivity(m_IdealActivity); } + +#ifdef MAPBASE + // If there was a gesture imitating a sequence, get rid of it + if ( GetFakeSequenceGesture() != -1 ) + { + ResetFakeSequenceGesture(); + } +#endif } @@ -7127,6 +7197,12 @@ void CAI_BaseNPC::MaintainActivity(void) } // Else a transition sequence is in progress, do nothing. } +#ifdef MAPBASE + else if (GetFakeSequenceGesture() != -1) + { + // Don't advance even if the sequence gesture is finished, as advancing would just play the original activity afterwards + } +#endif // Else get a specific sequence for the activity and try to transition to that. else { @@ -7145,11 +7221,103 @@ void CAI_BaseNPC::MaintainActivity(void) } +#ifdef MAPBASE +bool CAI_BaseNPC::ShouldPlayFakeSequenceGesture( Activity nActivity, Activity nTranslatedActivity ) +{ + // Don't do anything if we're resetting our activity + if (GetActivity() == ACT_RESET) + return false; + + // No need to do this while we're moving or for sequences which will make us move + if (IsMoving()) + return false; + + if (ai_debug_fake_sequence_gestures_always_play.GetBool()) + return true; + +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_ShouldPlayFakeSequenceGesture.CanRunInScope(m_ScriptScope)) + { + // activity, translatedActivity + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { GetActivityName( nActivity ), GetActivityName( nTranslatedActivity ) }; + if (g_Hook_ShouldPlayFakeSequenceGesture.Call( m_ScriptScope, &functionReturn, args )) + { + if (functionReturn.m_type == FIELD_BOOLEAN) + return functionReturn.m_bool; + } + } +#endif + + if (GetHintNode() && GetHintNode()->HintActivityName() != NULL_STRING) + { + switch (GetHintNode()->HintType()) + { + // Cover nodes with custom activities should allow NPCs to do things like reload while in cover. + case HINT_TACTICAL_COVER_LOW: + case HINT_TACTICAL_COVER_MED: + case HINT_TACTICAL_COVER_CUSTOM: + if (HasMemory( bits_MEMORY_INCOVER )) + { + // Don't attack while using a custom animation in cover + if (nActivity != ACT_RANGE_ATTACK1 && nActivity != ACT_RANGE_ATTACK1_LOW) + return true; + } + break; + } + } + + return false; +} + +Activity CAI_BaseNPC::SelectFakeSequenceGesture( Activity nActivity, Activity nTranslatedActivity ) +{ + return GetGestureVersionOfActivity( nTranslatedActivity ); +} + +inline void CAI_BaseNPC::PlayFakeSequenceGesture( Activity nActivity, Activity nSequence, Activity nTranslatedSequence ) +{ + RestartGesture( nActivity, true, true ); + m_FakeSequenceGestureLayer = FindGestureLayer( nActivity ); + + switch ( nSequence ) + { + case ACT_RANGE_ATTACK1: + //case ACT_RANGE_ATTACK2: + { + OnRangeAttack1(); + + // FIXME: this seems a bit wacked + Weapon_SetActivity( Weapon_TranslateActivity( nSequence ), 0 ); + } break; + } +} + +inline int CAI_BaseNPC::GetFakeSequenceGesture() +{ + return m_FakeSequenceGestureLayer; +} + +void CAI_BaseNPC::ResetFakeSequenceGesture() +{ + SetLayerCycle( m_FakeSequenceGestureLayer, 1.0f ); + m_FakeSequenceGestureLayer = -1; +} +#endif + + //----------------------------------------------------------------------------- // Purpose: Returns true if our ideal activity has finished playing. //----------------------------------------------------------------------------- bool CAI_BaseNPC::IsActivityFinished( void ) { +#ifdef MAPBASE + if (GetFakeSequenceGesture() != -1) + { + return IsLayerFinished( GetFakeSequenceGesture() ); + } +#endif + return (IsSequenceFinished() && (GetSequence() == m_nIdealSequence)); } @@ -7185,6 +7353,15 @@ void CAI_BaseNPC::OnChangeActivity( Activity eNewActivity ) eNewActivity == ACT_WALK ) { Stand(); + +#ifdef MAPBASE + // Unlock custom cover nodes + if (GetHintNode() && GetHintNode()->HintType() == HINT_TACTICAL_COVER_CUSTOM && HasMemory(bits_MEMORY_INCOVER)) + { + GetHintNode()->Unlock( GetHintDelay( GetHintNode()->HintType() ) ); + SetHintNode( NULL ); + } +#endif } } @@ -7461,7 +7638,11 @@ void CAI_BaseNPC::SetHullSizeNormal( bool force ) if ( m_fIsUsingSmallHull || force ) { // Find out what the height difference will be between the versions and adjust our bbox accordingly to keep us level +#ifdef MAPBASE // From Alien Swarm SDK + const float flScale = MIN( 1.0f, GetModelScale() ); // NOTE: Cannot scale NPC bounding box up, as pathfinding will fail (hull needs to match the traces used for the node network) +#else const float flScale = GetModelScale(); +#endif Vector vecMins = ( GetHullMins() * flScale ); Vector vecMaxs = ( GetHullMaxs() * flScale ); @@ -7769,7 +7950,7 @@ int CAI_BaseNPC::HolsterWeapon( void ) if ( IsWeaponHolstered() ) return -1; -#ifdef COMPANION_HOLSTER_WORKAROUND +#ifdef MAPBASE Activity activity = TranslateActivity( ACT_DISARM ); int iHolsterGesture = FindGestureLayer( activity ); if ( iHolsterGesture != -1 ) @@ -7825,7 +8006,7 @@ int CAI_BaseNPC::UnholsterWeapon( void ) if ( !IsWeaponHolstered() ) return -1; -#ifdef COMPANION_HOLSTER_WORKAROUND +#ifdef MAPBASE Activity activity = TranslateActivity( ACT_ARM ); int iHolsterGesture = FindGestureLayer( activity ); #else @@ -7854,13 +8035,12 @@ int CAI_BaseNPC::UnholsterWeapon( void ) { SetActiveWeapon( GetWeapon(i) ); -#ifdef COMPANION_HOLSTER_WORKAROUND - int iLayer = AddGesture( activity, true ); - //iLayer = AddGesture( ACT_GESTURE_ARM, true ); +#ifdef MAPBASE + int iLayer = AddGesture( TranslateActivity( ACT_ARM ), true ); #else int iLayer = AddGesture( ACT_ARM, true ); - //iLayer = AddGesture( ACT_GESTURE_ARM, true ); #endif + //iLayer = AddGesture( ACT_GESTURE_ARM, true ); if (iLayer != -1) { @@ -8039,6 +8219,26 @@ bool CAI_BaseNPC::DoUnholster( void ) return false; } +//----------------------------------------------------------------------------- +// Purpose: Returns true if the NPC should be unholstering their weapon +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::ShouldUnholsterWeapon( void ) +{ + return GetState() == NPC_STATE_COMBAT; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if the NPC can unholster their weapon +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::CanUnholsterWeapon( void ) +{ + // Don't unholster during special navigation + if ( GetNavType() == NAV_JUMP || GetNavType() == NAV_CLIMB ) + return false; + + return IsWeaponHolstered(); +} + //------------------------------------------------------------------------------ // Purpose: Give the NPC in question the weapon specified //------------------------------------------------------------------------------ @@ -9105,27 +9305,45 @@ Activity CAI_BaseNPC::GetCoverActivity( CAI_Hint *pHint ) switch (pHint->HintType()) { case HINT_TACTICAL_COVER_MED: -#ifndef MAPBASE // I know what you're thinking, but ACT_COVER_MED is pretty much deprecated at this point anyway. { - nCoverActivity = ACT_COVER_MED; #ifdef MAPBASE - // Some NPCs lack ACT_COVER_MED, but could easily use ACT_COVER_LOW. - if (SelectWeightedSequence(nCoverActivity) == ACTIVITY_NOT_AVAILABLE) - nCoverActivity = ACT_COVER_LOW; -#endif + // NPCs which lack ACT_COVER_MED should fall through to HINT_TACTICAL_COVER_LOW + if (SelectWeightedSequence( ACT_COVER_MED ) != ACTIVITY_NOT_AVAILABLE) + { + nCoverActivity = ACT_COVER_MED; + } +#else + nCoverActivity = ACT_COVER_MED; break; - } #endif + } case HINT_TACTICAL_COVER_LOW: { #ifdef MAPBASE + // Make sure nCoverActivity wasn't already assigned above, then fall through to HINT_TACTICAL_COVER_CUSTOM + if (nCoverActivity == ACT_INVALID) + nCoverActivity = ACT_COVER_LOW; +#else + nCoverActivity = ACT_COVER_LOW; + break; +#endif + } + +#ifdef MAPBASE + case HINT_TACTICAL_COVER_CUSTOM: + { if (pHint->HintActivityName() != NULL_STRING) + { nCoverActivity = (Activity)CAI_BaseNPC::GetActivityID( STRING(pHint->HintActivityName()) ); - else -#endif - nCoverActivity = ACT_COVER_LOW; + if (nCoverActivity == ACT_INVALID) + { + m_iszSceneCustomMoveSeq = pHint->HintActivityName(); + nCoverActivity = ACT_SCRIPT_CUSTOM_MOVE; + } + } break; } +#endif } } @@ -9163,6 +9381,13 @@ float CAI_BaseNPC::CalcIdealYaw( const Vector &vecTarget ) return UTIL_VecToYaw( vecProjection - GetLocalOrigin() ); } +#ifdef MAPBASE + // Allow hint nodes to override the yaw without needing to control AI + else if (GetHintNode() && GetHintNode()->OverridesNPCYaw( this )) + { + return GetHintNode()->Yaw(); + } +#endif else { return UTIL_VecToYaw ( vecTarget - GetLocalOrigin() ); @@ -9503,7 +9728,7 @@ void CAI_BaseNPC::HandleAnimEvent( animevent_t *pEvent ) if ( GetActiveWeapon() ) { #ifdef MAPBASE - GetActiveWeapon()->Reload_NPC(); + GetActiveWeapon()->Reload_NPC( true ); #else GetActiveWeapon()->WeaponSound( RELOAD_NPC ); GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1(); @@ -9527,8 +9752,12 @@ void CAI_BaseNPC::HandleAnimEvent( animevent_t *pEvent ) case EVENT_WEAPON_RELOAD_FILL_CLIP: { if ( GetActiveWeapon() ) - { + { +#ifdef MAPBASE + GetActiveWeapon()->Reload_NPC( false ); +#else GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1(); +#endif ClearCondition(COND_LOW_PRIMARY_AMMO); ClearCondition(COND_NO_PRIMARY_AMMO); ClearCondition(COND_NO_SECONDARY_AMMO); @@ -9543,7 +9772,11 @@ void CAI_BaseNPC::HandleAnimEvent( animevent_t *pEvent ) case NPC_EVENT_OPEN_DOOR: { +#ifdef MAPBASE + CBasePropDoor *pDoor = m_hOpeningDoor; +#else CBasePropDoor *pDoor = (CBasePropDoor *)(CBaseEntity *)GetNavigator()->GetPath()->GetCurWaypoint()->GetEHandleData(); +#endif if (pDoor != NULL) { OpenPropDoorNow( pDoor ); @@ -10691,6 +10924,7 @@ Vector CAI_BaseNPC::GetActualShootPosition( const Vector &shootOrigin ) #ifdef MAPBASE_VSCRIPT if (m_ScriptScope.IsInitialized() && g_Hook_GetActualShootPosition.CanRunInScope(m_ScriptScope)) { + // shootOrigin, target ScriptVariant_t functionReturn; ScriptVariant_t args[] = { shootOrigin, ToHScript( GetEnemy() ) }; if (g_Hook_GetActualShootPosition.Call( m_ScriptScope, &functionReturn, args )) @@ -11490,6 +11724,14 @@ bool CAI_BaseNPC::ChooseEnemy( void ) //========================================================= void CAI_BaseNPC::PickupWeapon( CBaseCombatWeapon *pWeapon ) { +#ifdef MAPBASE + if ( pWeapon->VPhysicsGetObject() && pWeapon->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) + { + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + pPlayer->ForceDropOfCarriedPhysObjects( pWeapon ); + } +#endif + pWeapon->OnPickedUp( this ); Weapon_Equip( pWeapon ); m_iszPendingWeapon = NULL_STRING; @@ -11509,17 +11751,9 @@ void CAI_BaseNPC::PickupItem( CBaseEntity *pItem ) m_OnItemPickup.Set( pItem, pItem, this ); Assert( pItem != NULL ); - if( FClassnameIs( pItem, "item_healthkit" ) ) - { - if ( TakeHealth( sk_healthkit.GetFloat(), DMG_GENERIC ) ) - { - RemoveAllDecals(); - UTIL_Remove( pItem ); - } - } - else if( FClassnameIs( pItem, "item_healthvial" ) ) + if( FClassnameIs( pItem, "item_health*" ) ) // item_healthkit, item_healthvial, item_healthkit_custom, etc. { - if ( TakeHealth( sk_healthvial.GetFloat(), DMG_GENERIC ) ) + if ( TakeHealth( static_cast(pItem)->GetItemAmount(), DMG_GENERIC ) ) { RemoveAllDecals(); UTIL_Remove( pItem ); @@ -11579,6 +11813,10 @@ CBaseEntity *CAI_BaseNPC::DropItem ( const char *pszItemName, Vector vecPos, QAn pItem->ApplyLocalAngularVelocityImpulse( AngularImpulse( 0, random->RandomFloat( 0, 100 ), 0 ) ); } +#ifdef MAPBASE + m_OnItemDrop.Set( pItem, pItem, this ); +#endif + return pItem; } else @@ -11901,6 +12139,7 @@ BEGIN_DATADESC( CAI_BaseNPC ) DEFINE_KEYFIELD( m_FriendlyFireOverride, FIELD_INTEGER, "FriendlyFireOverride" ), DEFINE_KEYFIELD( m_flSpeedModifier, FIELD_FLOAT, "BaseSpeedModifier" ), + DEFINE_FIELD( m_FakeSequenceGestureLayer, FIELD_INTEGER ), #endif // Satisfy classcheck @@ -11946,6 +12185,7 @@ BEGIN_DATADESC( CAI_BaseNPC ) DEFINE_OUTPUT( m_OnForcedInteractionFinished, "OnForcedInteractionFinished" ), #ifdef MAPBASE DEFINE_OUTPUT( m_OnItemPickup, "OnItemPickup" ), + DEFINE_OUTPUT( m_OnItemDrop, "OnItemDrop" ), #endif // Inputs @@ -12057,6 +12297,14 @@ BEGIN_ENT_SCRIPTDESC( CAI_BaseNPC, CBaseCombatCharacter, "The base class all NPC DEFINE_SCRIPTFUNC_NAMED( ScriptSetActivityID, "SetActivityID", "Set the NPC's current activity ID." ) DEFINE_SCRIPTFUNC( ResetActivity, "Reset the NPC's current activity." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptTranslateActivity, "TranslateActivity", "Translates the specified activity string and returns the translated activity ID." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptTranslateActivityID, "TranslateActivityID", "Translates the specified activity ID and returns the translated activity ID." ) + + DEFINE_SCRIPTFUNC_NAMED( VScriptGetGestureVersionOfActivity, "GetGestureVersionOfActivity", "Get the gesture activity counterpart of the specified sequence activity, if one exists." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptGetGestureVersionOfActivityID, "GetGestureVersionOfActivityID", "Get the gesture activity ID counterpart of the specified sequence activity ID, if one exists." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptGetSequenceVersionOfGesture, "GetSequenceVersionOfGesture", "Get the sequence activity counterpart of the specified gesture activity, if one exists." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptGetSequenceVersionOfGestureID, "GetSequenceVersionOfGestureID", "Get the sequence activity ID counterpart of the specified gesture activity ID, if one exists." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptGetSchedule, "GetSchedule", "Get the NPC's current schedule." ) DEFINE_SCRIPTFUNC_NAMED( VScriptGetScheduleID, "GetScheduleID", "Get the NPC's current schedule ID." ) DEFINE_SCRIPTFUNC_NAMED( VScriptSetSchedule, "SetSchedule", "Set the NPC's current schedule." ) @@ -12085,6 +12333,10 @@ BEGIN_ENT_SCRIPTDESC( CAI_BaseNPC, CBaseCombatCharacter, "The base class all NPC DEFINE_SCRIPTFUNC_NAMED( VScriptGetSquad, "GetSquad", "Get the NPC's squad if it has one." ) DEFINE_SCRIPTFUNC( IsInSquad, "Returns true if the NPC is in a squad." ) DEFINE_SCRIPTFUNC( NumWeaponsInSquad, "Get the number of weapons in a squad." ) + + DEFINE_SCRIPTFUNC( IsCrouching, "Returns true if the NPC is crouching." ) + DEFINE_SCRIPTFUNC( Crouch, "Tells the NPC to crouch." ) + DEFINE_SCRIPTFUNC( Stand, "Tells the NPC to stand if it is crouching." ) // // Hooks @@ -12103,10 +12355,17 @@ BEGIN_ENT_SCRIPTDESC( CAI_BaseNPC, CBaseCombatCharacter, "The base class all NPC DEFINE_SCRIPTHOOK_PARAM( "schedule", FIELD_CSTRING ) DEFINE_SCRIPTHOOK_PARAM( "schedule_id", FIELD_INTEGER ) END_SCRIPTHOOK() - BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_GetActualShootPosition, "GetActualShootPosition", FIELD_VOID, "Called when the NPC is getting their actual shoot position, using the default shoot position as the parameter. (NOTE: NPCs which override this themselves might not always use this hook!)" ) + BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_GetActualShootPosition, "GetActualShootPosition", FIELD_VECTOR, "Called when the NPC is getting their actual shoot position, using the default shoot position as the parameter. (NOTE: NPCs which override this themselves might not always use this hook!)" ) DEFINE_SCRIPTHOOK_PARAM( "shootOrigin", FIELD_VECTOR ) DEFINE_SCRIPTHOOK_PARAM( "target", FIELD_HSCRIPT ) END_SCRIPTHOOK() + BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_OverrideMove, "OverrideMove", FIELD_VOID, "Called when the NPC runs movement code, allowing the NPC's movement to be overridden by some other method. (NOTE: NPCs which override this themselves might not always use this hook!)" ) + DEFINE_SCRIPTHOOK_PARAM( "interval", FIELD_FLOAT ) + END_SCRIPTHOOK() + BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_ShouldPlayFakeSequenceGesture, "ShouldPlayFakeSequenceGesture", FIELD_BOOLEAN, "Called when an activity is set on a NPC. Returning true will make the NPC convert the activity into a gesture (if a gesture is available) and continue their current activity instead." ) + DEFINE_SCRIPTHOOK_PARAM( "activity", FIELD_CSTRING ) + DEFINE_SCRIPTHOOK_PARAM( "translatedActivity", FIELD_CSTRING ) + END_SCRIPTHOOK() END_SCRIPTDESC(); #endif @@ -12757,6 +13016,8 @@ CAI_BaseNPC::CAI_BaseNPC(void) #ifdef MAPBASE m_iDynamicInteractionsAllowed = TRS_NONE; m_flSpeedModifier = 1.0f; + + m_FakeSequenceGestureLayer = -1; #endif } @@ -13413,6 +13674,10 @@ void CAI_BaseNPC::Teleport( const Vector *newPosition, const QAngle *newAngles, CleanupScriptsOnTeleport( false ); BaseClass::Teleport( newPosition, newAngles, newVelocity ); + +#ifdef MAPBASE // From Alien Swarm SDK + CheckPVSCondition(); +#endif } //----------------------------------------------------------------------------- @@ -13536,6 +13801,20 @@ bool CAI_BaseNPC::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInt bool CAI_BaseNPC::OverrideMove( float flInterval ) { +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_OverrideMove.CanRunInScope(m_ScriptScope)) + { + // interval + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { flInterval }; + if (g_Hook_OverrideMove.Call( m_ScriptScope, &functionReturn, args )) + { + if (functionReturn.m_type == FIELD_BOOLEAN) + return functionReturn.m_bool; + } + } +#endif + return false; } @@ -13707,6 +13986,10 @@ bool CAI_BaseNPC::OnUpcomingPropDoor( AILocalMoveGoal_t *pMoveGoal, GetNavigator()->GetPath()->PrependWaypoints( pOpenDoorRoute ); +#ifdef MAPBASE + GetNavigator()->SetArrivalDirection( opendata.vecFaceDir ); +#endif + m_hOpeningDoor = pDoor; pMoveGoal->maxDist = distClear; *pResult = AIMR_CHANGE_TYPE; @@ -13727,6 +14010,21 @@ bool CAI_BaseNPC::OnUpcomingPropDoor( AILocalMoveGoal_t *pMoveGoal, //----------------------------------------------------------------------------- void CAI_BaseNPC::OpenPropDoorBegin( CBasePropDoor *pDoor ) { +#ifdef MAPBASE + opendata_t opendata; + pDoor->GetNPCOpenData(this, opendata); + + if (HaveSequenceForActivity( opendata.eActivity )) + { + int iLayer = AddGesture( opendata.eActivity ); + float flDuration = GetLayerDuration( iLayer ); + + // Face the door and wait for the activity to finish before trying to move through the doorway. + m_flMoveWaitFinished = gpGlobals->curtime + flDuration + pDoor->GetOpenInterval(); + AddFacingTarget( opendata.vecFaceDir, 1.0, flDuration ); + } + else +#else // dvs: not quite working, disabled for now. //opendata_t opendata; //pDoor->GetNPCOpenData(this, opendata); @@ -13736,6 +14034,7 @@ void CAI_BaseNPC::OpenPropDoorBegin( CBasePropDoor *pDoor ) // SetIdealActivity(opendata.eActivity); //} //else +#endif { // We don't have an appropriate sequence, just open the door magically. OpenPropDoorNow( pDoor ); @@ -13755,6 +14054,15 @@ void CAI_BaseNPC::OpenPropDoorNow( CBasePropDoor *pDoor ) // Wait for the door to finish opening before trying to move through the doorway. m_flMoveWaitFinished = gpGlobals->curtime + pDoor->GetOpenInterval(); + +#ifdef MAPBASE + // Remove the door from our waypoint + if (GetNavigator()->GetPath() && GetNavigator()->GetCurWaypointFlags() & bits_WP_TO_DOOR) + { + GetNavigator()->GetPath()->GetCurWaypoint()->ModifyFlags( bits_WP_TO_DOOR, false ); + GetNavigator()->GetPath()->GetCurWaypoint()->m_hData = NULL; + } +#endif } @@ -15251,7 +15559,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) @@ -15940,6 +16252,26 @@ bool CAI_BaseNPC::CouldShootIfCrouching( CBaseEntity *pTarget ) return bResult; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Check if this position will block our line of sight if aiming low. +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::CouldShootIfCrouchingAt( const Vector &vecPosition, const Vector &vecForward, const Vector &vecRight, float flDist ) +{ + Vector vGunPos = vecPosition; + vGunPos += (GetCrouchGunOffset() + vecRight * 8); + + trace_t tr; + AI_TraceLOS( vGunPos, vGunPos + (vecForward * flDist), this, &tr ); + if (tr.fraction != 1.0) + { + return false; + } + + return true; +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -15955,9 +16287,16 @@ bool CAI_BaseNPC::IsCrouchedActivity( Activity activity ) case ACT_COVER_SMG1_LOW: case ACT_RELOAD_SMG1_LOW: #ifdef MAPBASE - //case ACT_RELOAD_AR2_LOW: +#if AR2_ACTIVITY_FIX == 1 + case ACT_COVER_AR2_LOW: + case ACT_RELOAD_AR2_LOW: +#endif case ACT_RELOAD_PISTOL_LOW: case ACT_RELOAD_SHOTGUN_LOW: +#if EXPANDED_HL2_WEAPON_ACTIVITIES + case ACT_RELOAD_REVOLVER_LOW: + case ACT_RELOAD_CROSSBOW_LOW: +#endif #endif return true; } diff --git a/sp/src/game/server/ai_basenpc.h b/sp/src/game/server/ai_basenpc.h index 2a2ad03c65..878a6f817f 100644 --- a/sp/src/game/server/ai_basenpc.h +++ b/sp/src/game/server/ai_basenpc.h @@ -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 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 @@ class CAI_BaseNPC : public CBaseCombatCharacter, 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 @@ class CAI_BaseNPC : public CBaseCombatCharacter, 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 @@ -1000,12 +998,17 @@ class CAI_BaseNPC : public CBaseCombatCharacter, Activity NPC_TranslateActivity( Activity eNewActivity ); #ifdef MAPBASE Activity TranslateCrouchActivity( Activity baseAct ); + virtual bool CanTranslateCrouchActivity( void ) { return true; } virtual Activity NPC_BackupActivity( Activity eNewActivity ); #endif Activity GetActivity( void ) { return m_Activity; } virtual void SetActivity( Activity NewActivity ); Activity GetIdealActivity( void ) { return m_IdealActivity; } void SetIdealActivity( Activity NewActivity ); +#ifdef MAPBASE + Activity GetTranslatedActivity( void ) { return m_translatedActivity; } + Activity GetIdealTranslatedActivity( void ) { return m_IdealTranslatedActivity; } +#endif void ResetIdealActivity( Activity newIdealActivity ); void SetSequenceByName( const char *szSequence ); void SetSequenceById( int iSequence ); @@ -1022,6 +1025,25 @@ class CAI_BaseNPC : public CBaseCombatCharacter, void SetActivityAndSequence(Activity NewActivity, int iSequence, Activity translatedActivity, Activity weaponActivity); +#ifdef MAPBASE + //----------------------------------------------------- + + // Returns the gesture variant of an activity (i.e. "ACT_GESTURE_RANGE_ATTACK1") + static Activity GetGestureVersionOfActivity( Activity inActivity ); + + // Returns the sequence variant of a gesture activity + static Activity GetSequenceVersionOfGesture( Activity inActivity ); + + //----------------------------------------------------- + + virtual bool ShouldPlayFakeSequenceGesture( Activity nActivity, Activity nTranslatedActivity ); + virtual Activity SelectFakeSequenceGesture( Activity nActivity, Activity nTranslatedActivity ); + void PlayFakeSequenceGesture( Activity nActivity, Activity nSequence, Activity nTranslatedSequence ); + + int GetFakeSequenceGesture(); + void ResetFakeSequenceGesture(); +#endif + private: void AdvanceToIdealActivity(void); @@ -1035,6 +1057,10 @@ class CAI_BaseNPC : public CBaseCombatCharacter, Activity m_IdealTranslatedActivity; // Desired actual translated animation state Activity m_IdealWeaponActivity; // Desired weapon animation state +#ifdef MAPBASE + int m_FakeSequenceGestureLayer; // The gesture layer impersonating a sequence (-1 if invalid) +#endif + CNetworkVar(int, m_iDeathPose ); CNetworkVar(int, m_iDeathFrame ); @@ -1219,6 +1245,8 @@ class CAI_BaseNPC : public CBaseCombatCharacter, #endif #ifdef MAPBASE_VSCRIPT +private: + // VScript stuff uses "VScript" instead of just "Script" to avoid // confusion with NPC_STATE_SCRIPT or StartScripting HSCRIPT VScriptGetEnemy(); @@ -1245,6 +1273,11 @@ class CAI_BaseNPC : public CBaseCombatCharacter, int ScriptTranslateActivity( const char *szActivity ) { return TranslateActivity( (Activity)GetActivityID( szActivity ) ); } int ScriptTranslateActivityID( int iActivity ) { return TranslateActivity( (Activity)iActivity ); } + const char* VScriptGetGestureVersionOfActivity( const char *pszActivity ) { return GetActivityName( GetGestureVersionOfActivity( (Activity)GetActivityID( pszActivity ) ) ); } + int VScriptGetGestureVersionOfActivityID( int iActivity ) { return GetGestureVersionOfActivity( (Activity)iActivity ); } + const char* VScriptGetSequenceVersionOfGesture( const char *pszActivity ) { return GetActivityName( GetSequenceVersionOfGesture( (Activity)GetActivityID( pszActivity ) ) ); } + int VScriptGetSequenceVersionOfGestureID( int iActivity ) { return GetSequenceVersionOfGesture( (Activity)iActivity ); } + const char* VScriptGetSchedule(); int VScriptGetScheduleID(); void VScriptSetSchedule( const char *szSchedule ); @@ -1733,8 +1766,8 @@ class CAI_BaseNPC : public CBaseCombatCharacter, virtual bool DoHolster(void); virtual bool DoUnholster(void); - virtual bool ShouldUnholsterWeapon() { return GetState() == NPC_STATE_COMBAT; } - virtual bool CanUnholsterWeapon() { return IsWeaponHolstered(); } + virtual bool ShouldUnholsterWeapon(); + virtual bool CanUnholsterWeapon(); void InputGiveWeaponHolstered( inputdata_t &inputdata ); void InputChangeWeapon( inputdata_t &inputdata ); @@ -2121,6 +2154,7 @@ class CAI_BaseNPC : public CBaseCombatCharacter, COutputEHANDLE m_OnUnholsterWeapon; COutputEHANDLE m_OnItemPickup; + COutputEHANDLE m_OnItemDrop; COutputInt m_OnStateChange; #endif @@ -2208,6 +2242,10 @@ class CAI_BaseNPC : public CBaseCombatCharacter, inline void ForceCrouch( void ); inline void ClearForceCrouch( void ); +#ifdef MAPBASE + bool CouldShootIfCrouchingAt( const Vector &vecPosition, const Vector &vecForward, const Vector &vecRight, float flDist = 48.0f ); +#endif + protected: virtual bool Crouch( void ); virtual bool Stand( void ); @@ -2267,6 +2305,16 @@ class CAI_BaseNPC : public CBaseCombatCharacter, static CAI_GlobalScheduleNamespace gm_SchedulingSymbols; static CAI_ClassScheduleIdSpace gm_ClassScheduleIdSpace; +#ifdef MAPBASE + typedef struct + { + Activity sequence; + Activity gesture; + } actlink_t; + + static actlink_t gm_ActivityGestureLinks[]; +#endif + public: //---------------------------------------------------- // Debugging tools @@ -2318,6 +2366,7 @@ class CAI_BaseNPC : public CBaseCombatCharacter, static ScriptHook_t g_Hook_TranslateSchedule; static ScriptHook_t g_Hook_GetActualShootPosition; static ScriptHook_t g_Hook_OverrideMove; + static ScriptHook_t g_Hook_ShouldPlayFakeSequenceGesture; #endif private: diff --git a/sp/src/game/server/ai_basenpc_schedule.cpp b/sp/src/game/server/ai_basenpc_schedule.cpp index 10bf02e549..61fefb3ff0 100644 --- a/sp/src/game/server/ai_basenpc_schedule.cpp +++ b/sp/src/game/server/ai_basenpc_schedule.cpp @@ -980,11 +980,12 @@ bool CAI_BaseNPC::FindCoverFromEnemy( bool bNodesOnly, float flMinDistance, floa // FIXME: add to goal if (GetHintNode()) { - GetNavigator()->SetArrivalActivity( GetCoverActivity( GetHintNode() ) ); #ifdef MAPBASE - if (GetHintNode()->GetIgnoreFacing() != HIF_NO) -#endif + GetHintNode()->NPCHandleStartNav( this, true ); +#else + GetNavigator()->SetArrivalActivity( GetCoverActivity( GetHintNode() ) ); GetNavigator()->SetArrivalDirection( GetHintNode()->GetDirection() ); +#endif } return true; @@ -1363,6 +1364,14 @@ void CAI_BaseNPC::StartTask( const Task_t *pTask ) break; case TASK_STOP_MOVING: +#ifdef MAPBASE + if ( GetNavType() == NAV_CLIMB ) + { + // Don't clear the goal so that the climb can finish + DbgNavMsg( this, "Start TASK_STOP_MOVING with climb workaround\n" ); + } + else +#endif if ( ( GetNavigator()->IsGoalSet() && GetNavigator()->IsGoalActive() ) || GetNavType() == NAV_JUMP ) { DbgNavMsg( this, "Start TASK_STOP_MOVING\n" ); @@ -3015,7 +3024,16 @@ void CAI_BaseNPC::StartTask( const Task_t *pTask ) case TASK_ITEM_PICKUP: { - SetIdealActivity( ACT_PICKUP_GROUND ); +#ifdef MAPBASE + if (GetTarget() && fabs( GetTarget()->WorldSpaceCenter().z - GetAbsOrigin().z ) >= 12.0f) + { + SetIdealActivity( ACT_PICKUP_RACK ); + } + else +#endif + { + SetIdealActivity( ACT_PICKUP_GROUND ); + } } break; @@ -3339,8 +3357,40 @@ void CAI_BaseNPC::RunTask( const Task_t *pTask ) // a navigation while in the middle of a climb if (GetNavType() == NAV_CLIMB) { +#ifdef MAPBASE + if (GetActivity() != ACT_CLIMB_DISMOUNT) + { + // Try to just pause the climb, but dismount if we're in SCHED_FAIL + if (IsCurSchedule( SCHED_FAIL, false )) + { + GetMotor()->MoveClimbStop(); + } + else + { + GetMotor()->MoveClimbPause(); + } + + TaskComplete(); + } + else if (IsActivityFinished()) + { + // Dismount complete. + GetMotor()->MoveClimbStop(); + + // Fix up our position if we have to + Vector vecTeleportOrigin; + if (GetMotor()->MoveClimbShouldTeleportToSequenceEnd( vecTeleportOrigin )) + { + SetLocalOrigin( vecTeleportOrigin ); + } + + TaskComplete(); + } + break; +#else // wait until you reach the end break; +#endif } DbgNavMsg( this, "TASK_STOP_MOVING Complete\n" ); @@ -3385,6 +3435,17 @@ void CAI_BaseNPC::RunTask( const Task_t *pTask ) // If the yaw is locked, this function will not act correctly Assert( GetMotor()->IsYawLocked() == false ); +#ifdef MAPBASE + if ( GetHintNode() && GetHintNode()->OverridesNPCYaw( this ) ) + { + // If the yaw is supposed to use that of a hint node, chain to TASK_FACE_HINTNODE + GetMotor()->SetIdealYaw( GetHintNode()->Yaw() ); + GetMotor()->SetIdealYaw( CalcReasonableFacing( true ) ); // CalcReasonableFacing() is based on previously set ideal yaw + ChainRunTask( TASK_FACE_HINTNODE, pTask->flTaskData ); + break; + } +#endif + Vector vecEnemyLKP = GetEnemyLKP(); if (!FInAimCone( vecEnemyLKP )) { @@ -3979,10 +4040,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 ); } @@ -4265,6 +4332,15 @@ void CAI_BaseNPC::SetTurnActivity ( void ) float flYD; flYD = GetMotor()->DeltaIdealYaw(); +#ifdef MAPBASE + // Allow AddTurnGesture() to decide this + if (GetMotor()->AddTurnGesture( flYD )) + { + SetIdealActivity( ACT_IDLE ); + Remember( bits_MEMORY_TURNING ); + return; + } +#else // FIXME: unknown case, update yaw should catch these /* if (GetMotor()->AddTurnGesture( flYD )) @@ -4274,6 +4350,7 @@ void CAI_BaseNPC::SetTurnActivity ( void ) return; } */ +#endif if( flYD <= -80 && flYD >= -100 && SelectWeightedSequence( ACT_90_RIGHT ) != ACTIVITY_NOT_AVAILABLE ) { diff --git a/sp/src/game/server/ai_behavior.cpp b/sp/src/game/server/ai_behavior.cpp index 8822f75075..4730e122f3 100644 --- a/sp/src/game/server/ai_behavior.cpp +++ b/sp/src/game/server/ai_behavior.cpp @@ -420,6 +420,24 @@ bool CAI_BehaviorBase::CanUnholsterWeapon( void ) return m_pBackBridge->BackBridge_CanUnholsterWeapon(); } + +//------------------------------------- + +bool CAI_BehaviorBase::ShouldPickADeathPose( void ) +{ + Assert( m_pBackBridge != NULL ); + + return m_pBackBridge->BackBridge_ShouldPickADeathPose(); +} + +//------------------------------------- + +bool CAI_BehaviorBase::CanTranslateCrouchActivity( void ) +{ + Assert( m_pBackBridge != NULL ); + + return m_pBackBridge->BackBridge_CanTranslateCrouchActivity(); +} #endif //------------------------------------- diff --git a/sp/src/game/server/ai_behavior.h b/sp/src/game/server/ai_behavior.h index ce26ca61ae..f4df0640a1 100644 --- a/sp/src/game/server/ai_behavior.h +++ b/sp/src/game/server/ai_behavior.h @@ -132,6 +132,8 @@ abstract_class CAI_BehaviorBase : public CAI_Component void BridgeHandleAnimEvent( animevent_t *pEvent ); #ifdef MAPBASE bool BridgeCanUnholsterWeapon( void ); + bool BridgeShouldPickADeathPose( void ); + bool BridgeCanTranslateCrouchActivity( void ); #endif virtual void GatherConditions(); @@ -220,6 +222,8 @@ abstract_class CAI_BehaviorBase : public CAI_Component virtual void HandleAnimEvent( animevent_t *pEvent ); #ifdef MAPBASE virtual bool CanUnholsterWeapon( void ); + virtual bool ShouldPickADeathPose( void ); + virtual bool CanTranslateCrouchActivity( void ); #endif virtual bool ShouldAlwaysThink(); @@ -370,6 +374,9 @@ abstract_class IBehaviorBackBridge #ifdef MAPBASE // For func_tank behavior virtual bool BackBridge_CanUnholsterWeapon( void ) = 0; + + virtual bool BackBridge_ShouldPickADeathPose( void ) = 0; + virtual bool BackBridge_CanTranslateCrouchActivity( void ) = 0; #endif //------------------------------------- @@ -470,6 +477,8 @@ class CAI_BehaviorHost : public BASE_NPC, void HandleAnimEvent( animevent_t *pEvent ); #ifdef MAPBASE bool CanUnholsterWeapon( void ); + bool ShouldPickADeathPose( void ); + bool CanTranslateCrouchActivity( void ); #endif bool ShouldAlwaysThink(); @@ -534,6 +543,9 @@ class CAI_BehaviorHost : public BASE_NPC, #ifdef MAPBASE // For func_tank behavior bool BackBridge_CanUnholsterWeapon( void ); + + bool BackBridge_ShouldPickADeathPose( void ); + bool BackBridge_CanTranslateCrouchActivity( void ); #endif CAI_BehaviorBase **AccessBehaviors(); @@ -913,6 +925,20 @@ inline bool CAI_BehaviorBase::BridgeCanUnholsterWeapon( void ) { return CanUnholsterWeapon(); } + +//----------------------------------------------------------------------------- + +inline bool CAI_BehaviorBase::BridgeShouldPickADeathPose( void ) +{ + return ShouldPickADeathPose(); +} + +//----------------------------------------------------------------------------- + +inline bool CAI_BehaviorBase::BridgeCanTranslateCrouchActivity( void ) +{ + return CanTranslateCrouchActivity(); +} #endif //----------------------------------------------------------------------------- @@ -1498,6 +1524,22 @@ inline bool CAI_BehaviorHost::BackBridge_CanUnholsterWeapon( void ) { return BaseClass::CanUnholsterWeapon(); } + +//------------------------------------- + +template +inline bool CAI_BehaviorHost::BackBridge_ShouldPickADeathPose( void ) +{ + return BaseClass::ShouldPickADeathPose(); +} + +//------------------------------------- + +template +inline bool CAI_BehaviorHost::BackBridge_CanTranslateCrouchActivity( void ) +{ + return BaseClass::CanTranslateCrouchActivity(); +} #endif //------------------------------------- @@ -1914,6 +1956,28 @@ inline bool CAI_BehaviorHost::CanUnholsterWeapon( void ) return BaseClass::CanUnholsterWeapon(); } + +//------------------------------------- + +template +inline bool CAI_BehaviorHost::ShouldPickADeathPose( void ) +{ + if (m_pCurBehavior) + return m_pCurBehavior->BridgeShouldPickADeathPose(); + + return BaseClass::ShouldPickADeathPose(); +} + +//------------------------------------- + +template +inline bool CAI_BehaviorHost::CanTranslateCrouchActivity( void ) +{ + if (m_pCurBehavior) + return m_pCurBehavior->BridgeCanTranslateCrouchActivity(); + + return BaseClass::CanTranslateCrouchActivity(); +} #endif //------------------------------------- diff --git a/sp/src/game/server/ai_behavior_follow.cpp b/sp/src/game/server/ai_behavior_follow.cpp index bcd254a555..a2ab0caeab 100644 --- a/sp/src/game/server/ai_behavior_follow.cpp +++ b/sp/src/game/server/ai_behavior_follow.cpp @@ -408,10 +408,11 @@ bool CAI_FollowBehavior::SetFollowGoal( CAI_FollowGoal *pGoal, bool fFinishCurSc SetFollowTarget( pGoal->GetGoalEntity() ); #ifdef MAPBASE Assert( pGoal->m_iFormation < AIF_NUM_FORMATIONS ); + SetParameters( AI_FollowParams_t( (AI_Formations_t)pGoal->m_iFormation, pGoal->m_bNormalMemoryDiscard ) ); #else Assert( pGoal->m_iFormation == AIF_SIMPLE || pGoal->m_iFormation == AIF_WIDE || pGoal->m_iFormation == AIF_MEDIUM || pGoal->m_iFormation == AIF_SIDEKICK || pGoal->m_iFormation == AIF_VORTIGAUNT ); -#endif SetParameters( AI_FollowParams_t( (AI_Formations_t)pGoal->m_iFormation ) ); +#endif m_hFollowGoalEnt = pGoal; m_flTimeUpdatedFollowPosition = 0; return true; @@ -2138,6 +2139,9 @@ bool CAI_FollowBehavior::ShouldAlwaysThink() BEGIN_DATADESC( CAI_FollowGoal ) DEFINE_KEYFIELD( m_iFormation, FIELD_INTEGER, "Formation" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bNormalMemoryDiscard, FIELD_BOOLEAN, "NormalMemoryDiscard" ), +#endif #ifdef HL2_EPISODIC DEFINE_INPUTFUNC( FIELD_VOID, "OutsideTransition", InputOutsideTransition ), diff --git a/sp/src/game/server/ai_behavior_follow.h b/sp/src/game/server/ai_behavior_follow.h index 84cb0109ac..097588c616 100644 --- a/sp/src/game/server/ai_behavior_follow.h +++ b/sp/src/game/server/ai_behavior_follow.h @@ -71,6 +71,10 @@ class CAI_FollowGoal : public CAI_GoalEntity int m_iFormation; +#ifdef MAPBASE + bool m_bNormalMemoryDiscard = false; +#endif + DECLARE_DATADESC(); }; diff --git a/sp/src/game/server/ai_behavior_lead.h b/sp/src/game/server/ai_behavior_lead.h index 2104b1f2ca..ef4bb0253a 100644 --- a/sp/src/game/server/ai_behavior_lead.h +++ b/sp/src/game/server/ai_behavior_lead.h @@ -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" diff --git a/sp/src/game/server/ai_behavior_standoff.cpp b/sp/src/game/server/ai_behavior_standoff.cpp index 0792df23cf..00e1565b53 100644 --- a/sp/src/game/server/ai_behavior_standoff.cpp +++ b/sp/src/game/server/ai_behavior_standoff.cpp @@ -240,6 +240,23 @@ void CAI_StandoffBehavior::SetActive( bool fActive ) { if ( fActive != m_fActive ) { +#ifdef MAPBASE + // These sentences are only spoken if the standoff behavior is active, so they have to be arranged separately + if ( fActive ) + { + m_fActive = fActive; + NotifyChangeBehaviorStatus(); + + GetOuter()->SpeakSentence( STANDOFF_SENTENCE_BEGIN_STANDOFF ); + } + else + { + GetOuter()->SpeakSentence( STANDOFF_SENTENCE_END_STANDOFF ); + + m_fActive = fActive; + NotifyChangeBehaviorStatus(); + } +#else if ( fActive ) { GetOuter()->SpeakSentence( STANDOFF_SENTENCE_BEGIN_STANDOFF ); @@ -251,6 +268,7 @@ void CAI_StandoffBehavior::SetActive( bool fActive ) m_fActive = fActive; NotifyChangeBehaviorStatus(); +#endif } } @@ -278,7 +296,12 @@ bool CAI_StandoffBehavior::CanSelectSchedule() if ( !m_fActive ) return false; +#ifdef MAPBASE + // Allow NPCs with innate range attacks to use standoffs + return ( GetNpcState() == NPC_STATE_COMBAT && (GetOuter()->GetActiveWeapon() != NULL || GetOuter()->CapabilitiesGet() & bits_CAP_INNATE_RANGE_ATTACK1) ); +#else return ( GetNpcState() == NPC_STATE_COMBAT && GetOuter()->GetActiveWeapon() != NULL ); +#endif } //------------------------------------- @@ -517,10 +540,11 @@ int CAI_StandoffBehavior::SelectScheduleCheckCover( void ) { StandoffMsg( "Regulated to not shoot\n" ); #ifdef MAPBASE - if ( GetHintType() == HINT_TACTICAL_COVER_LOW || GetHintType() == HINT_TACTICAL_COVER_MED ) -#else - if ( GetHintType() == HINT_TACTICAL_COVER_LOW ) + if ( GetHintType() == HINT_TACTICAL_COVER_MED || GetCoverActivity() == ACT_COVER_MED ) + SetPosture( AIP_CROUCHING_MED ); + else #endif + if ( GetHintType() == HINT_TACTICAL_COVER_LOW ) SetPosture( AIP_CROUCHING ); else SetPosture( AIP_STANDING ); @@ -539,7 +563,11 @@ int CAI_StandoffBehavior::SelectScheduleEstablishAim( void ) { if ( HasCondition( COND_ENEMY_OCCLUDED ) ) { +#if EXPANDED_HL2_COVER_ACTIVITIES + if ( GetPosture() == AIP_CROUCHING || GetPosture() == AIP_CROUCHING_MED ) +#else if ( GetPosture() == AIP_CROUCHING ) +#endif { // force a stand up, just in case GetOuter()->SpeakSentence( STANDOFF_SENTENCE_STAND_CHECK_TARGET ); @@ -577,7 +605,11 @@ int CAI_StandoffBehavior::SelectScheduleAttack( void ) !HasCondition( COND_CAN_MELEE_ATTACK1 ) && HasCondition( COND_TOO_FAR_TO_ATTACK ) ) { +#ifdef MAPBASE + if ( (GetOuter()->GetActiveWeapon() && ( GetOuter()->GetActiveWeapon()->CapabilitiesGet() & bits_CAP_WEAPON_RANGE_ATTACK1 )) || GetOuter()->CapabilitiesGet() & bits_CAP_INNATE_RANGE_ATTACK1 ) +#else if ( GetOuter()->GetActiveWeapon() && ( GetOuter()->GetActiveWeapon()->CapabilitiesGet() & bits_CAP_WEAPON_RANGE_ATTACK1 ) ) +#endif { if ( !HasCondition( COND_ENEMY_OCCLUDED ) || random->RandomInt(0,99) < 50 ) // Don't advance, just fire anyway @@ -650,6 +682,15 @@ Activity CAI_MappedActivityBehavior_Temporary::GetMappedActivity( AI_Posture_t p { if ( posture != AIP_STANDING ) { +#if EXPANDED_HL2_COVER_ACTIVITIES + // See UpdateTranslateActivityMap() for more information on what this is for + if ( posture == AIP_CROUCHING_MED ) + { + if (activity != ACT_RANGE_ATTACK1) + posture = AIP_CROUCHING; + } +#endif + unsigned short iActivityTranslation = m_ActivityMap.Find( MAKE_ACTMAP_KEY( posture, activity ) ); if ( iActivityTranslation != m_ActivityMap.InvalidIndex() ) { @@ -667,10 +708,28 @@ Activity CAI_StandoffBehavior::NPC_TranslateActivity( Activity activity ) Activity coverActivity = GetCoverActivity(); if ( coverActivity != ACT_INVALID ) { +#ifdef MAPBASE + if ( GetPosture() == AIP_STANDING ) + { + if ( coverActivity == ACT_COVER_LOW ) + SetPosture( AIP_CROUCHING ); + else if ( coverActivity == ACT_COVER_MED ) + { + SetPosture( AIP_CROUCHING_MED ); + coverActivity = ACT_COVER_LOW; + } + } + else if (coverActivity == ACT_COVER_MED) + coverActivity = ACT_COVER_LOW; + if ( activity == ACT_IDLE ) activity = coverActivity; +#else + if (activity == ACT_IDLE) + activity = coverActivity; if ( GetPosture() == AIP_STANDING && coverActivity == ACT_COVER_LOW ) SetPosture( AIP_CROUCHING ); +#endif } Activity result = GetMappedActivity( GetPosture(), activity ); @@ -1071,12 +1130,25 @@ void CAI_StandoffBehavior::UnlockHintNode() Activity CAI_StandoffBehavior::GetCoverActivity() { -#ifdef MAPBASE - // This does two things: - // A. Allows medium cover nodes to be used, kind of. - // B. GetCoverActivity() already checks everything we checked here. - Activity coveract = GetOuter()->GetCoverActivity( GetHintNode() ); - return coveract == ACT_IDLE ? ACT_INVALID : coveract; +#if EXPANDED_HL2_COVER_ACTIVITIES + // GetCoverActivity() already checks everything we checked here. + Activity coverActivity = GetOuter()->GetCoverActivity( GetHintNode() ); + + if (coverActivity == ACT_COVER_LOW) + { + // Check if this node will block our line of sight if aiming low. + Vector vHintPos, vHintForward, vHintRight; + GetHintNode()->GetPosition( GetHullType(), &vHintPos ); + vHintForward = GetHintNode()->GetDirection(); + + GetHintNode()->GetVectors( NULL, &vHintRight, NULL ); + if (GetOuter()->CouldShootIfCrouchingAt( vHintPos, vHintForward, vHintRight )) + { + coverActivity = ACT_COVER_MED; + } + } + + return coverActivity == ACT_IDLE ? ACT_INVALID : coverActivity; #else CAI_Hint *pHintNode = GetHintNode(); if ( pHintNode && pHintNode->HintType() == HINT_TACTICAL_COVER_LOW ) @@ -1094,6 +1166,14 @@ struct AI_ActivityMapping_t Activity activity; const char * pszWeapon; Activity translation; +#ifdef MAPBASE + Activity backup; + + AI_ActivityMapping_t( AI_Posture_t _p, Activity _a, const char *_w, Activity _t, Activity _b = ACT_INVALID ) + { + posture = _p; activity = _a; pszWeapon = _w; translation = _t; backup = _b; + } +#endif }; void CAI_MappedActivityBehavior_Temporary::UpdateTranslateActivityMap() @@ -1107,15 +1187,60 @@ void CAI_MappedActivityBehavior_Temporary::UpdateTranslateActivityMap() { AIP_CROUCHING, ACT_WALK_AIM, NULL, ACT_WALK_CROUCH_AIM, }, { AIP_CROUCHING, ACT_RUN_AIM, NULL, ACT_RUN_CROUCH_AIM, }, { AIP_CROUCHING, ACT_RELOAD, NULL, ACT_RELOAD_LOW, }, +#ifdef MAPBASE + { AIP_CROUCHING, ACT_RANGE_ATTACK1, NULL, ACT_RANGE_ATTACK1_LOW, }, + { AIP_CROUCHING, ACT_COVER_MED, NULL, ACT_COVER_LOW, }, +#else { AIP_CROUCHING, ACT_RANGE_ATTACK_SMG1, NULL, ACT_RANGE_ATTACK_SMG1_LOW, }, { AIP_CROUCHING, ACT_RANGE_ATTACK_AR2, NULL, ACT_RANGE_ATTACK_AR2_LOW, }, +#endif +#if EXPANDED_HL2_COVER_ACTIVITIES + // + // ============ Really long explanation that should be in a wiki/documentation article somewhere ~ Blixibon, 10/27/2021 ============ + // + // Standoff behavior assumes low attack animations allow NPCs to see over barricades, with ACT_COVER_LOW being their "safely in cover" animation. + // This is why AIP_CROUCHING translates ACT_RANGE_ATTACK1 to its low animation, but translates ACT_IDLE_ANGRY to ACT_COVER_LOW instead of ACT_RANGE_AIM_LOW, + // as this would ideally allow NPCs to pop in and out of cover to shoot. + // This is also why AIP_PEEKING translates ACT_COVER_LOW to ACT_RANGE_AIM_LOW, as it's supposed to force the NPC to peek over their cover. + // + // However, this assumption mainly just applies to metrocops. Citizens' low attacking animations crouch low to the ground (which isn't effective for + // shooting over most barricades) and, while they do have a distinct ACT_COVER_LOW animation with transitions, they are close enough together that popping + // in and out of cover is redundant in most cases. Meanwhile, Combine soldiers have identical ACT_COVER_LOW and ACT_RANGE_AIM_LOW animations, which means + // they do not pop in and out of cover and AIP_PEEKING does nothing. This may be the reason why Combine soldiers occasionally get stuck in cover after a fight. + // + // ------------------------------------------------------------- + // + // As part of Mapbase v7.0's NPC activity overhaul, a new "medium cover" activity set has been added. Metrocops' previous "low cover" animation set (which, as + // mentioned before, is different from that of other NPCs) has been retroactively changed to use "medium cover". This was done for a few reasons unrelated to + // standoff behavior, but the important point is that these activities indicate a new cover height. This means we can use them to give standoff behavior more leeway + // for judging which animations to use in various levels of cover. + // + // Standoff behavior can use "medium cover" animations in cover which is too high for the "low" animations, and when the medium cover animations are not available, + // it simply falls back to the "standing" animations, thus resolving the issue with other NPCs not peeking in and out of cover without requiring new medium cover + // animations. + // + // In Mapbase, this is done by changing AIP_PEEKING to use the medium cover animations and adding a new alternate crouching posture posture called "AIP_CROUCHING_MED", + // which only uses the medium cover attack activity and otherwise automatically falls back to AIP_CROUCHING. AIP_CROUCHING_MED is automatically set if the NPC cannot + // get LOS from a regular crouching position. + // + { AIP_CROUCHING_MED, ACT_RANGE_ATTACK1, NULL, ACT_RANGE_ATTACK1_MED, }, + + //---- + { AIP_PEEKING, ACT_IDLE, NULL, ACT_RANGE_AIM_MED, }, + { AIP_PEEKING, ACT_IDLE_ANGRY, NULL, ACT_RANGE_AIM_MED, }, + { AIP_PEEKING, ACT_COVER_LOW, NULL, ACT_RANGE_AIM_MED, ACT_IDLE_ANGRY }, + { AIP_PEEKING, ACT_COVER_MED, NULL, ACT_RANGE_AIM_MED, ACT_IDLE_ANGRY }, + { AIP_PEEKING, ACT_RANGE_ATTACK1, NULL, ACT_RANGE_ATTACK1_MED, }, + { AIP_PEEKING, ACT_RELOAD, NULL, ACT_RELOAD_LOW, }, +#else //---- { AIP_PEEKING, ACT_IDLE, NULL, ACT_RANGE_AIM_LOW, }, { AIP_PEEKING, ACT_IDLE_ANGRY, NULL, ACT_RANGE_AIM_LOW, }, { AIP_PEEKING, ACT_COVER_LOW, NULL, ACT_RANGE_AIM_LOW, }, { AIP_PEEKING, ACT_RANGE_ATTACK1, NULL, ACT_RANGE_ATTACK1_LOW, }, { AIP_PEEKING, ACT_RELOAD, NULL, ACT_RELOAD_LOW, }, +#endif }; m_ActivityMap.RemoveAll(); @@ -1127,7 +1252,7 @@ void CAI_MappedActivityBehavior_Temporary::UpdateTranslateActivityMap() if ( !mappings[i].pszWeapon || stricmp( mappings[i].pszWeapon, pszWeaponClass ) == 0 ) { #ifdef MAPBASE - // Check backup activity + // Check NPC backup activity if ( HaveSequenceForActivity( mappings[i].translation ) || HaveSequenceForActivity( GetOuter()->Weapon_TranslateActivity( mappings[i].translation ) ) || HaveSequenceForActivity( GetOuter()->Weapon_BackupActivity( mappings[i].translation ) ) ) #else if ( HaveSequenceForActivity( mappings[i].translation ) || HaveSequenceForActivity( GetOuter()->Weapon_TranslateActivity( mappings[i].translation ) ) ) @@ -1136,6 +1261,14 @@ void CAI_MappedActivityBehavior_Temporary::UpdateTranslateActivityMap() Assert( m_ActivityMap.Find( MAKE_ACTMAP_KEY( mappings[i].posture, mappings[i].activity ) ) == m_ActivityMap.InvalidIndex() ); m_ActivityMap.Insert( MAKE_ACTMAP_KEY( mappings[i].posture, mappings[i].activity ), mappings[i].translation ); } +#ifdef MAPBASE + // Check activity map backup activity + else if ( mappings[i].backup != ACT_INVALID && HaveSequenceForActivity( mappings[i].backup ) ) + { + Assert( m_ActivityMap.Find( MAKE_ACTMAP_KEY( mappings[i].posture, mappings[i].activity ) ) == m_ActivityMap.InvalidIndex() ); + m_ActivityMap.Insert( MAKE_ACTMAP_KEY( mappings[i].posture, mappings[i].activity ), mappings[i].backup ); + } +#endif } } } diff --git a/sp/src/game/server/ai_behavior_standoff.h b/sp/src/game/server/ai_behavior_standoff.h index c08059e84a..9141e87434 100644 --- a/sp/src/game/server/ai_behavior_standoff.h +++ b/sp/src/game/server/ai_behavior_standoff.h @@ -51,6 +51,9 @@ enum AI_Posture_t AIP_INDIFFERENT, AIP_STANDING, AIP_CROUCHING, +#if EXPANDED_HL2_COVER_ACTIVITIES + AIP_CROUCHING_MED, // See UpdateTranslateActivityMap() for more information on what this is for +#endif AIP_PEEKING, }; @@ -149,6 +152,14 @@ class CAI_StandoffBehavior : public CAI_MappedActivityBehavior_Temporary // Standoff overrides base AI crouch handling bool IsCrouching( void ) { return false; } + +#ifdef MAPBASE + // Standoff overrides base cover activity translation + bool CanTranslateCrouchActivity( void ) { return false; } + + // Don't do death poses while crouching + bool ShouldPickADeathPose( void ) { return (GetPosture() != AIP_CROUCHING && GetPosture() != AIP_PEEKING) && BaseClass::ShouldPickADeathPose(); } +#endif private: diff --git a/sp/src/game/server/ai_blended_movement.cpp b/sp/src/game/server/ai_blended_movement.cpp index d1b32d1432..0a11c915dd 100644 --- a/sp/src/game/server/ai_blended_movement.cpp +++ b/sp/src/game/server/ai_blended_movement.cpp @@ -1640,10 +1640,17 @@ void CAI_BlendedMotor::MaintainTurnActivity( void ) ConVar scene_flatturn( "scene_flatturn", "1" ); +#ifdef MAPBASE +ConVar ai_turning_enabled( "ai_turning_enabled", "1", FCVAR_NONE, "Enables NPC turning, which was previously disabled by Valve at some point after 2004 due to a now-unknown major issue." ); +#endif + bool CAI_BlendedMotor::AddTurnGesture( float flYD ) { // some funky bug with human turn gestures, disable for now +#ifdef MAPBASE + if (!ai_turning_enabled.GetBool()) +#endif return false; // try using a turn gesture diff --git a/sp/src/game/server/ai_expresserfollowup.cpp b/sp/src/game/server/ai_expresserfollowup.cpp new file mode 100644 index 0000000000..6557570925 --- /dev/null +++ b/sp/src/game/server/ai_expresserfollowup.cpp @@ -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 + +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(pRespondent)) + { + pPlayer->Speak( followup.followup_concept, &criteria ); + } + + else if (CAI_BaseActor *pActor = dynamic_cast(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(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 \"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 \"criteria1:value1,criteria2:value2,...\"\n" + "criteria values are optional.\n" + + , FCVAR_CHEAT ); diff --git a/sp/src/game/server/ai_hint.cpp b/sp/src/game/server/ai_hint.cpp index 1f8ec4d142..2e8e90af85 100644 --- a/sp/src/game/server/ai_hint.cpp +++ b/sp/src/game/server/ai_hint.cpp @@ -35,6 +35,10 @@ CHintCriteria::CHintCriteria( void ) m_strGroup = NULL_STRING; m_iFlags = 0; m_HintTypes.Purge(); +#ifdef MAPBASE // From Alien Swarm SDK + m_pfnFilter = NULL; + m_pFilterContext = NULL; +#endif } //----------------------------------------------------------------------------- @@ -1117,10 +1121,10 @@ void CAI_Hint::NPCHandleStartNav( CAI_BaseNPC *pNPC, bool bDefaultFacing ) HintIgnoreFacing_t facing = GetIgnoreFacing(); if (facing == HIF_DEFAULT) - facing = bDefaultFacing ? HIF_YES : HIF_NO; + facing = bDefaultFacing ? HIF_NO : HIF_YES; - if (facing == HIF_YES) - pNPC->GetNavigator()->SetArrivalDirection(GetDirection()); + if (facing == HIF_NO) + pNPC->GetNavigator()->SetArrivalDirection( GetDirection() ); if (HintActivityName() != NULL_STRING) { @@ -1139,6 +1143,51 @@ void CAI_Hint::NPCHandleStartNav( CAI_BaseNPC *pNPC, bool bDefaultFacing ) } } } + +//----------------------------------------------------------------------------- +// Purpose: Returns true if this hint should override a NPC's yaw even during regular AI. +//----------------------------------------------------------------------------- +bool CAI_Hint::OverridesNPCYaw( CAI_BaseNPC *pNPC ) +{ + switch (HintType()) + { + case HINT_TACTICAL_COVER_CUSTOM: + case HINT_TACTICAL_COVER_MED: + case HINT_TACTICAL_COVER_LOW: + { + if (pNPC->HasMemory( bits_MEMORY_INCOVER )) + { + // By default, don't override yaw on cover nodes unless they use custom activities. + HintIgnoreFacing_t facing = GetIgnoreFacing(); + if (facing == HIF_DEFAULT) + return ( HintActivityName() != NULL_STRING ); + + return facing == HIF_NO; + } + + break; + } + + case HINT_PLAYER_ALLY_MOVE_AWAY_DEST: + { + Vector vHintPos; + GetPosition( pNPC, &vHintPos ); + if (VectorsAreEqual( vHintPos, pNPC->GetAbsOrigin(), 0.1f )) + { + // By default, don't override yaw on move away destinations unless they use custom activities. + HintIgnoreFacing_t facing = GetIgnoreFacing(); + if (facing == HIF_DEFAULT) + return ( HintActivityName() != NULL_STRING ); + + return facing == HIF_NO; + } + + break; + } + } + + return false; +} #endif //----------------------------------------------------------------------------- @@ -1258,6 +1307,30 @@ bool CAI_Hint::HintMatchesCriteria( CAI_BaseNPC *pNPC, const CHintCriteria &hint return false; } +#ifdef MAPBASE + // Test against generic filter + // (From Alien Swarm SDK) + if ( !hintCriteria.PassesFilter( this ) ) + { + REPORTFAILURE( "Failed filter test" ); + return false; + } + + // (From Alien Swarm SDK) + int nRadius = GetRadius(); + if ( nRadius != 0 ) + { + // Calculate our distance + float distance = (GetAbsOrigin() - position).LengthSqr(); + + if ( distance > nRadius * nRadius ) + { + REPORTFAILURE( "NPC is not within the node's radius." ); + return false; + } + } +#endif + if ( hintCriteria.HasFlag(bits_HINT_NPC_IN_NODE_FOV) ) { if ( pNPC == NULL ) @@ -1377,10 +1450,18 @@ bool CAI_Hint::HintMatchesCriteria( CAI_BaseNPC *pNPC, const CHintCriteria &hint { trace_t tr; // Can my bounding box fit there? +#ifdef MAPBASE // From Alien Swarm SDK + Vector vStep( 0, 0, pNPC->StepHeight() ); + AI_TraceHull ( GetAbsOrigin() + vStep, GetAbsOrigin(), pNPC->WorldAlignMins(), pNPC->WorldAlignMaxs() - vStep, + MASK_SOLID, pNPC, COLLISION_GROUP_NONE, &tr ); + + if ( tr.fraction < 0.95 ) +#else AI_TraceHull ( GetAbsOrigin(), GetAbsOrigin(), pNPC->WorldAlignMins(), pNPC->WorldAlignMaxs(), MASK_SOLID, pNPC, COLLISION_GROUP_NONE, &tr ); if ( tr.fraction != 1.0 ) +#endif { REPORTFAILURE( "Node isn't clear." ); return false; @@ -1396,6 +1477,15 @@ bool CAI_Hint::HintMatchesCriteria( CAI_BaseNPC *pNPC, const CHintCriteria &hint // Calculate our distance float distance = (GetAbsOrigin() - position).Length(); +#ifdef MAPBASE + // Divide by hint weight + float flWeight = GetHintWeight(); + if ( flWeight != 1.0f ) + { + distance *= GetHintWeightInverse(); + } +#endif + // Must be closer than the current best if ( distance > *flNearestDistance ) { @@ -1576,6 +1666,14 @@ void CAI_Hint::OnRestore() m_NodeData.nNodeID = g_pAINetworkManager->GetEditOps()->GetNodeIdFromWCId( m_NodeData.nWCNodeID ); FixupTargetNode(); +#ifdef MAPBASE + if (m_NodeData.flWeight != 0.0f && m_NodeData.flWeight != 1.0f) + { + // Re-invert the weight + m_NodeData.flWeightInverse = 1.0f / m_NodeData.flWeight; + } +#endif + CAI_Node *pNode = GetNode(); if ( !pNode ) @@ -1751,6 +1849,11 @@ void CC_ai_drop_hint( const CCommand &args ) nodeData.fIgnoreFacing = HIF_DEFAULT; nodeData.minState = NPC_STATE_IDLE; nodeData.maxState = NPC_STATE_COMBAT; +#ifdef MAPBASE + nodeData.nRadius = 0; // From Alien Swarm SDK + nodeData.flWeight = 1.0f; + nodeData.flWeightInverse = 1.0f; +#endif CAI_Hint *pHint = CAI_HintManager::CreateHint( &nodeData, NULL ); if ( pHint ) { diff --git a/sp/src/game/server/ai_hint.h b/sp/src/game/server/ai_hint.h index 25fd4fdb2c..570f85ef91 100644 --- a/sp/src/game/server/ai_hint.h +++ b/sp/src/game/server/ai_hint.h @@ -112,6 +112,13 @@ enum Hint_e // CS port hints HINT_CSTRIKE_HOSTAGE_ESCAPE = 1100, + +#ifdef MAPBASE + // Mapbase hints + // (these start at a high number to avoid potential conflicts with mod hints) + + HINT_TACTICAL_COVER_CUSTOM = 10000, // Cover node with a custom hint activity (NPCs can take cover and reload here while playing said activity) +#endif }; const char *GetHintTypeDescription( Hint_e iHintType ); const char *GetHintTypeDescription( CAI_Hint *pHint ); @@ -120,6 +127,10 @@ const char *GetHintTypeDescription( CAI_Hint *pHint ); // CHintCriteria //----------------------------------------------------------------------------- +#ifdef MAPBASE // From Alien Swarm SDK +typedef bool (*HintSearchFilterFunc_t)( void *pContext, CAI_Hint *pCandidate ); +#endif + class CHintCriteria { public: @@ -134,6 +145,11 @@ class CHintCriteria void SetGroup( string_t group ); string_t GetGroup( void ) const { return m_strGroup; } +#ifdef MAPBASE // From Alien Swarm SDK + void SetFilterFunc( HintSearchFilterFunc_t pfnFilter, void *pContext = NULL ) { m_pfnFilter = pfnFilter; m_pFilterContext = pContext; } + bool PassesFilter( CAI_Hint *pCandidate ) const { return (m_pfnFilter) ? (*m_pfnFilter)(m_pFilterContext, pCandidate) : true; } +#endif + int GetFirstHintType( void ) const { return m_iFirstHintType; } int GetLastHintType( void ) const { return m_iLastHintType; } bool MatchesHintType( int hintType ) const; @@ -176,6 +192,11 @@ class CHintCriteria zoneList_t m_zoneInclude; zoneList_t m_zoneExclude; + +#ifdef MAPBASE + HintSearchFilterFunc_t m_pfnFilter; + void * m_pFilterContext; +#endif }; class CAI_Node; @@ -289,6 +310,11 @@ class CAI_Hint : public CServerOnlyEntity 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 ); @@ -305,11 +331,21 @@ class CAI_Hint : public CServerOnlyEntity int GetNodeId() { return m_NodeData.nNodeID; } int GetWCId() { return m_NodeData.nWCNodeID; } +#ifdef MAPBASE + int GetRadius() const { return m_NodeData.nRadius; } // From Alien Swarm SDK + + float GetHintWeight() const { return m_NodeData.flWeight; } + float GetHintWeightInverse() const { return m_NodeData.flWeightInverse; } // Used to multiply distances +#endif + bool HintMatchesCriteria( CAI_BaseNPC *pNPC, const CHintCriteria &hintCriteria, const Vector &position, float *flNearestDistance, bool bIgnoreLock = false, bool bIgnoreHintType = false ); bool IsInNodeFOV( CBaseEntity *pOther ); #ifdef MAPBASE void NPCHandleStartNav( CAI_BaseNPC *pNPC, bool bDefaultFacing ); + + // Returns true if this hint should override a NPC's yaw even during regular AI. + bool OverridesNPCYaw( CAI_BaseNPC *pNPC ); #endif #ifdef MAPBASE_VSCRIPT @@ -319,7 +355,9 @@ class CAI_Hint : public CServerOnlyEntity const char* ScriptGetHintActivity() { return STRING( HintActivityName() ); } #endif +#ifndef MAPBASE private: +#endif void Spawn( void ); virtual void Activate(); virtual void UpdateOnRemove( void ); diff --git a/sp/src/game/server/ai_initutils.cpp b/sp/src/game/server/ai_initutils.cpp index 8c4767197e..cd79279881 100644 --- a/sp/src/game/server/ai_initutils.cpp +++ b/sp/src/game/server/ai_initutils.cpp @@ -173,6 +173,10 @@ BEGIN_SIMPLE_DATADESC( HintNodeData ) DEFINE_KEYFIELD( fIgnoreFacing, FIELD_INTEGER, "IgnoreFacing" ), DEFINE_KEYFIELD( minState, FIELD_INTEGER, "MinimumState" ), DEFINE_KEYFIELD( maxState, FIELD_INTEGER, "MaximumState" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( nRadius, FIELD_INTEGER, "radius" ), // From Alien Swarm SDK + DEFINE_KEYFIELD( flWeight, FIELD_FLOAT, "hintweight" ), +#endif END_DATADESC() @@ -205,6 +209,17 @@ int CNodeEnt::Spawn( const char *pMapData ) m_NodeData.minState = NPC_STATE_IDLE; if ( m_NodeData.maxState == NPC_STATE_NONE ) m_NodeData.maxState = NPC_STATE_COMBAT; +#ifdef MAPBASE + if (m_NodeData.flWeight == 0.0f) + { + m_NodeData.flWeight = 1.0f; + } + else if (m_NodeData.flWeight != 1.0f) + { + // Invert the weight so that it could be used as a direct multiplier for distances, etc. + m_NodeData.flWeightInverse = 1.0f / m_NodeData.flWeight; + } +#endif // --------------------------------------------------------------------------------- // If just a hint node (not used for navigation) just create a hint and bail // --------------------------------------------------------------------------------- @@ -227,7 +242,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 ) { diff --git a/sp/src/game/server/ai_initutils.h b/sp/src/game/server/ai_initutils.h index 22aaf5f3af..70bd019c00 100644 --- a/sp/src/game/server/ai_initutils.h +++ b/sp/src/game/server/ai_initutils.h @@ -42,6 +42,11 @@ struct HintNodeData HintIgnoreFacing_t fIgnoreFacing; NPC_STATE minState; NPC_STATE maxState; +#ifdef MAPBASE + int nRadius; // From Alien Swarm SDK + float flWeight; + float flWeightInverse; // Not saved +#endif int nWCNodeID; // Node ID assigned by worldcraft (not same as engine!) diff --git a/sp/src/game/server/ai_memory.cpp b/sp/src/game/server/ai_memory.cpp index 0af73a8957..7ac69311a2 100644 --- a/sp/src/game/server/ai_memory.cpp +++ b/sp/src/game/server/ai_memory.cpp @@ -146,29 +146,6 @@ BEGIN_SIMPLE_DATADESC( AI_EnemyInfo_t ) // NOT SAVED nextEMemory END_DATADESC() -#ifdef MAPBASE_VSCRIPT -#define DEFINE_ENEMY_INFO_SCRIPTFUNCS(name, desc) \ - DEFINE_SCRIPTFUNC_NAMED( Get##name, #name, "Get " desc ) \ - DEFINE_SCRIPTFUNC( Set##name, "Set " desc ) - -BEGIN_SCRIPTDESC_ROOT( AI_EnemyInfo_t, "Accessor for information about an enemy." ) - DEFINE_SCRIPTFUNC( Enemy, "Get the enemy." ) - DEFINE_SCRIPTFUNC( SetEnemy, "Set the enemy." ) - DEFINE_ENEMY_INFO_SCRIPTFUNCS( LastKnownLocation, "the enemy's last known location." ) - DEFINE_ENEMY_INFO_SCRIPTFUNCS( LastSeenLocation, "the enemy's last seen location." ) - DEFINE_ENEMY_INFO_SCRIPTFUNCS( TimeLastSeen, "the last time the enemy was seen." ) - DEFINE_ENEMY_INFO_SCRIPTFUNCS( TimeFirstSeen, "the first time the enemy was seen." ) - DEFINE_ENEMY_INFO_SCRIPTFUNCS( TimeLastReacquired, "the last time the enemy was reaquired." ) - DEFINE_ENEMY_INFO_SCRIPTFUNCS( TimeValidEnemy, "the time at which the enemy can be selected (reaction delay)." ) - DEFINE_ENEMY_INFO_SCRIPTFUNCS( TimeLastReceivedDamageFrom, "the last time damage was received from this enemy." ) - DEFINE_ENEMY_INFO_SCRIPTFUNCS( TimeAtFirstHand, "the time at which the enemy was seen firsthand." ) - DEFINE_ENEMY_INFO_SCRIPTFUNCS( DangerMemory, "the memory of danger position w/o enemy pointer." ) - DEFINE_ENEMY_INFO_SCRIPTFUNCS( EludedMe, "whether the enemy is not at the last known location." ) - DEFINE_ENEMY_INFO_SCRIPTFUNCS( Unforgettable, "whether the enemy is unforgettable." ) - DEFINE_ENEMY_INFO_SCRIPTFUNCS( MobbedMe, "whether the enemy was part of a mob at some point." ) -END_SCRIPTDESC(); -#endif - //----------------------------------------------------------------------------- CAI_Enemies::CAI_Enemies(void) diff --git a/sp/src/game/server/ai_memory.h b/sp/src/game/server/ai_memory.h index faa482a267..d348c53e02 100644 --- a/sp/src/game/server/ai_memory.h +++ b/sp/src/game/server/ai_memory.h @@ -45,29 +45,6 @@ struct AI_EnemyInfo_t bool bUnforgettable; bool bMobbedMe; // True if enemy was part of a mob at some point -#ifdef MAPBASE_VSCRIPT - // Script functions. - #define ENEMY_INFO_SCRIPT_FUNCS(type, name, var) \ - type Get##name() { return var; } \ - void Set##name( type v ) { var = v; } - - HSCRIPT Enemy() { return ToHScript(hEnemy); } - void SetEnemy( HSCRIPT ent ) { hEnemy = ToEnt(ent); } - - ENEMY_INFO_SCRIPT_FUNCS( Vector, LastKnownLocation, vLastKnownLocation ); - ENEMY_INFO_SCRIPT_FUNCS( Vector, LastSeenLocation, vLastSeenLocation ); - ENEMY_INFO_SCRIPT_FUNCS( float, TimeLastSeen, timeLastSeen ); - ENEMY_INFO_SCRIPT_FUNCS( float, TimeFirstSeen, timeFirstSeen ); - ENEMY_INFO_SCRIPT_FUNCS( float, TimeLastReacquired, timeLastReacquired ); - ENEMY_INFO_SCRIPT_FUNCS( float, TimeValidEnemy, timeValidEnemy ); - ENEMY_INFO_SCRIPT_FUNCS( float, TimeLastReceivedDamageFrom, timeLastReceivedDamageFrom ); - ENEMY_INFO_SCRIPT_FUNCS( float, TimeAtFirstHand, timeAtFirstHand ); - ENEMY_INFO_SCRIPT_FUNCS( bool, DangerMemory, bDangerMemory ); - ENEMY_INFO_SCRIPT_FUNCS( bool, EludedMe, bEludedMe ); - ENEMY_INFO_SCRIPT_FUNCS( bool, Unforgettable, bUnforgettable ); - ENEMY_INFO_SCRIPT_FUNCS( bool, MobbedMe, bMobbedMe ); -#endif - DECLARE_SIMPLE_DATADESC(); }; diff --git a/sp/src/game/server/ai_motor.cpp b/sp/src/game/server/ai_motor.cpp index 49f3d5f462..87a8ec6e7e 100644 --- a/sp/src/game/server/ai_motor.cpp +++ b/sp/src/game/server/ai_motor.cpp @@ -14,6 +14,9 @@ #include "ai_basenpc.h" #include "ai_localnavigator.h" #include "ai_moveprobe.h" +#ifdef MAPBASE +#include "ai_hint.h" +#endif #include "saverestore_utlvector.h" // memdbgon must be the last include file in a .cpp file!!! @@ -235,18 +238,47 @@ void CAI_Motor::MoveClimbStart( const Vector &climbDest, const Vector &climbDir // > code are not reciprocal for all state, and furthermore, stomp // > other state? + bool bGoingUp = (climbDir.z > 0.01); +#if EXPANDED_NAVIGATION_ACTIVITIES + if ( bGoingUp && GetOuter()->HaveSequenceForActivity( ACT_CLIMB_MOUNT_BOTTOM ) ) + { + SetActivity( ACT_CLIMB_MOUNT_BOTTOM ); + + // Steal m_vecDismount for this + GetOuter()->GetSequenceLinearMotion( GetSequence(), &m_vecDismount ); + GetOuter()->SetCycle( GetOuter()->GetMovementFrame( m_vecDismount.z - climbDist ) ); + } + else if ( !bGoingUp && GetOuter()->HaveSequenceForActivity( ACT_CLIMB_MOUNT_TOP ) ) + { + SetActivity( ACT_CLIMB_MOUNT_TOP ); + + // Steal m_vecDismount for this + GetOuter()->GetSequenceLinearMotion( GetSequence(), &m_vecDismount ); + GetOuter()->SetCycle( GetOuter()->GetMovementFrame( m_vecDismount.z - climbDist ) ); + } + else +#endif if ( fabsf( climbDir.z ) < .1 ) { SetActivity( GetNavigator()->GetMovementActivity() ); } else { - SetActivity( (climbDir.z > -0.01 ) ? ACT_CLIMB_UP : ACT_CLIMB_DOWN ); + SetActivity( bGoingUp ? ACT_CLIMB_UP : ACT_CLIMB_DOWN ); } m_nDismountSequence = SelectWeightedSequence( ACT_CLIMB_DISMOUNT ); if (m_nDismountSequence != ACT_INVALID) { +#if EXPANDED_NAVIGATION_ACTIVITIES + if ( !bGoingUp ) + { + int nBottomDismount = SelectWeightedSequence( ACT_CLIMB_DISMOUNT_BOTTOM ); + if (nBottomDismount != ACTIVITY_NOT_AVAILABLE) + m_nDismountSequence = nBottomDismount; + } +#endif + GetOuter()->GetSequenceLinearMotion( m_nDismountSequence, &m_vecDismount ); } else @@ -262,6 +294,76 @@ void CAI_Motor::MoveClimbStart( const Vector &climbDest, const Vector &climbDir AIMoveResult_t CAI_Motor::MoveClimbExecute( const Vector &climbDest, const Vector &climbDir, float climbDist, float yaw, int climbNodesLeft ) { +#if EXPANDED_NAVIGATION_ACTIVITIES + if ( (GetActivity() == ACT_CLIMB_MOUNT_TOP || GetActivity() == ACT_CLIMB_MOUNT_BOTTOM) ) + { + if (!GetOuter()->IsActivityFinished()) + { + // Wait for the mounting to finish + SetGroundEntity( NULL ); + } + else + { + // Fix up our position if we have to + Vector vecTeleportOrigin; + if (MoveClimbShouldTeleportToSequenceEnd( vecTeleportOrigin )) + { + SetLocalOrigin( vecTeleportOrigin ); + } + + // Reset activity and start from the beginning + GetOuter()->ResetActivity(); + return MoveClimbExecute( climbDest, climbDir, climbDist, yaw, climbNodesLeft ); + } + } + else if ( fabsf( climbDir.z ) > .1 && (GetActivity() != ACT_CLIMB_DISMOUNT && GetActivity() != ACT_CLIMB_DISMOUNT_BOTTOM) ) + { + bool bGoingUp = (climbDir.z > -0.01); + if ( GetOuter()->HaveSequenceForActivity( ACT_CLIMB_ALL ) ) + { + SetActivity( ACT_CLIMB_ALL ); + + // TODO: Use UTIL_VecToPitch() instead if move_yaw becomes a true climb yaw and not just an up-down scalar + SetPoseParameter( GetOuter()->LookupPoseMoveYaw(), climbDir.z < 0 ? 180.0 : -180.0 ); + } + else + { + Activity desiredActivity = bGoingUp ? ACT_CLIMB_UP : ACT_CLIMB_DOWN; + if ( GetActivity() != desiredActivity ) + { + SetActivity( desiredActivity ); + } + } + + if (m_nDismountSequence != ACT_INVALID) + { + if (climbNodesLeft <= 2 && climbDist < fabs( m_vecDismount.z )) + { + if (bGoingUp) + { + // fixme: No other way to force m_nIdealSequence? + GetOuter()->SetActivity( ACT_CLIMB_DISMOUNT ); + GetOuter()->SetCycle( GetOuter()->GetMovementFrame( m_vecDismount.z - climbDist ) ); + } + else + { + if (GetSequence() != m_nDismountSequence && GetOuter()->GetSequenceActivity( m_nDismountSequence ) == ACT_CLIMB_DISMOUNT_BOTTOM) + { + SetActivity( ACT_CLIMB_DISMOUNT_BOTTOM ); + } + } + } + } + } + else if ( climbDir.Length() == 0 && GetOuter()->GetInstantaneousVelocity() <= 0.01 ) + { + // The NPC is somehow stuck climbing with no direction or movement. + // This can be caused by NPCs getting stuck in each other and/or being moved away from the ladder. + // In these cases, the NPC has to be made unstuck, or else they may remain in an immobile climbing state forever. + Warning( "%s had to abort climbing due to no direction or movement\n", GetOuter()->GetDebugName() ); + return AIMR_ILLEGAL; + } +#else if ( fabsf( climbDir.z ) > .1 ) { if ( GetActivity() != ACT_CLIMB_DISMOUNT ) @@ -292,13 +394,34 @@ AIMoveResult_t CAI_Motor::MoveClimbExecute( const Vector &climbDest, const Vecto } } } +#endif float climbSpeed = GetOuter()->GetInstantaneousVelocity(); if (m_nDismountSequence != ACT_INVALID) { // catch situations where the climb mount/dismount finished before reaching goal +#if EXPANDED_NAVIGATION_ACTIVITIES + if ((GetActivity() == ACT_CLIMB_DISMOUNT || GetActivity() == ACT_CLIMB_DISMOUNT_BOTTOM)) + { + SetGroundEntity( NULL ); + + if (GetOuter()->IsActivityFinished()) + { + // Fix up our position if we have to + Vector vecTeleportOrigin; + if (MoveClimbShouldTeleportToSequenceEnd( vecTeleportOrigin )) + { + // Just force it to complete + climbDist = 0.0f; + } + + climbSpeed = 200.0f; + } + } +#else climbSpeed = MAX( climbSpeed, 30.0 ); +#endif } else { @@ -314,7 +437,7 @@ AIMoveResult_t CAI_Motor::MoveClimbExecute( const Vector &climbDest, const Vecto climbDist = 0; const float climbTime = climbDist / climbSpeed; - + SetMoveInterval( GetMoveInterval() - climbTime ); SetLocalOrigin( climbDest ); @@ -330,6 +453,20 @@ AIMoveResult_t CAI_Motor::MoveClimbExecute( const Vector &climbDest, const Vecto // -------------------------------------------- SetIdealYawAndUpdate( yaw ); +#ifdef MAPBASE + // Lock the yaw if we're in position + if ( UTIL_AngleMod( yaw ) == UTIL_AngleMod( GetLocalAngles().y ) ) + { + SetYawLocked( true ); + } + else if ( IsYawLocked() ) + { + // We're in a different position now. Unlock the yaw and update it + SetYawLocked( false ); + UpdateYaw( -1 ); + } +#endif + return AIMR_OK; } @@ -340,11 +477,62 @@ void CAI_Motor::MoveClimbStop() else SetActivity( ACT_IDLE ); +#ifdef MAPBASE + // Unlock desired weapon state so NPCs can unholster their weapons again. + GetOuter()->SetDesiredWeaponState( DESIREDWEAPONSTATE_IGNORE ); + + // Unlock yaw + SetYawLocked( false ); +#endif + GetOuter()->RemoveFlag( FL_FLY ); SetSmoothedVelocity( vec3_origin ); SetGravity( 1.0 ); } +#ifdef MAPBASE +void CAI_Motor::MoveClimbPause() +{ + if (GetActivity() != ACT_CLIMB_DISMOUNT +#if EXPANDED_NAVIGATION_ACTIVITIES + && GetActivity() != ACT_CLIMB_MOUNT_TOP && GetActivity() != ACT_CLIMB_MOUNT_BOTTOM +#endif + ) + { + if ( GetActivity() == ACT_CLIMB_ALL ) + { + SetPoseParameter( GetOuter()->LookupPoseMoveYaw(), 0.0f ); + } + + SetSmoothedVelocity( vec3_origin ); + } + else + { + // If already dismounting, do nothing + } +} + +//----------------------------------------------------------------------------- +// Purpose: This is part of a hack needed in cases where ladder mount/dismount animations collide with the world and don't move properly. +// It's based off of the same code as scripted_sequence's teleportation fixup, although this function only resolves the bone origin and +// returns whether or not teleportation is necessary, as the teleportation is achieved in different ways for different uses of this code. +//----------------------------------------------------------------------------- +bool CAI_Motor::MoveClimbShouldTeleportToSequenceEnd( Vector &teleportOrigin ) +{ + QAngle new_angle; + GetOuter()->GetBonePosition( 0, teleportOrigin, new_angle ); + + // Ensure that there is actually a distance needed to teleport there + if ((GetLocalOrigin() - teleportOrigin).Length2DSqr() > Square( 8.0 )) + { + teleportOrigin.z = GetLocalOrigin().z; + return true; + } + + return false; +} +#endif + //----------------------------------------------------------------------------- // Purpose: Motion for jumping // Input : @@ -651,9 +839,20 @@ void CAI_Motor::MoveFacing( const AILocalMoveGoal_t &move ) { // FIXME: move this up to navigator so that path goals can ignore these overrides. Vector dir; - float flInfluence = GetFacingDirection( dir ); - dir = move.facing * (1 - flInfluence) + dir * flInfluence; - VectorNormalize( dir ); + +#ifdef MAPBASE + if (IsDeceleratingToGoal() && (GetOuter()->GetHintNode() /*|| GetOuter()->m_hOpeningDoor*/)) + { + // Don't let the facing queue interfere with arrival direction in important cases + dir = move.facing; + } + else +#endif + { + float flInfluence = GetFacingDirection( dir ); + dir = move.facing * (1 - flInfluence) + dir * flInfluence; + VectorNormalize( dir ); + } // ideal facing direction float idealYaw = UTIL_AngleMod( UTIL_VecToYaw( dir ) ); diff --git a/sp/src/game/server/ai_motor.h b/sp/src/game/server/ai_motor.h index d7f14293b6..29c05f8464 100644 --- a/sp/src/game/server/ai_motor.h +++ b/sp/src/game/server/ai_motor.h @@ -62,6 +62,10 @@ class CAI_Motor : public CAI_Component, virtual void MoveClimbStart( const Vector &climbDest, const Vector &climbDir, float climbDist, float yaw ); virtual AIMoveResult_t MoveClimbExecute( const Vector &climbDest, const Vector &climbDir, float climbDist, float yaw, int climbNodesLeft ); virtual void MoveClimbStop(); +#ifdef MAPBASE + virtual void MoveClimbPause(); + virtual bool MoveClimbShouldTeleportToSequenceEnd( Vector &teleportOrigin ); +#endif //--------------------------------- @@ -83,6 +87,9 @@ class CAI_Motor : public CAI_Component, const Vector & GetCurVel() const { return m_vecVelocity; } virtual float OverrideMaxYawSpeed( Activity activity ) { return -1; } +#ifdef MAPBASE + virtual +#endif bool IsDeceleratingToGoal() const { return false; } //--------------------------------- diff --git a/sp/src/game/server/ai_navigator.cpp b/sp/src/game/server/ai_navigator.cpp index d136c92325..dacab2aa94 100644 --- a/sp/src/game/server/ai_navigator.cpp +++ b/sp/src/game/server/ai_navigator.cpp @@ -999,12 +999,23 @@ int CAI_Navigator::GetArrivalSequence( int curSequence ) activity = ACT_IDLE; } - sequence = GetOuter()->SelectWeightedSequence( GetOuter()->TranslateActivity( activity ), curSequence ); + Activity translatedActivity = GetOuter()->TranslateActivity( activity ); + sequence = GetOuter()->SelectWeightedSequence( translatedActivity, curSequence ); if ( sequence == ACT_INVALID ) { - DevMsg( GetOuter(), "No appropriate sequence for arrival activity %s (%d)\n", GetOuter()->GetActivityName( GetPath()->GetArrivalActivity() ), GetPath()->GetArrivalActivity() ); - sequence = GetOuter()->SelectWeightedSequence( GetOuter()->TranslateActivity( ACT_IDLE ), curSequence ); +#ifdef MAPBASE + if ( translatedActivity == ACT_SCRIPT_CUSTOM_MOVE ) + { + // ACT_SCRIPT_CUSTOM_MOVE allows activity translation to resolve into specific sequences + sequence = GetOuter()->GetScriptCustomMoveSequence(); + } + else +#endif + { + DevMsg( GetOuter(), "No appropriate sequence for arrival activity %s (%d)\n", GetOuter()->GetActivityName( GetPath()->GetArrivalActivity() ), GetPath()->GetArrivalActivity() ); + sequence = GetOuter()->SelectWeightedSequence( GetOuter()->TranslateActivity( ACT_IDLE ), curSequence ); + } } Assert( sequence != ACT_INVALID ); GetPath()->SetArrivalSequence( sequence ); @@ -1642,6 +1653,15 @@ void CAI_Navigator::MoveCalcBaseGoal( AILocalMoveGoal_t *pMoveGoal ) AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint(); if ( pCurWaypoint->GetNext() && pCurWaypoint->GetNext()->NavType() != pCurWaypoint->NavType() ) pMoveGoal->flags |= AILMG_TARGET_IS_TRANSITION; + +#ifdef MAPBASE + // TODO: Better place for this code? + if (pMoveGoal->flags & AILMG_TARGET_IS_TRANSITION && pCurWaypoint->GetNext()->NavType() == NAV_CLIMB) + { + // NPCs need to holster their weapons before climbing. + GetOuter()->SetDesiredWeaponState( DESIREDWEAPONSTATE_HOLSTERED ); + } +#endif } const Task_t *pCurTask = GetOuter()->GetTask(); @@ -2164,11 +2184,26 @@ bool CAI_Navigator::OnMoveBlocked( AIMoveResult_t *pResult ) if (pDoor != NULL) { GetOuter()->OpenPropDoorBegin( pDoor ); +#ifdef MAPBASE + // Tell the navigation to stop running until we're done. + OnNewGoal(); +#endif *pResult = AIMR_OK; return true; } } +#ifdef MAPBASE + if ( GetOuter()->m_hOpeningDoor ) + { + // In the process of opening a door + // Because navigation is now supposed to terminate when a NPC begins opening a door, this code should not be reached. + DbgNavMsg( GetOuter(), "CAI_Navigator::OnMoveBlocked had to check for m_hOpeningDoor\n" ); + *pResult = AIMR_OK; + return true; + } +#endif + // Allow the NPC to override this behavior if ( GetOuter()->OnMoveBlocked( pResult )) @@ -2591,8 +2626,12 @@ bool CAI_Navigator::Move( float flInterval ) if ( GetNavType() == NAV_CLIMB ) { +#ifdef MAPBASE + GetMotor()->MoveClimbPause(); +#else GetMotor()->MoveClimbStop(); SetNavType( NAV_GROUND ); +#endif } GetMotor()->MoveStop(); AssertMsg( TaskIsRunning() || TaskIsComplete(), ("Schedule stalled!!\n") ); @@ -2695,6 +2734,11 @@ void CAI_Navigator::AdvancePath() if (pDoor != NULL) { GetOuter()->OpenPropDoorBegin(pDoor); + +#ifdef MAPBASE + // Tell the navigation to stop running until we're done. + OnNewGoal(); +#endif } else { @@ -3880,7 +3924,12 @@ bool CAI_Navigator::GetStoppingPath( CAI_WaypointList * pClippedWaypoints ) AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint(); if ( pCurWaypoint ) { +#if EXPANDED_NAVIGATION_ACTIVITIES + // Since regular climb nav can interrupt itself now, only do this when dismounting + bool bMustCompleteCurrent = ( (pCurWaypoint->NavType() == NAV_CLIMB && (GetActivity() == ACT_CLIMB_DISMOUNT || GetActivity() == ACT_CLIMB_MOUNT_TOP)) || pCurWaypoint->NavType() == NAV_JUMP ); +#else bool bMustCompleteCurrent = ( pCurWaypoint->NavType() == NAV_CLIMB || pCurWaypoint->NavType() == NAV_JUMP ); +#endif float distRemaining = GetMotor()->MinStoppingDist( 0 ); if ( bMustCompleteCurrent ) diff --git a/sp/src/game/server/ai_network.cpp b/sp/src/game/server/ai_network.cpp index 16d8a7fa58..1323b7ce21 100644 --- a/sp/src/game/server/ai_network.cpp +++ b/sp/src/game/server/ai_network.cpp @@ -144,6 +144,31 @@ class CNodeFilter : public INodeListFilter int m_capabilities; // cache this }; +#ifdef MAPBASE +//------------------------------------- +// Purpose: A version of CNodeFilter which allows hints to influence the result. +//------------------------------------- +class CNodeHintFilter : public CNodeFilter +{ +public: + CNodeHintFilter( CAI_BaseNPC *pNPC, const Vector &pos ) : CNodeFilter( pNPC, pos ) {} + CNodeHintFilter( const Vector &pos ) : CNodeFilter( pos ) {} + + virtual float NodeDistanceSqr( CAI_Node &node ) + { + // Heavier hints are considered closer + if ( node.GetHint() && node.GetHint()->GetHintWeight() != 1.0f && (node.GetHint()->GetGroup() == NULL_STRING || node.GetHint()->GetGroup() == m_pNPC->GetHintGroup()) ) + { + return CNodeFilter::NodeDistanceSqr( node ) * node.GetHint()->GetHintWeightInverse(); + } + else + { + return CNodeFilter::NodeDistanceSqr( node ); + } + } +}; +#endif + //----------------------------------------------------------------------------- // CAI_Network //----------------------------------------------------------------------------- @@ -351,7 +376,12 @@ int CAI_Network::NearestNodeToPoint( CAI_BaseNPC *pNPC, const Vector &vecOrigin, // First get nodes distances and eliminate those that are beyond // the maximum allowed distance for local movements // --------------------------------------------------------------- +#ifdef MAPBASE + // Allow hint weight to influence supposed distance + CNodeHintFilter filter( pNPC, vecOrigin ); +#else CNodeFilter filter( pNPC, vecOrigin ); +#endif #ifdef AI_PERF_MON m_nPerfStatNN++; @@ -605,8 +635,13 @@ CAI_Node *CAI_Network::AddNode( const Vector &origin, float yaw ) CAI_Link *CAI_Network::CreateLink( int srcID, int destID, CAI_DynamicLink *pDynamicLink ) { +#ifdef MAPBASE // From Alien Swarm SDK + CAI_Node *pSrcNode = GetNode( srcID ); + CAI_Node *pDestNode = GetNode( destID ); +#else CAI_Node *pSrcNode = g_pBigAINet->GetNode( srcID ); CAI_Node *pDestNode = g_pBigAINet->GetNode( destID ); +#endif Assert( pSrcNode && pDestNode && pSrcNode != pDestNode ); diff --git a/sp/src/game/server/ai_networkmanager.cpp b/sp/src/game/server/ai_networkmanager.cpp index 8f470a47fb..2477ec6744 100644 --- a/sp/src/game/server/ai_networkmanager.cpp +++ b/sp/src/game/server/ai_networkmanager.cpp @@ -71,6 +71,8 @@ CON_COMMAND( ai_debug_node_connect, "Debug the attempted connection between two ConVar g_ai_norebuildgraph( "ai_norebuildgraph", "0" ); #ifdef MAPBASE ConVar g_ai_norebuildgraphmessage( "ai_norebuildgraphmessage", "0", FCVAR_ARCHIVE, "Stops the \"Node graph out of date\" message from appearing when rebuilding node graph" ); + +ConVar g_ai_ignore_graph_timestamps( "g_ai_ignore_graph_timestamps", "1", FCVAR_NONE, "Ignores file timestamps when rebuilding nodegraphs, only relying on internal map version differences" ); #endif @@ -986,6 +988,11 @@ bool CAI_NetworkManager::IsAIFileCurrent ( const char *szMapName ) // dvd build process validates and guarantees correctness, timestamps are allowed to be wrong return true; } + +#ifdef MAPBASE + if (g_ai_ignore_graph_timestamps.GetBool()) + return true; +#endif { const char *pGameDir = CommandLine()->ParmValue( "-game", "hl2" ); @@ -3059,6 +3066,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 ) ) ); diff --git a/sp/src/game/server/ai_playerally.cpp b/sp/src/game/server/ai_playerally.cpp index 25673612ce..f7594febaf 100644 --- a/sp/src/game/server/ai_playerally.cpp +++ b/sp/src/game/server/ai_playerally.cpp @@ -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 { public: CConceptInfoMap() : +#ifdef NEW_RESPONSE_SYSTEM + CUtlMap( ConceptInfoStringLessFunc ) +#else CUtlMap( 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; } diff --git a/sp/src/game/server/ai_playerally.h b/sp/src/game/server/ai_playerally.h index dc9948ce96..93448ec790 100644 --- a/sp/src/game/server/ai_playerally.h +++ b/sp/src/game/server/ai_playerally.h @@ -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 }; //------------------------------------- diff --git a/sp/src/game/server/ai_speech.cpp b/sp/src/game/server/ai_speech.cpp index dafad41e4a..16b8e93272 100644 --- a/sp/src/game/server/ai_speech.cpp +++ b/sp/src/game/server/ai_speech.cpp @@ -1153,6 +1153,32 @@ void CAI_Expresser::ClearSpokeConcept( AIConcept_t concept ) m_ConceptHistories.Remove( concept ); } +#ifdef MAPBASE +//------------------------------------- + +AIConcept_t CAI_Expresser::GetLastSpokeConcept( AIConcept_t excludeConcept /* = NULL */ ) +{ + int iLastSpokenIndex = m_ConceptHistories.InvalidIndex(); + float flLast = 0.0f; + for ( int i = m_ConceptHistories.First(); i != m_ConceptHistories.InvalidIndex(); i = m_ConceptHistories.Next(i ) ) + { + ConceptHistory_t *h = &m_ConceptHistories[ i ]; + + // If an 'exclude concept' was provided, skip over this entry in the history if it matches the exclude concept + if ( excludeConcept != NULL && FStrEq( m_ConceptHistories.GetElementName( i ), excludeConcept ) ) + continue; + + if ( h->timeSpoken >= flLast ) + { + iLastSpokenIndex = i; + flLast = h->timeSpoken; + } + } + + return iLastSpokenIndex != m_ConceptHistories.InvalidIndex() ? m_ConceptHistories.GetElementName( iLastSpokenIndex ) : NULL; +} +#endif + //------------------------------------- void CAI_Expresser::DumpHistories() @@ -1240,7 +1266,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 diff --git a/sp/src/game/server/ai_speech.h b/sp/src/game/server/ai_speech.h index 5ed1225550..d91fee83f4 100644 --- a/sp/src/game/server/ai_speech.h +++ b/sp/src/game/server/ai_speech.h @@ -5,6 +5,9 @@ // $NoKeywords: $ //=============================================================================// +#ifdef NEW_RESPONSE_SYSTEM +#include "ai_speech_new.h" +#else #ifndef AI_SPEECH_H #define AI_SPEECH_H @@ -184,6 +187,10 @@ class CAI_Expresser : public IResponseFilter bool CanSpeakAfterMyself(); float GetTimeSpeechComplete() const { return m_flStopTalkTime; } void BlockSpeechUntil( float time ); + +#ifdef MAPBASE + float GetRealTimeSpeechComplete() const { return m_flStopTalkTimeWithoutDelay; } +#endif // -------------------------------- @@ -192,6 +199,10 @@ class CAI_Expresser : public IResponseFilter float GetTimeSpokeConcept( AIConcept_t concept ); // returns -1 if never void SetSpokeConcept( AIConcept_t concept, AI_Response *response, bool bCallback = true ); void ClearSpokeConcept( AIConcept_t concept ); + +#ifdef MAPBASE + AIConcept_t GetLastSpokeConcept( AIConcept_t excludeConcept = NULL ); +#endif // -------------------------------- @@ -452,3 +463,4 @@ inline void CAI_ExpresserHost::DispatchResponse( const char *conceptNa //----------------------------------------------------------------------------- #endif // AI_SPEECH_H +#endif diff --git a/sp/src/game/server/ai_speech_new.cpp b/sp/src/game/server/ai_speech_new.cpp new file mode 100644 index 0000000000..8d73911f6b --- /dev/null +++ b/sp/src/game/server/ai_speech_new.cpp @@ -0,0 +1,1765 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "ai_speech.h" + +#include "game.h" +#include "engine/IEngineSound.h" +#include "KeyValues.h" +#include "ai_basenpc.h" +#include "AI_Criteria.h" +#include "isaverestore.h" +#include "sceneentity.h" +#include "ai_speechqueue.h" +#ifdef MAPBASE +#include "ai_squad.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include + +#define DEBUG_AISPEECH 1 +#ifdef DEBUG_AISPEECH +ConVar ai_debug_speech( "ai_debug_speech", "0" ); +#define DebuggingSpeech() ai_debug_speech.GetBool() +#else +inline void SpeechMsg( ... ) {} +#define DebuggingSpeech() (false) +#endif + +extern ConVar rr_debugresponses; + +#ifdef MAPBASE +ConVar ai_speech_print_mode( "ai_speech_print_mode", "1", FCVAR_NONE, "Set this value to 1 to print responses as game_text instead of debug point_message-like text." ); +#endif + +//----------------------------------------------------------------------------- + +CAI_TimedSemaphore g_AIFriendliesTalkSemaphore; +CAI_TimedSemaphore g_AIFoesTalkSemaphore; + +ConceptHistory_t::~ConceptHistory_t() +{ +} + +ConceptHistory_t::ConceptHistory_t( const ConceptHistory_t& src ) +{ + timeSpoken = src.timeSpoken; + m_response = src.m_response ; +} + +ConceptHistory_t& ConceptHistory_t::operator =( const ConceptHistory_t& src ) +{ + if ( this == &src ) + return *this; + + timeSpoken = src.timeSpoken; + m_response = src.m_response ; + + return *this; +} + +BEGIN_SIMPLE_DATADESC( ConceptHistory_t ) + DEFINE_FIELD( timeSpoken, FIELD_TIME ), // Relative to server time + // DEFINE_EMBEDDED( response, FIELD_??? ), // This is manually saved/restored by the ConceptHistory saverestore ops below +END_DATADESC() + +class CConceptHistoriesDataOps : public CDefSaveRestoreOps +{ +public: + virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave ) + { + CUtlDict< ConceptHistory_t, int > *ch = ((CUtlDict< ConceptHistory_t, int > *)fieldInfo.pField); + int count = ch->Count(); + pSave->WriteInt( &count ); + for ( int i = 0 ; i < count; i++ ) + { + ConceptHistory_t *pHistory = &(*ch)[ i ]; + + pSave->StartBlock(); + { + + // Write element name + pSave->WriteString( ch->GetElementName( i ) ); + + // Write data + pSave->WriteAll( pHistory ); + // Write response blob + bool hasresponse = !pHistory->m_response.IsEmpty() ; + pSave->WriteBool( &hasresponse ); + if ( hasresponse ) + { + pSave->WriteAll( &pHistory->m_response ); + } + // TODO: Could blat out pHistory->criteria pointer here, if it's needed + } + pSave->EndBlock(); + } + } + + virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore ) + { + CUtlDict< ConceptHistory_t, int > *ch = ((CUtlDict< ConceptHistory_t, int > *)fieldInfo.pField); + + int count = pRestore->ReadInt(); + Assert( count >= 0 ); + for ( int i = 0 ; i < count; i++ ) + { + char conceptname[ 512 ]; + conceptname[ 0 ] = 0; + ConceptHistory_t history; + + pRestore->StartBlock(); + { + pRestore->ReadString( conceptname, sizeof( conceptname ), 0 ); + + pRestore->ReadAll( &history ); + + bool hasresponse = false; + + pRestore->ReadBool( &hasresponse ); + if ( hasresponse ) + { + history.m_response; + pRestore->ReadAll( &history.m_response ); + } + else + { + history.m_response.Invalidate(); + } + } + + pRestore->EndBlock(); + + // TODO: Could restore pHistory->criteria pointer here, if it's needed + + // Add to utldict + if ( conceptname[0] != 0 ) + { + ch->Insert( conceptname, history ); + } + else + { + Assert( !"Error restoring ConceptHistory_t, discarding!" ); + } + } + } + + virtual void MakeEmpty( const SaveRestoreFieldInfo_t &fieldInfo ) + { + } + + virtual bool IsEmpty( const SaveRestoreFieldInfo_t &fieldInfo ) + { + CUtlDict< ConceptHistory_t, int > *ch = ((CUtlDict< ConceptHistory_t, int > *)fieldInfo.pField); + return ch->Count() == 0 ? true : false; + } +}; + +CConceptHistoriesDataOps g_ConceptHistoriesSaveDataOps; + +///////////////////////////////////////////////// +// context operators +RR::CApplyContextOperator RR::sm_OpCopy(0); // " +RR::CIncrementOperator RR::sm_OpIncrement(2); // "++" +RR::CDecrementOperator RR::sm_OpDecrement(2); // "--" +#ifdef MAPBASE +RR::CMultiplyOperator RR::sm_OpMultiply(2); // "**" +RR::CDivideOperator RR::sm_OpDivide(2); // "/=" +#endif +RR::CToggleOperator RR::sm_OpToggle(1); // "!" + +#ifdef MAPBASE +// LEGACY - See below +RR::CIncrementOperator RR::sm_OpLegacyIncrement(1); // "+" +RR::CDecrementOperator RR::sm_OpLegacyDecrement(1); // "-" +RR::CMultiplyOperator RR::sm_OpLegacyMultiply(1); // "*" +RR::CDivideOperator RR::sm_OpLegacyDivide(1); // "/" +#endif + +RR::CApplyContextOperator *RR::CApplyContextOperator::FindOperator( const char *pContextString ) +{ + if ( !pContextString || pContextString[0] == 0 ) + { + return &sm_OpCopy; + } + +#ifdef MAPBASE + // This is one of those freak coincidences where Mapbase implemented its own context operators with no knowledge of context operators from later versions of the engine. + // Mapbase's context operators only required *one* operator character (e.g. '+' as opposed to '++') and supported multiplication and division. + // Although Valve's system now replaces Mapbase's system, multiplication and division have been added and maintaining the old syntax is needed for legacy support. + // That being said, it's believed that almost nobody knew that Mapbase supported context operators in the first place, so there might not be much legacy to support anyway. + switch (pContextString[0]) + { + case '+': + { + if (pContextString[1] != '+') + { + Warning( "\"%s\" needs another '+' to qualify as a proper operator. This code is regarding it as an operator anyway for legacy support, which might be going away soon!!!\n", pContextString ); + return &sm_OpLegacyIncrement; + } + else if (pContextString[2] == '\0') + break; + return &sm_OpIncrement; + } + case '-': + { + if (pContextString[1] != '-') + { + Warning( "\"%s\" needs another '-' to qualify as a proper operator. This code is regarding it as an operator anyway for legacy support, which might be going away soon!!!\n", pContextString ); + return &sm_OpLegacyDecrement; + } + else if (pContextString[2] == '\0') + break; + return &sm_OpIncrement; + } + case '*': + { + if (pContextString[1] != '*') + { + Warning( "\"%s\" needs another '*' to qualify as a proper operator. This code is regarding it as an operator anyway for legacy support, which might be going away soon!!!\n", pContextString ); + return &sm_OpLegacyMultiply; + } + else if (pContextString[2] == '\0') + break; + return &sm_OpMultiply; + } + case '/': + { + if (pContextString[1] != '=') + { + Warning( "\"%s\" needs a '=' after the '/' to qualify as a proper operator. This code is regarding it as an operator anyway for legacy support, which might be going away soon!!!\n", pContextString ); + return &sm_OpLegacyDivide; + } + else if (pContextString[2] == '\0') + break; + return &sm_OpDivide; + } break; + case '!': + { + return &sm_OpToggle; + } + } + + return &sm_OpCopy; +#else + if ( pContextString[0] == '+' && pContextString [1] == '+' && pContextString[2] != '\0' ) + { + return &sm_OpIncrement; + } + else if ( pContextString[0] == '-' && pContextString [1] == '-' && pContextString[2] != '\0' ) + { + return &sm_OpDecrement; + } +#ifdef MAPBASE + else if ( pContextString[0] == '*' && pContextString [1] == '*' && pContextString[2] != '\0' ) + { + return &sm_OpMultiply; + } + else if ( pContextString[0] == '/' && pContextString [1] == '=' && pContextString[2] != '\0' ) + { + return &sm_OpDivide; + } +#endif + else if ( pContextString[0] == '!' ) + { + return &sm_OpToggle; + } + else + { + return &sm_OpCopy; + } +#endif +} + +// default is just copy +bool RR::CApplyContextOperator::Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize ) +{ + Assert( pOperator && pNewValue && pNewValBufSize > 0 ); + Assert( m_nSkipChars == 0 ); + if ( pOperator ) + { + V_strncpy( pNewValue, pOperator, pNewValBufSize ); + } + else + { + *pNewValue = 0; + } + return true; +} + +bool RR::CIncrementOperator::Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize ) +{ + Assert( pOperator[0] == '+' && pOperator[1] == '+' ); + // parse out the old value as a numeric + int nOld = pOldValue ? V_atoi(pOldValue) : 0; + int nInc = V_atoi( pOperator+m_nSkipChars ); + V_snprintf( pNewValue, pNewValBufSize, "%d", nOld+nInc ); + return true; +} + +bool RR::CDecrementOperator::Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize ) +{ + Assert( pOperator[0] == '-' && pOperator[1] == '-' ); + // parse out the old value as a numeric + int nOld = pOldValue ? V_atoi(pOldValue) : 0; + int nInc = V_atoi( pOperator+m_nSkipChars ); + V_snprintf( pNewValue, pNewValBufSize, "%d", nOld-nInc ); + return true; +} + +#ifdef MAPBASE +bool RR::CMultiplyOperator::Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize ) +{ + Assert( pOperator[0] == '*' && pOperator[1] == '*' ); + // parse out the old value as a numeric + int nOld = pOldValue ? V_atoi(pOldValue) : 0; + int nInc = V_atoi( pOperator+m_nSkipChars ); + V_snprintf( pNewValue, pNewValBufSize, "%d", nOld*nInc ); + return true; +} + +bool RR::CDivideOperator::Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize ) +{ + Assert( pOperator[0] == '/' && pOperator[1] == '=' ); + // parse out the old value as a numeric + int nOld = pOldValue ? V_atoi(pOldValue) : 0; + int nInc = V_atoi( pOperator+m_nSkipChars ); + if (nInc == 0) + V_strncpy( pNewValue, "0", pNewValBufSize ); + else + V_snprintf( pNewValue, pNewValBufSize, "%d", nOld/nInc ); + return true; +} +#endif + +bool RR::CToggleOperator::Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize ) +{ + Assert( pOperator[0] == '!' ); + // parse out the old value as a numeric + int nOld = pOldValue ? V_atoi(pOldValue) : 0; + V_snprintf( pNewValue, pNewValBufSize, "%d", nOld ? 0 : 1 ); + return true; +} + + +//----------------------------------------------------------------------------- +// +// CLASS: CAI_Expresser +// + +BEGIN_SIMPLE_DATADESC( CAI_Expresser ) + // m_pSink (reconnected on load) +// DEFINE_FIELD( m_pOuter, CHandle < CBaseFlex > ), + DEFINE_CUSTOM_FIELD( m_ConceptHistories, &g_ConceptHistoriesSaveDataOps ), + DEFINE_FIELD( m_flStopTalkTime, FIELD_TIME ), + DEFINE_FIELD( m_flStopTalkTimeWithoutDelay, FIELD_TIME ), + DEFINE_FIELD( m_flBlockedTalkTime, FIELD_TIME ), + DEFINE_FIELD( m_voicePitch, FIELD_INTEGER ), + DEFINE_FIELD( m_flLastTimeAcceptedSpeak, FIELD_TIME ), +END_DATADESC() + +#ifdef MAPBASE_VSCRIPT +BEGIN_SCRIPTDESC_ROOT( CAI_Expresser, "Expresser class for complex speech." ) + + DEFINE_SCRIPTFUNC( IsSpeaking, "Check if the actor is speaking." ) + DEFINE_SCRIPTFUNC( CanSpeak, "Check if the actor can speak." ) + DEFINE_SCRIPTFUNC( BlockSpeechUntil, "Block speech for a certain amount of time. This is stored in curtime." ) + DEFINE_SCRIPTFUNC( ForceNotSpeaking, "If the actor is speaking, force the system to recognize them as not speaking." ) + + DEFINE_SCRIPTFUNC( GetVoicePitch, "Get the actor's voice pitch. Used in sentences." ) + DEFINE_SCRIPTFUNC( SetVoicePitch, "Set the actor's voice pitch. Used in sentences." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptSpeakRawScene, "SpeakRawScene", "Speak a raw, instanced VCD scene as though it were played through the Response System. Return whether the scene successfully plays." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSpeakAutoGeneratedScene, "SpeakAutoGeneratedScene", "Speak an automatically generated, instanced VCD scene for this sound as though it were played through the Response System. Return whether the scene successfully plays." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSpeakRawSentence, "SpeakRawSentence", "Speak a raw sentence as though it were played through the Response System. Return the sentence's index; -1 if not successfully played." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSpeak, "Speak", "Speak a response concept with the specified modifiers." ) + +END_SCRIPTDESC(); +#endif + +//------------------------------------- + +bool CAI_Expresser::SemaphoreIsAvailable( CBaseEntity *pTalker ) +{ + if ( !GetSink()->UseSemaphore() ) + return true; + + CAI_TimedSemaphore *pSemaphore = GetMySpeechSemaphore( pTalker->MyNPCPointer() ); + return (pSemaphore ? pSemaphore->IsAvailable( pTalker ) : true); +} + +//------------------------------------- + +float CAI_Expresser::GetSemaphoreAvailableTime( CBaseEntity *pTalker ) +{ + CAI_TimedSemaphore *pSemaphore = GetMySpeechSemaphore( pTalker->MyNPCPointer() ); + return (pSemaphore ? pSemaphore->GetReleaseTime() : 0); +} + +//------------------------------------- + +int CAI_Expresser::GetVoicePitch() const +{ + return m_voicePitch + random->RandomInt(0,3); +} + +#ifdef DEBUG +static int g_nExpressers; +#endif + +/* +inline bool ShouldBeInExpresserQueue( CBaseFlex *pOuter ) +{ + return true; // return IsTerrorPlayer( pOuter, TEAM_SURVIVOR ); +} +*/ + +CAI_Expresser::CAI_Expresser( CBaseFlex *pOuter ) + : m_pOuter( pOuter ), + m_pSink( NULL ), + m_flStopTalkTime( 0 ), + m_flBlockedTalkTime( 0 ), + m_flStopTalkTimeWithoutDelay( 0 ), + m_voicePitch( 100 ), + m_flLastTimeAcceptedSpeak( 0 ) +{ +#ifdef DEBUG + g_nExpressers++; +#endif + if (m_pOuter) + { + // register me with the global expresser queue. + + // L4D: something a little ass backwards is happening here. We only want + // survivors to be in the queue. However, the team number isn't + // specified yet. So, we actually need to do this in the player's ChangeTeam. + g_ResponseQueueManager.GetQueue()->AddExpresserHost(m_pOuter); + + } +} + +CAI_Expresser::~CAI_Expresser() +{ + m_ConceptHistories.Purge(); + + CBaseFlex *RESTRICT outer = GetOuter(); + if ( outer ) + { + CAI_TimedSemaphore *pSemaphore = GetMySpeechSemaphore( outer ); + if ( pSemaphore ) + { + if ( pSemaphore->GetOwner() == outer ) + pSemaphore->Release(); + +#ifdef DEBUG + g_nExpressers--; + if ( g_nExpressers == 0 && pSemaphore->GetOwner() ) + CGMsg( 2, CON_GROUP_SPEECH_AI, "Speech semaphore being held by non-talker entity\n" ); +#endif + } + + g_ResponseQueueManager.GetQueue()->RemoveExpresserHost(outer); + } +} + +//----------------------------------------------------------------------------- +void CAI_Expresser::TestAllResponses() +{ + IResponseSystem *pResponseSystem = GetOuter()->GetResponseSystem(); + if ( pResponseSystem ) + { + CUtlVector responses; + pResponseSystem->GetAllResponses( &responses ); + for ( int i = 0; i < responses.Count(); i++ ) + { + char response[ 256 ]; + responses[i].GetResponse( response, sizeof( response ) ); + + Msg( "Response: %s\n", response ); + AIConcept_t concept; + SpeakDispatchResponse( concept, &responses[i], NULL ); + } + } +} + +//----------------------------------------------------------------------------- +void CAI_Expresser::SetOuter( CBaseFlex *pOuter ) +{ + // if we're changing outers (which is a strange thing to do) + // unregister the old one from the queue. + if ( m_pOuter && ( m_pOuter != pOuter ) ) + { + AssertMsg2( false, "Expresser is switching its Outer from %s to %s. Why?", m_pOuter->GetDebugName(), pOuter->GetDebugName() ); + // unregister me with the global expresser queue + g_ResponseQueueManager.GetQueue()->RemoveExpresserHost(m_pOuter); + } + + m_pOuter = pOuter; +} + +//----------------------------------------------------------------------------- + +static const int LEN_SPECIFIC_SCENE_MODIFIER = strlen( AI_SPECIFIC_SCENE_MODIFIER ); + + +// This function appends "Global world" criteria that are always added to +// any character doing any match. This represents global concepts like weather, who's +// alive, etc. +static void ModifyOrAppendGlobalCriteria( AI_CriteriaSet * RESTRICT outputSet ) +{ + return; +} + + +void CAI_Expresser::GatherCriteria( AI_CriteriaSet * RESTRICT outputSet, const AIConcept_t &concept, const char * RESTRICT modifiers ) +{ + // Always include the concept name + outputSet->AppendCriteria( "concept", concept, CONCEPT_WEIGHT ); + +#if 1 + outputSet->Merge( modifiers ); +#else + // Always include any optional modifiers + if ( modifiers != NULL ) + { + char copy_modifiers[ 255 ]; + const char *pCopy; + char key[ 128 ] = { 0 }; + char value[ 128 ] = { 0 }; + + Q_strncpy( copy_modifiers, modifiers, sizeof( copy_modifiers ) ); + pCopy = copy_modifiers; + + while( pCopy ) + { + pCopy = SplitContext( pCopy, key, sizeof( key ), value, sizeof( value ), NULL, modifiers ); + + if( *key && *value ) + { + outputSet->AppendCriteria( key, value, CONCEPT_WEIGHT ); + } + } + } +#endif + + // include any global criteria + ModifyOrAppendGlobalCriteria( outputSet ); + + // Let our outer fill in most match criteria + GetOuter()->ModifyOrAppendCriteria( *outputSet ); + + // Append local player criteria to set, but not if this is a player doing the talking + if ( !GetOuter()->IsPlayer() ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 ); + if( pPlayer ) + pPlayer->ModifyOrAppendPlayerCriteria( *outputSet ); + } + +#ifdef MAPBASE + GetOuter()->ReAppendContextCriteria( *outputSet ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Searches for a possible response +// Input : concept - +// NULL - +// Output : AI_Response +//----------------------------------------------------------------------------- +// AI_Response *CAI_Expresser::SpeakFindResponse( AIConcept_t concept, const char *modifiers /*= NULL*/ ) +bool CAI_Expresser::FindResponse( AI_Response &outResponse, const AIConcept_t &concept, AI_CriteriaSet *criteria ) +{ + VPROF("CAI_Expresser::FindResponse"); + IResponseSystem *rs = GetOuter()->GetResponseSystem(); + if ( !rs ) + { + Assert( !"No response system installed for CAI_Expresser::GetOuter()!!!" ); + return NULL; + } + + // if I'm dead, I can't possibly match dialog. +#ifndef MAPBASE // Except for...you know...death sounds. + if ( !GetOuter()->IsAlive() ) + { + return false; + } +#endif + +#if 0 // this is the old technique, where we always gathered criteria in this function + AI_CriteriaSet set; + // Always include the concept name + set.AppendCriteria( "concept", concept, CONCEPT_WEIGHT ); + + // Always include any optional modifiers + if ( modifiers != NULL ) + { + char copy_modifiers[ 255 ]; + const char *pCopy; + char key[ 128 ] = { 0 }; + char value[ 128 ] = { 0 }; + + Q_strncpy( copy_modifiers, modifiers, sizeof( copy_modifiers ) ); + pCopy = copy_modifiers; + + while( pCopy ) + { + pCopy = SplitContext( pCopy, key, sizeof( key ), value, sizeof( value ), NULL, modifiers ); + + if( *key && *value ) + { + set.AppendCriteria( key, value, CONCEPT_WEIGHT ); + } + } + } + + // Let our outer fill in most match criteria + GetOuter()->ModifyOrAppendCriteria( set ); + + // Append local player criteria to set, but not if this is a player doing the talking + if ( !GetOuter()->IsPlayer() ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 ); + if( pPlayer ) + pPlayer->ModifyOrAppendPlayerCriteria( set ); + } +#else + AI_CriteriaSet localCriteriaSet; // put it on the stack so we don't deal with new/delete + if (criteria == NULL) + { + GatherCriteria( &localCriteriaSet, concept, NULL ); + criteria = &localCriteriaSet; + } +#endif + + /// intercept any deferred criteria that are being sent to world + AI_CriteriaSet worldWritebackCriteria; + AI_CriteriaSet::InterceptWorldSetContexts( criteria, &worldWritebackCriteria ); + + // Now that we have a criteria set, ask for a suitable response + bool found = rs->FindBestResponse( *criteria, outResponse, this ); + + if ( rr_debugresponses.GetInt() == 4 ) + { + if ( ( GetOuter()->MyNPCPointer() && GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT ) || GetOuter()->IsPlayer() ) + { + const char *pszName; + if ( GetOuter()->IsPlayer() ) + { + pszName = ((CBasePlayer*)GetOuter())->GetPlayerName(); + } + else + { + pszName = GetOuter()->GetDebugName(); + } + + if ( found ) + { + char response[ 256 ]; + outResponse.GetResponse( response, sizeof( response ) ); + + Warning( "RESPONSERULES: %s spoke '%s'. Found response '%s'.\n", pszName, (const char*)concept, response ); + } + else + { + Warning( "RESPONSERULES: %s spoke '%s'. Found no matching response.\n", pszName, (const char*)concept ); + } + } + } + + if ( !found ) + { + return false; + } + else if ( worldWritebackCriteria.GetCount() > 0 ) + { + Assert( CBaseEntity::Instance( INDEXENT( 0 ) )->IsWorld( ) ); + worldWritebackCriteria.WriteToEntity( CBaseEntity::Instance( INDEXENT( 0 ) ) ); + } + + if ( outResponse.IsEmpty() ) + { + // AssertMsg2( false, "RR: %s got empty but valid response for %s", GetOuter()->GetDebugName(), concept.GetStringConcept() ); + return false; + } + else + { + return true; + } +} + +#if 0 +//----------------------------------------------------------------------------- +// Purpose: Searches for a possible response; writes it into a response passed as +// parameter rather than new'ing one up. +// Input : concept - +// NULL - +// Output : bool : true on success, false on fail +//----------------------------------------------------------------------------- +AI_Response *CAI_Expresser::SpeakFindResponse( AI_Response *result, const AIConcept_t &concept, AI_CriteriaSet *criteria ) +{ + Assert(response); + + IResponseSystem *rs = GetOuter()->GetResponseSystem(); + if ( !rs ) + { + Assert( !"No response system installed for CAI_Expresser::GetOuter()!!!" ); + return NULL; + } + +#if 0 + AI_CriteriaSet set; + // Always include the concept name + set.AppendCriteria( "concept", concept, CONCEPT_WEIGHT ); + + // Always include any optional modifiers + if ( modifiers != NULL ) + { + char copy_modifiers[ 255 ]; + const char *pCopy; + char key[ 128 ] = { 0 }; + char value[ 128 ] = { 0 }; + + Q_strncpy( copy_modifiers, modifiers, sizeof( copy_modifiers ) ); + pCopy = copy_modifiers; + + while( pCopy ) + { + pCopy = SplitContext( pCopy, key, sizeof( key ), value, sizeof( value ), NULL, modifiers ); + + if( *key && *value ) + { + set.AppendCriteria( key, value, CONCEPT_WEIGHT ); + } + } + } + + // Let our outer fill in most match criteria + GetOuter()->ModifyOrAppendCriteria( set ); + + // Append local player criteria to set, but not if this is a player doing the talking + if ( !GetOuter()->IsPlayer() ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 ); + if( pPlayer ) + pPlayer->ModifyOrAppendPlayerCriteria( set ); + } +#else + AI_CriteriaSet &set = *criteria; +#endif + + // Now that we have a criteria set, ask for a suitable response + bool found = rs->FindBestResponse( set, *result, this ); + + if ( rr_debugresponses.GetInt() == 4 ) + { + if ( ( GetOuter()->MyNPCPointer() && GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT ) || GetOuter()->IsPlayer() ) + { + const char *pszName; + if ( GetOuter()->IsPlayer() ) + { + pszName = ((CBasePlayer*)GetOuter())->GetPlayerName(); + } + else + { + pszName = GetOuter()->GetDebugName(); + } + + if ( found ) + { + char response[ 256 ]; + result->GetResponse( response, sizeof( response ) ); + + Warning( "RESPONSERULES: %s spoke '%s'. Found response '%s'.\n", pszName, concept, response ); + } + else + { + Warning( "RESPONSERULES: %s spoke '%s'. Found no matching response.\n", pszName, concept ); + } + } + } + + if ( !found ) + { + //Assert( !"rs->FindBestResponse: Returned a NULL AI_Response!" ); + return false; + } + + char response[ 256 ]; + result->GetResponse( response, sizeof( response ) ); + + if ( !response[0] ) + { + return false; + } + + return true; +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: Dispatches the result +// Input : *response - +//----------------------------------------------------------------------------- +bool CAI_Expresser::SpeakDispatchResponse( AIConcept_t concept, AI_Response *result, AI_CriteriaSet *criteria, IRecipientFilter *filter /* = NULL */ ) +{ + char response[ 256 ]; + result->GetResponse( response, sizeof( response ) ); + + float delay = result->GetDelay(); + + bool spoke = false; + + soundlevel_t soundlevel = result->GetSoundLevel(); + + if ( IsSpeaking() && concept[0] != 0 && result->GetType() != ResponseRules::RESPONSE_PRINT ) + { + const char *entityName = STRING( GetOuter()->GetEntityName() ); + if ( GetOuter()->IsPlayer() ) + { + entityName = ToBasePlayer( GetOuter() )->GetPlayerName(); + } + CGMsg( 2, CON_GROUP_SPEECH_AI, "SpeakDispatchResponse: Entity ( %i/%s ) already speaking, forcing '%s'\n", GetOuter()->entindex(), entityName ? entityName : "UNKNOWN", (const char*)concept ); + + // Tracker 15911: Can break the game if we stop an imported map placed lcs here, so only + // cancel actor out of instanced scripted scenes. ywb + RemoveActorFromScriptedScenes( GetOuter(), true /*instanced scenes only*/ ); + GetOuter()->SentenceStop(); + + if ( IsRunningScriptedScene( GetOuter() ) ) + { + CGMsg( 1, CON_GROUP_SPEECH_AI, "SpeakDispatchResponse: Entity ( %i/%s ) refusing to speak due to scene entity, tossing '%s'\n", GetOuter()->entindex(), entityName ? entityName : "UNKNOWN", (const char*)concept ); + return false; + } + } + + switch ( result->GetType() ) + { + default: + case ResponseRules::RESPONSE_NONE: + break; + + case ResponseRules::RESPONSE_SPEAK: + { + if ( !result->ShouldntUseScene() ) + { + // This generates a fake CChoreoScene wrapping the sound.txt name + spoke = SpeakAutoGeneratedScene( response, delay ); + } + else + { + float speakTime = GetResponseDuration( result ); + GetOuter()->EmitSound( response ); + + CGMsg( 2, CON_GROUP_SPEECH_AI, "SpeakDispatchResponse: Entity ( %i/%s ) playing sound '%s'\n", GetOuter()->entindex(), STRING( GetOuter()->GetEntityName() ), response ); + NoteSpeaking( speakTime, delay ); + spoke = true; +#ifdef MAPBASE + // Not really any other way of doing this + OnSpeechFinished(); +#endif + } + } + break; + + case ResponseRules::RESPONSE_SENTENCE: + { + spoke = ( -1 != SpeakRawSentence( response, delay, VOL_NORM, soundlevel ) ) ? true : false; +#ifdef MAPBASE + // Not really any other way of doing this + OnSpeechFinished(); +#endif + } + break; + + case ResponseRules::RESPONSE_SCENE: + { + spoke = SpeakRawScene( response, delay, result, filter ); + } + break; + + case ResponseRules::RESPONSE_RESPONSE: + { + // This should have been recursively resolved already + Assert( 0 ); + } + break; + case ResponseRules::RESPONSE_PRINT: + { +#ifdef MAPBASE + // Note speaking for print responses + int responseLen = Q_strlen( response ); + float responseDuration = ((float)responseLen) * 0.1f; + NoteSpeaking( responseDuration, delay ); + + // game_text print responses + hudtextparms_t textParams; + textParams.holdTime = 4.0f + responseDuration; // Give extra padding for the text itself + 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 (ai_speech_print_mode.GetBool() && GetOuter()->GetGameTextSpeechParams( textParams )) + { + CRecipientFilter filter; + filter.AddAllPlayers(); + filter.MakeReliable(); + + UserMessageBegin( filter, "HudMsg" ); + WRITE_BYTE ( textParams.channel & 0xFF ); + WRITE_FLOAT( textParams.x ); + WRITE_FLOAT( textParams.y ); + WRITE_BYTE ( textParams.r1 ); + WRITE_BYTE ( textParams.g1 ); + WRITE_BYTE ( textParams.b1 ); + WRITE_BYTE ( textParams.a1 ); + WRITE_BYTE ( textParams.r2 ); + WRITE_BYTE ( textParams.g2 ); + WRITE_BYTE ( textParams.b2 ); + WRITE_BYTE ( textParams.a2 ); + WRITE_BYTE ( textParams.effect ); + WRITE_FLOAT( textParams.fadeinTime ); + WRITE_FLOAT( textParams.fadeoutTime ); + WRITE_FLOAT( textParams.holdTime ); + WRITE_FLOAT( textParams.fxTime ); + WRITE_STRING( response ); + WRITE_STRING( "" ); // No custom font + WRITE_BYTE ( responseLen ); + MessageEnd(); + + spoke = true; + + OnSpeechFinished(); + } + else +#endif + if ( g_pDeveloper->GetInt() > 0 ) + { + Vector vPrintPos; + GetOuter()->CollisionProp()->NormalizedToWorldSpace( Vector(0.5,0.5,1.0f), &vPrintPos ); + NDebugOverlay::Text( vPrintPos, response, true, 1.5 ); + } + spoke = true; +#ifdef MAPBASE + OnSpeechFinished(); +#endif + } + break; + case ResponseRules::RESPONSE_ENTITYIO: + { + spoke = FireEntIOFromResponse( response, GetOuter() ); +#ifdef MAPBASE + OnSpeechFinished(); +#endif + } + break; +#ifdef MAPBASE_VSCRIPT + case ResponseRules::RESPONSE_VSCRIPT: + { + spoke = RunScriptResponse( GetOuter(), response, criteria, false ); + OnSpeechFinished(); + } + break; + case ResponseRules::RESPONSE_VSCRIPT_FILE: + { + spoke = RunScriptResponse( GetOuter(), response, criteria, true ); + OnSpeechFinished(); + } + break; +#endif + } + + if ( spoke ) + { + m_flLastTimeAcceptedSpeak = gpGlobals->curtime; + if ( DebuggingSpeech() && g_pDeveloper->GetInt() > 0 && response && result->GetType() != ResponseRules::RESPONSE_PRINT ) + { + Vector vPrintPos; + GetOuter()->CollisionProp()->NormalizedToWorldSpace( Vector(0.5,0.5,1.0f), &vPrintPos ); + NDebugOverlay::Text( vPrintPos, CFmtStr( "%s: %s", (const char*)concept, response ), true, 1.5 ); + } + +#ifdef MAPBASE + if (result->GetContext()) + { + const char *pszContext = result->GetContext(); + + int iContextFlags = result->GetContextFlags(); + if ( iContextFlags & ResponseRules::APPLYCONTEXT_SQUAD ) + { + CAI_BaseNPC *pNPC = GetOuter()->MyNPCPointer(); + if (pNPC && pNPC->GetSquad()) + { + AISquadIter_t iter; + CAI_BaseNPC *pSquadmate = pNPC->GetSquad()->GetFirstMember( &iter ); + while ( pSquadmate ) + { + pSquadmate->AddContext( pszContext ); + + pSquadmate = pNPC->GetSquad()->GetNextMember( &iter ); + } + } + } + if ( iContextFlags & ResponseRules::APPLYCONTEXT_ENEMY ) + { + CBaseEntity *pEnemy = GetOuter()->GetEnemy(); + if ( pEnemy ) + { + pEnemy->AddContext( pszContext ); + } + } + if ( iContextFlags & ResponseRules::APPLYCONTEXT_WORLD ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( INDEXENT( 0 ) ); + if ( pEntity ) + { + pEntity->AddContext( pszContext ); + } + } + if ( iContextFlags == 0 || iContextFlags & ResponseRules::APPLYCONTEXT_SELF ) + { + GetOuter()->AddContext( pszContext ); + } + } +#else + if ( result->IsApplyContextToWorld() ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( INDEXENT( 0 ) ); + if ( pEntity ) + { + pEntity->AddContext( result->GetContext() ); + } + } + else + { + GetOuter()->AddContext( result->GetContext() ); + } +#endif + SetSpokeConcept( concept, result ); + } + else + { + } + + return spoke; +} + +bool CAI_Expresser::FireEntIOFromResponse( char *response, CBaseEntity *pInitiator ) +{ + // find the space-separator in the response name, then split into entityname, input, and parameter + // may barf in linux; there, should make some StringTokenizer() class that wraps the strtok_s behavior, etc. + char *pszEntname; + char *pszInput; + char *pszParam; + char *strtokContext; + + pszEntname = V_strtok_s( response, " ", &strtokContext ); + if ( !pszEntname ) + { + Warning( "Response was entityio but had bad value %s\n", response ); + return false; + } + + pszInput = V_strtok_s( NULL, " ", &strtokContext ); + if ( !pszInput ) + { + Warning( "Response was entityio but had bad value %s\n", response ); + return false; + } + + pszParam = V_strtok_s( NULL, " ", &strtokContext ); + + // poke entity io + CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, pszEntname, pInitiator ); + if ( !pTarget ) + { + CGMsg( 0, CON_GROUP_SPEECH_AI, "Response rule targeted %s with entityio, but that doesn't exist.\n", pszEntname ); + // but this is actually a legit use case, so return true (below). + } + else + { + // pump the action into the target + variant_t variant; + if ( pszParam ) + { + variant.SetString( MAKE_STRING(pszParam) ); + } + pTarget->AcceptInput( pszInput, pInitiator, pInitiator, variant, 0 ); + + } + return true; +} + +#ifdef MAPBASE_VSCRIPT +bool CAI_Expresser::RunScriptResponse( CBaseEntity *pTarget, const char *response, AI_CriteriaSet *criteria, bool file ) +{ + if (!pTarget->ValidateScriptScope()) + return false; + + ScriptVariant_t varCriteriaTable; + g_pScriptVM->CreateTable( varCriteriaTable ); + + if (criteria) + { + // Sort all of the criteria into a table. + // Letting VScript have access to this is important because not all criteria is appended in ModifyOrAppendCriteria() and + // not all contexts are actually appended as contexts. This is specifically important for followup responses. + int count = criteria->GetCount(); + for ( int i = 0 ; i < count ; ++i ) + { + // TODO: Weight? + g_pScriptVM->SetValue( varCriteriaTable, criteria->GetName(i), criteria->GetValue(i) ); + } + + g_pScriptVM->SetValue( "criteria", varCriteriaTable ); + } + + bool bSuccess = false; + if (file) + { + bSuccess = pTarget->RunScriptFile( response ); + } + else + { + bSuccess = pTarget->RunScript( response, "ResponseScript" ); + } + + g_pScriptVM->ClearValue( "criteria" ); + g_pScriptVM->ReleaseScript( varCriteriaTable ); + + return bSuccess; +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *response - +// Output : float +//----------------------------------------------------------------------------- +float CAI_Expresser::GetResponseDuration( AI_Response *result ) +{ + Assert( result ); + char response[ 256 ]; + result->GetResponse( response, sizeof( response ) ); + + switch ( result->GetType() ) + { + case ResponseRules::RESPONSE_SPEAK: + { + return GetOuter()->GetSoundDuration( response, STRING( GetOuter()->GetModelName() ) ); + } + break; + case ResponseRules::RESPONSE_SENTENCE: + { + Assert( 0 ); + return 999.0f; + } + break; + case ResponseRules::RESPONSE_SCENE: + { + return GetSceneDuration( response ); + } + break; + case ResponseRules::RESPONSE_RESPONSE: + { + // This should have been recursively resolved already + Assert( 0 ); + } + break; + case ResponseRules::RESPONSE_PRINT: + { + return 1.0; + } + break; + default: + case ResponseRules::RESPONSE_NONE: + case ResponseRules::RESPONSE_ENTITYIO: + return 0.0f; + } + + return 0.0f; +} + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_Expresser::SetUsingProspectiveResponses( bool bToggle ) +{ + VPROF("CAI_Expresser::SetUsingProspectiveResponses"); + IResponseSystem *rs = GetOuter()->GetResponseSystem(); + if ( !rs ) + { + Assert( !"No response system installed for CAI_Expresser::GetOuter()!!!" ); + return; + } + + rs->SetProspective( bToggle ); +} + +void CAI_Expresser::MarkResponseAsUsed( AI_Response *response ) +{ + VPROF("CAI_Expresser::MarkResponseAsUsed"); + IResponseSystem *rs = GetOuter()->GetResponseSystem(); + if ( !rs ) + { + Assert( !"No response system installed for CAI_Expresser::GetOuter()!!!" ); + return; + } + + rs->MarkResponseAsUsed( response->GetInternalIndices()[0], response->GetInternalIndices()[1] ); +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: Placeholder for rules based response system +// Input : concept - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAI_Expresser::Speak( AIConcept_t concept, const char *modifiers /*= NULL*/, char *pszOutResponseChosen /* = NULL*/, size_t bufsize /* = 0 */, IRecipientFilter *filter /* = NULL */ ) +{ + concept.SetSpeaker(GetOuter()); + AI_CriteriaSet criteria; + GatherCriteria(&criteria, concept, modifiers); + + return Speak( concept, &criteria, pszOutResponseChosen, bufsize, filter ); +} + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CAI_Expresser::Speak( const AIConcept_t &concept, AI_CriteriaSet * RESTRICT criteria, char *pszOutResponseChosen , size_t bufsize , IRecipientFilter *filter ) +{ + VPROF("CAI_Expresser::Speak"); + if ( IsSpeechGloballySuppressed() ) + { + return false; + } + + GetOuter()->ModifyOrAppendDerivedCriteria(*criteria); + AI_Response result; + if ( !FindResponse( result, concept, criteria ) ) + { + return false; + } + + 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; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CAI_Expresser::SpeakRawScene( const char *pszScene, float delay, AI_Response *response, IRecipientFilter *filter /* = NULL */ ) +{ + float sceneLength = GetOuter()->PlayScene( pszScene, delay, response, filter ); + if ( sceneLength > 0 ) + { + SpeechMsg( GetOuter(), "SpeakRawScene( %s, %f) %f\n", pszScene, delay, sceneLength ); + +#if defined( HL2_EPISODIC ) + char szInstanceFilename[256]; + GetOuter()->GenderExpandString( pszScene, szInstanceFilename, sizeof( szInstanceFilename ) ); + // Only mark ourselves as speaking if the scene has speech + if ( GetSceneSpeechCount(szInstanceFilename) > 0 ) + { + NoteSpeaking( sceneLength, delay ); + } +#else + NoteSpeaking( sceneLength, delay ); +#endif + + return true; + } + return false; +} + +// This will create a fake .vcd/CChoreoScene to wrap the sound to be played +bool CAI_Expresser::SpeakAutoGeneratedScene( char const *soundname, float delay ) +{ + float speakTime = GetOuter()->PlayAutoGeneratedSoundScene( soundname ); + if ( speakTime > 0 ) + { + SpeechMsg( GetOuter(), "SpeakAutoGeneratedScene( %s, %f) %f\n", soundname, delay, speakTime ); + NoteSpeaking( speakTime, delay ); + return true; + } + return false; +} + +//------------------------------------- + +int CAI_Expresser::SpeakRawSentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, CBaseEntity *pListener ) +{ + int sentenceIndex = -1; + + if ( !pszSentence ) + return sentenceIndex; + + if ( pszSentence[0] == AI_SP_SPECIFIC_SENTENCE ) + { + sentenceIndex = SENTENCEG_Lookup( pszSentence ); + + if( sentenceIndex == -1 ) + { + // sentence not found + return -1; + } + + CPASAttenuationFilter filter( GetOuter(), soundlevel ); + CBaseEntity::EmitSentenceByIndex( filter, GetOuter()->entindex(), CHAN_VOICE, sentenceIndex, volume, soundlevel, 0, GetVoicePitch()); + } + else + { + sentenceIndex = SENTENCEG_PlayRndSz( GetOuter()->NetworkProp()->edict(), pszSentence, volume, soundlevel, 0, GetVoicePitch() ); + } + + SpeechMsg( GetOuter(), "SpeakRawSentence( %s, %f) %f\n", pszSentence, delay, engine->SentenceLength( sentenceIndex ) ); + NoteSpeaking( engine->SentenceLength( sentenceIndex ), delay ); + + return sentenceIndex; +} + +//------------------------------------- + +void CAI_Expresser::BlockSpeechUntil( float time ) +{ + SpeechMsg( GetOuter(), "BlockSpeechUntil(%f) %f\n", time, time - gpGlobals->curtime ); + m_flBlockedTalkTime = time; +} + + +//------------------------------------- + +void CAI_Expresser::NoteSpeaking( float duration, float delay ) +{ + duration += delay; + + GetSink()->OnStartSpeaking(); + + if ( duration <= 0 ) + { + // no duration :( + m_flStopTalkTime = gpGlobals->curtime + 3; + duration = 0; + } + else + { + m_flStopTalkTime = gpGlobals->curtime + duration; + } + + m_flStopTalkTimeWithoutDelay = m_flStopTalkTime - delay; + + SpeechMsg( GetOuter(), "NoteSpeaking( %f, %f ) (stop at %f)\n", duration, delay, m_flStopTalkTime ); + + if ( GetSink()->UseSemaphore() ) + { + CAI_TimedSemaphore *pSemaphore = GetMySpeechSemaphore( GetOuter() ); + if ( pSemaphore ) + { + pSemaphore->Acquire( duration, GetOuter() ); + } + } +} + +//------------------------------------- + +void CAI_Expresser::ForceNotSpeaking( void ) +{ + if ( IsSpeaking() ) + { + m_flStopTalkTime = gpGlobals->curtime; + m_flStopTalkTimeWithoutDelay = gpGlobals->curtime; + + CAI_TimedSemaphore *pSemaphore = GetMySpeechSemaphore( GetOuter() ); + if ( pSemaphore ) + { + if ( pSemaphore->GetOwner() == GetOuter() ) + { + pSemaphore->Release(); + } + } + } +} + +//------------------------------------- + +bool CAI_Expresser::IsSpeaking( void ) +{ + if ( m_flStopTalkTime > gpGlobals->curtime ) + SpeechMsg( GetOuter(), "IsSpeaking() %f\n", m_flStopTalkTime - gpGlobals->curtime ); + + if ( m_flLastTimeAcceptedSpeak == gpGlobals->curtime ) // only one speak accepted per think + return true; + + return ( m_flStopTalkTime > gpGlobals->curtime ); +} + +//------------------------------------- + +bool CAI_Expresser::CanSpeak() +{ + if ( m_flLastTimeAcceptedSpeak == gpGlobals->curtime ) // only one speak accepted per think + return false; + + float timeOk = MAX( m_flStopTalkTime, m_flBlockedTalkTime ); + return ( timeOk <= gpGlobals->curtime ); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if it's ok for this entity to speak after himself. +// The base CanSpeak() includes the default speech delay, and won't +// return true until that delay time has passed after finishing the +// speech. This returns true as soon as the speech finishes. +//----------------------------------------------------------------------------- +bool CAI_Expresser::CanSpeakAfterMyself() +{ + if ( m_flLastTimeAcceptedSpeak == gpGlobals->curtime ) // only one speak accepted per think + return false; + + float timeOk = MAX( m_flStopTalkTimeWithoutDelay, m_flBlockedTalkTime ); + return ( timeOk <= gpGlobals->curtime ); +} + +//------------------------------------- +bool CAI_Expresser::CanSpeakConcept( const AIConcept_t &concept ) +{ + // Not in history? + int iter = m_ConceptHistories.Find( concept ); + if ( iter == m_ConceptHistories.InvalidIndex() ) + { + return true; + } + + ConceptHistory_t *history = &m_ConceptHistories[iter]; + Assert( history ); + + const AI_Response &response = history->m_response; + if ( response.IsEmpty() ) + return true; + + if ( response.GetSpeakOnce() ) + return false; + + float respeakDelay = response.GetRespeakDelay(); + + if ( respeakDelay != 0.0f ) + { + if ( history->timeSpoken != -1 && ( gpGlobals->curtime < history->timeSpoken + respeakDelay ) ) + return false; + } + + return true; +} + +//------------------------------------- + +bool CAI_Expresser::SpokeConcept( const AIConcept_t &concept ) +{ + return GetTimeSpokeConcept( concept ) != -1.f; +} + +//------------------------------------- + +float CAI_Expresser::GetTimeSpokeConcept( const AIConcept_t &concept ) +{ + int iter = m_ConceptHistories.Find( concept ); + if ( iter == m_ConceptHistories.InvalidIndex() ) + return -1; + + ConceptHistory_t *h = &m_ConceptHistories[iter]; + return h->timeSpoken; +} + +//------------------------------------- + +void CAI_Expresser::SetSpokeConcept( const AIConcept_t &concept, AI_Response *response, bool bCallback ) +{ + int idx = m_ConceptHistories.Find( concept ); + if ( idx == m_ConceptHistories.InvalidIndex() ) + { + ConceptHistory_t h; + h.timeSpoken = gpGlobals->curtime; + idx = m_ConceptHistories.Insert( concept, h ); + } + + ConceptHistory_t *slot = &m_ConceptHistories[ idx ]; + + slot->timeSpoken = gpGlobals->curtime; + // Update response info + if ( response ) + { + slot->m_response = *response; + } + + if ( bCallback ) + GetSink()->OnSpokeConcept( concept, response ); +} + +//------------------------------------- + +void CAI_Expresser::ClearSpokeConcept( const AIConcept_t &concept ) +{ + m_ConceptHistories.Remove( concept ); +} + +#ifdef MAPBASE +//------------------------------------- + +AIConcept_t CAI_Expresser::GetLastSpokeConcept( AIConcept_t excludeConcept /* = NULL */ ) +{ + int iLastSpokenIndex = m_ConceptHistories.InvalidIndex(); + float flLast = 0.0f; + for ( int i = m_ConceptHistories.First(); i != m_ConceptHistories.InvalidIndex(); i = m_ConceptHistories.Next(i ) ) + { + ConceptHistory_t *h = &m_ConceptHistories[ i ]; + + // If an 'exclude concept' was provided, skip over this entry in the history if it matches the exclude concept + if ( excludeConcept != NULL && FStrEq( m_ConceptHistories.GetElementName( i ), excludeConcept ) ) + continue; + + if ( h->timeSpoken >= flLast ) + { + iLastSpokenIndex = i; + flLast = h->timeSpoken; + } + } + + return iLastSpokenIndex != m_ConceptHistories.InvalidIndex() ? m_ConceptHistories.GetElementName( iLastSpokenIndex ) : NULL; +} +#endif + +//------------------------------------- + +void CAI_Expresser::DumpHistories() +{ + int c = 1; + for ( int i = m_ConceptHistories.First(); i != m_ConceptHistories.InvalidIndex(); i = m_ConceptHistories.Next(i ) ) + { + ConceptHistory_t *h = &m_ConceptHistories[ i ]; + + CGMsg( 1, CON_GROUP_SPEECH_AI, "%i: %s at %f\n", c++, m_ConceptHistories.GetElementName( i ), h->timeSpoken ); + } +} + +//------------------------------------- + +bool CAI_Expresser::IsValidResponse( ResponseType_t type, const char *pszValue ) +{ + if ( type == ResponseRules::RESPONSE_SCENE ) + { + char szInstanceFilename[256]; + GetOuter()->GenderExpandString( pszValue, szInstanceFilename, sizeof( szInstanceFilename ) ); + return ( GetSceneDuration( szInstanceFilename ) > 0 ); + } + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CAI_TimedSemaphore *CAI_Expresser::GetMySpeechSemaphore( CBaseEntity *pNpc ) +{ + if ( !pNpc->MyNPCPointer() ) + return NULL; + + return (pNpc->MyNPCPointer()->IsPlayerAlly() ? &g_AIFriendliesTalkSemaphore : &g_AIFoesTalkSemaphore ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_Expresser::SpeechMsg( CBaseEntity *pFlex, const char *pszFormat, ... ) +{ + if ( !DebuggingSpeech() ) + return; + + va_list arg_ptr; + + va_start(arg_ptr, pszFormat); + CFmtStr formatted; + formatted.sprintf_argv(pszFormat, arg_ptr); + va_end(arg_ptr); + + if ( pFlex->MyNPCPointer() ) + { + + DevMsg( pFlex->MyNPCPointer(), "%s", formatted.Get() ); + } + else + { + CGMsg( 1, CON_GROUP_SPEECH_AI, "%s", formatted.Get() ); + } + UTIL_LogPrintf( "%s", formatted.Get() ); +} + +//----------------------------------------------------------------------------- +// Purpose: returns true when l4d is in credits screen or some other +// speech-forbidden state +//----------------------------------------------------------------------------- +bool CAI_Expresser::IsSpeechGloballySuppressed() +{ + return false; +} + +//----------------------------------------------------------------------------- + +void CAI_ExpresserHost_NPC_DoModifyOrAppendCriteria( CAI_BaseNPC *pSpeaker, AI_CriteriaSet& set ) +{ + // Append current activity name + const char *pActivityName = pSpeaker->GetActivityName( pSpeaker->GetActivity() ); + if ( pActivityName ) + { + set.AppendCriteria( "activity", pActivityName ); + } + + static const char *pStateNames[] = { "None", "Idle", "Alert", "Combat", "Scripted", "PlayDead", "Dead" }; + if ( (int)pSpeaker->m_NPCState < ARRAYSIZE(pStateNames) ) + { + set.AppendCriteria( "npcstate", UTIL_VarArgs( "[NPCState::%s]", pStateNames[pSpeaker->m_NPCState] ) ); + } + + if ( pSpeaker->GetEnemy() ) + { + set.AppendCriteria( "enemy", pSpeaker->GetEnemy()->GetClassname() ); + set.AppendCriteria( "timesincecombat", "-1" ); + } + else + { + if ( pSpeaker->GetLastEnemyTime() == 0.0 ) + set.AppendCriteria( "timesincecombat", "999999.0" ); + else + set.AppendCriteria( "timesincecombat", UTIL_VarArgs( "%f", gpGlobals->curtime - pSpeaker->GetLastEnemyTime() ) ); + } + + set.AppendCriteria( "speed", UTIL_VarArgs( "%.3f", pSpeaker->GetSmoothedVelocity().Length() ) ); + + CBaseCombatWeapon *weapon = pSpeaker->GetActiveWeapon(); + if ( weapon ) + { + set.AppendCriteria( "weapon", weapon->GetClassname() ); + } + else + { + set.AppendCriteria( "weapon", "none" ); + } + + CBasePlayer *pPlayer = AI_GetSinglePlayer(); + if ( pPlayer ) + { + Vector distance = pPlayer->GetAbsOrigin() - pSpeaker->GetAbsOrigin(); + + set.AppendCriteria( "distancetoplayer", UTIL_VarArgs( "%f", distance.Length() ) ); + + } + else + { + set.AppendCriteria( "distancetoplayer", UTIL_VarArgs( "%i", MAX_COORD_RANGE ) ); + } + + if ( pSpeaker->HasCondition( COND_SEE_PLAYER ) ) + { + set.AppendCriteria( "seeplayer", "1" ); + } + else + { + set.AppendCriteria( "seeplayer", "0" ); + } + + if ( pPlayer && pPlayer->FInViewCone( pSpeaker ) && pPlayer->FVisible( pSpeaker ) ) + { + set.AppendCriteria( "seenbyplayer", "1" ); + } + else + { + set.AppendCriteria( "seenbyplayer", "0" ); + } +} + +//----------------------------------------------------------------------------- + +extern CBaseEntity *FindPickerEntity( CBasePlayer *pPlayer ); + +CON_COMMAND( npc_speakall, "Force the npc to try and speak all their responses" ) +{ + CBaseEntity *pEntity; + + if ( args[1] && *args[1] ) + { + pEntity = gEntList.FindEntityByName( NULL, args[1], NULL ); + if ( !pEntity ) + { + pEntity = gEntList.FindEntityByClassname( NULL, args[1] ); + } + } + else + { + pEntity = UTIL_GetCommandClient() ? FindPickerEntity( UTIL_GetCommandClient() ) : NULL; + } + + if ( pEntity ) + { + CAI_BaseNPC *pNPC = pEntity->MyNPCPointer(); + if (pNPC) + { + if ( pNPC->GetExpresser() ) + { + bool save = engine->LockNetworkStringTables( false ); + pNPC->GetExpresser()->TestAllResponses(); + engine->LockNetworkStringTables( save ); + } + } + } +} +//----------------------------------------------------------------------------- + +CMultiplayer_Expresser::CMultiplayer_Expresser( CBaseFlex *pOuter ) : CAI_ExpresserWithFollowup( pOuter ) +{ + m_bAllowMultipleScenes = false; +} + +bool CMultiplayer_Expresser::IsSpeaking( void ) +{ + if ( m_bAllowMultipleScenes ) + { + return false; + } + + return CAI_Expresser::IsSpeaking(); +} + + +void CMultiplayer_Expresser::AllowMultipleScenes() +{ + m_bAllowMultipleScenes = true; +} + +void CMultiplayer_Expresser::DisallowMultipleScenes() +{ + m_bAllowMultipleScenes = false; +} diff --git a/sp/src/game/server/ai_speech_new.h b/sp/src/game/server/ai_speech_new.h new file mode 100644 index 0000000000..70e73354ee --- /dev/null +++ b/sp/src/game/server/ai_speech_new.h @@ -0,0 +1,709 @@ +//========= 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 ); + +#ifdef MAPBASE + AIConcept_t GetLastSpokeConcept( AIConcept_t excludeConcept = NULL ); +#endif + + // -------------------------------- + + 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 m_pOuter; +}; + +//----------------------------------------------------------------------------- +// +// An NPC base class to assist a branch of the inheritance graph +// in utilizing CAI_Expresser +// + +template +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 +inline void CAI_ExpresserHost::NoteSpeaking( float duration, float delay ) +{ + this->GetExpresser()->NoteSpeaking( duration, delay ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline bool CAI_ExpresserHost::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 +inline bool CAI_ExpresserHost::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 +inline int CAI_ExpresserHost::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 +inline void CAI_ExpresserHost::ModifyOrAppendCriteria( AI_CriteriaSet& criteriaSet ) +{ + BaseClass::ModifyOrAppendCriteria( criteriaSet ); + + + if ( this->MyNPCPointer() ) + { + CAI_ExpresserHost_NPC_DoModifyOrAppendCriteria( this->MyNPCPointer(), criteriaSet ); + } + +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline IResponseSystem *CAI_ExpresserHost::GetResponseSystem() +{ + extern IResponseSystem *g_pResponseSystem; + // Expressive NPC's use the general response system + return g_pResponseSystem; +} + + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline void CAI_ExpresserHost::GatherCriteria( AI_CriteriaSet *outputCriteria, const AIConcept_t &concept, const char *modifiers ) +{ + return this->GetExpresser()->GatherCriteria( outputCriteria, concept, modifiers ); +} + + +#if 1 +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline bool CAI_ExpresserHost::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 +inline AI_Response *CAI_ExpresserHost::SpeakFindResponse( const AIConcept_t &concept, const char *modifiers /*= NULL*/ ) +{ + return this->GetExpresser()->SpeakFindResponse( concept, modifiers ); +} +#endif + +#if 0 +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline AI_Response *CAI_ExpresserHost::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 +inline AI_Response * CAI_ExpresserHost::SpeakFindResponse( const AIConcept_t &concept ) +{ + AI_CriteriaSet criteria; + GatherCriteria( &criteria, concept, NULL ); + return this->GetExpresser()->SpeakFindResponse( concept, &criteria ); +} +#endif + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline bool CAI_ExpresserHost::FindResponse( AI_Response &outResponse, const AIConcept_t &concept, AI_CriteriaSet *criteria ) +{ + return this->GetExpresser()->FindResponse( outResponse, concept, criteria ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline bool CAI_ExpresserHost::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 +inline float CAI_ExpresserHost::GetResponseDuration( AI_Response *response ) +{ + return this->GetExpresser()->GetResponseDuration( response ); +} + +//----------------------------------------------------------------------------- +// Override of base entity response input handler +//----------------------------------------------------------------------------- +template +inline void CAI_ExpresserHost::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 CAI_ExpresserHostWithData : public CAI_ExpresserHost +{ + DECLARE_CLASS_NOFRIEND( CAI_ExpresserHostWithData, CAI_ExpresserHost ); + +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 diff --git a/sp/src/game/server/ai_speechqueue.cpp b/sp/src/game/server/ai_speechqueue.cpp new file mode 100644 index 0000000000..7e8bf05548 --- /dev/null +++ b/sp/src/game/server/ai_speechqueue.cpp @@ -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 + +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(pEnt) ) +#endif + { + return pPlayer->GetExpresser(); + } + else if ( CAI_BaseActor *pActor = dynamic_cast(pEnt) ) + { + return pActor->GetExpresser(); + } + /* + else if ( CFlexExpresser *pFlex = dynamic_cast(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" ); + diff --git a/sp/src/game/server/ai_speechqueue.h b/sp/src/game/server/ai_speechqueue.h new file mode 100644 index 0000000000..15101b70b3 --- /dev/null +++ b/sp/src/game/server/ai_speechqueue.h @@ -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 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 diff --git a/sp/src/game/server/ai_tacticalservices.cpp b/sp/src/game/server/ai_tacticalservices.cpp index 87399c4f22..8bcdcc0826 100644 --- a/sp/src/game/server/ai_tacticalservices.cpp +++ b/sp/src/game/server/ai_tacticalservices.cpp @@ -411,7 +411,12 @@ int CAI_TacticalServices::FindCoverNode(const Vector &vNearPos, const Vector &vT // -------------------------------------------------------- pNode->Lock( 1.0 ); +#ifdef MAPBASE + if ( pNode->GetHint() && ( pNode->GetHint()->HintType() == HINT_TACTICAL_COVER_MED || pNode->GetHint()->HintType() == HINT_TACTICAL_COVER_LOW + || pNode->GetHint()->HintType() == HINT_TACTICAL_COVER_CUSTOM ) ) +#else if ( pNode->GetHint() && ( pNode->GetHint()->HintType() == HINT_TACTICAL_COVER_MED || pNode->GetHint()->HintType() == HINT_TACTICAL_COVER_LOW ) ) +#endif { if ( GetOuter()->GetHintNode() ) { diff --git a/sp/src/game/server/baseanimating.cpp b/sp/src/game/server/baseanimating.cpp index 788edc2779..1db0693a8d 100644 --- a/sp/src/game/server/baseanimating.cpp +++ b/sp/src/game/server/baseanimating.cpp @@ -32,6 +32,9 @@ #include "gib.h" #include "CRagdollMagnet.h" #endif +#ifdef MAPBASE_VSCRIPT +#include "mapbase/vscript_funcs_shared.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -506,6 +509,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); @@ -1291,7 +1299,7 @@ bool CBaseAnimating::ScriptHookHandleAnimEvent( animevent_t *pEvent ) { if (m_ScriptScope.IsInitialized() && g_Hook_HandleAnimEvent.CanRunInScope(m_ScriptScope)) { - HSCRIPT hEvent = g_pScriptVM->RegisterInstance( pEvent ); + HSCRIPT hEvent = g_pScriptVM->RegisterInstance( reinterpret_cast(pEvent) ); // event ScriptVariant_t args[] = { hEvent }; @@ -1940,7 +1948,7 @@ ConVar ai_setupbones_debug( "ai_setupbones_debug", "0", 0, "Shows that bones tha -inline bool CBaseAnimating::CanSkipAnimation( void ) +bool CBaseAnimating::CanSkipAnimation( void ) { if ( !sv_pvsskipanimation.GetBool() ) return false; diff --git a/sp/src/game/server/baseanimating.h b/sp/src/game/server/baseanimating.h index 8f0e0aa82e..4fb3c47c3b 100644 --- a/sp/src/game/server/baseanimating.h +++ b/sp/src/game/server/baseanimating.h @@ -84,6 +84,7 @@ class CBaseAnimating : public CBaseEntity 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 ); @@ -387,6 +388,8 @@ class CBaseAnimating : public CBaseEntity void InputSetCycle( inputdata_t &inputdata ); void InputSetPlaybackRate( inputdata_t &inputdata ); + +public: // From Alien Swarm SDK #endif bool CanSkipAnimation( void ); diff --git a/sp/src/game/server/basebludgeonweapon.cpp b/sp/src/game/server/basebludgeonweapon.cpp index cb9c1fc8ff..57683a19a0 100644 --- a/sp/src/game/server/basebludgeonweapon.cpp +++ b/sp/src/game/server/basebludgeonweapon.cpp @@ -406,4 +406,8 @@ void CBaseHLBludgeonWeapon::Swing( int bIsSecondary ) //Play swing sound WeaponSound( SINGLE ); #endif + +#ifdef MAPBASE + pOwner->SetAnimation( PLAYER_ATTACK1 ); +#endif } diff --git a/sp/src/game/server/basecombatcharacter.cpp b/sp/src/game/server/basecombatcharacter.cpp index b740e3f0bb..f4b3539204 100644 --- a/sp/src/game/server/basecombatcharacter.cpp +++ b/sp/src/game/server/basecombatcharacter.cpp @@ -61,11 +61,6 @@ extern int g_interactionBarnacleVictimReleased; #endif //HL2_DLL -#ifdef MAPBASE -extern acttable_t *GetSMG1Acttable(); -extern int GetSMG1ActtableCount(); -#endif - extern ConVar weapon_showproficiency; ConVar ai_show_hull_attacks( "ai_show_hull_attacks", "0" ); @@ -151,6 +146,9 @@ BEGIN_DATADESC( CBaseCombatCharacter ) END_DATADESC() #ifdef MAPBASE_VSCRIPT +ScriptHook_t CBaseCombatCharacter::g_Hook_RelationshipType; +ScriptHook_t CBaseCombatCharacter::g_Hook_RelationshipPriority; + BEGIN_ENT_SCRIPTDESC( CBaseCombatCharacter, CBaseFlex, "The base class shared by players and NPCs." ) DEFINE_SCRIPTFUNC_NAMED( GetScriptActiveWeapon, "GetActiveWeapon", "Get the character's active weapon entity." ) @@ -165,6 +163,8 @@ BEGIN_ENT_SCRIPTDESC( CBaseCombatCharacter, CBaseFlex, "The base class shared by DEFINE_SCRIPTFUNC_NAMED( ScriptEquipWeapon, "EquipWeapon", "Make the character equip the specified weapon entity. If they don't already own the weapon, they will acquire it instantly." ) DEFINE_SCRIPTFUNC_NAMED( ScriptDropWeapon, "DropWeapon", "Make the character drop the specified weapon entity if they own it." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGiveAmmo, "GiveAmmo", "Gives the specified amount of the specified ammo type. The third parameter is whether or not to suppress the ammo pickup sound. Returns the amount of ammo actually given, which is 0 if the player's ammo for this type is already full." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptRemoveAmmo, "RemoveAmmo", "Removes the specified amount of the specified ammo type." ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetAmmoCount, "GetAmmoCount", "Get the ammo count of the specified ammo type." ) DEFINE_SCRIPTFUNC_NAMED( ScriptSetAmmoCount, "SetAmmoCount", "Set the ammo count of the specified ammo type." ) @@ -192,6 +192,19 @@ BEGIN_ENT_SCRIPTDESC( CBaseCombatCharacter, CBaseFlex, "The base class shared by DEFINE_SCRIPTFUNC( EyeDirection2D, "Get the eyes' 2D direction." ) DEFINE_SCRIPTFUNC( EyeDirection3D, "Get the eyes' 3D direction." ) + // + // Hooks + // + BEGIN_SCRIPTHOOK( CBaseCombatCharacter::g_Hook_RelationshipType, "RelationshipType", FIELD_INTEGER, "Called when a character's relationship to another entity is requested. Returning a disposition will make the game use that disposition instead of the default relationship. (note: 'default' in this case includes overrides from ai_relationship/SetRelationship)" ) + DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT ) + DEFINE_SCRIPTHOOK_PARAM( "def", FIELD_INTEGER ) + END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( CBaseCombatCharacter::g_Hook_RelationshipPriority, "RelationshipPriority", FIELD_INTEGER, "Called when a character's relationship priority for another entity is requested. Returning a number will make the game use that priority instead of the default priority. (note: 'default' in this case includes overrides from ai_relationship/SetRelationship)" ) + DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT ) + DEFINE_SCRIPTHOOK_PARAM( "def", FIELD_INTEGER ) + END_SCRIPTHOOK() + END_SCRIPTDESC(); #endif @@ -1725,25 +1738,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(&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 @@ -1791,7 +1785,14 @@ void CBaseCombatCharacter::Event_Killed( const CTakeDamageInfo &info ) // if flagged to drop a health kit if (HasSpawnFlags(SF_NPC_DROP_HEALTHKIT)) { - CBaseEntity::Create( "item_healthvial", GetAbsOrigin(), GetAbsAngles() ); + CBaseEntity *pItem = CBaseEntity::Create( "item_healthvial", GetAbsOrigin(), GetAbsAngles() ); + if (pItem) + { +#ifdef MAPBASE + if (MyNPCPointer()) + MyNPCPointer()->m_OnItemDrop.Set( pItem, pItem, this ); +#endif + } } // clear the deceased's sound channels.(may have been firing or reloading when killed) EmitSound( "BaseCombatCharacter.StopWeaponSounds" ); @@ -2726,10 +2727,38 @@ bool CBaseCombatCharacter::Weapon_CanUse( CBaseCombatWeapon *pWeapon ) } #ifdef MAPBASE + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +static Activity Weapon_BackupActivityFromList( CBaseCombatCharacter *pBCC, acttable_t *pTable, int actCount, Activity activity, bool weaponTranslationWasRequired, CBaseCombatWeapon *pWeapon ) +{ + int i = 0; + for ( ; i < actCount; i++, pTable++ ) + { + if ( activity == pTable->baseAct ) + { + // Don't pick backup activities we don't actually have an animation for. + if (!pBCC->GetModelPtr()->HaveSequenceForActivity(pTable->weaponAct)) + break; + + return (Activity)pTable->weaponAct; + } + } + + // We didn't succeed in finding an activity. See if we can recurse + acttable_t *pBackupTable = CBaseCombatWeapon::GetDefaultBackupActivityList( pTable - i, actCount ); + if (pBackupTable) + { + return Weapon_BackupActivityFromList( pBCC, pBackupTable, actCount, activity, weaponTranslationWasRequired, pWeapon ); + } + + return activity; +} + //----------------------------------------------------------------------------- // Purpose: Uses an activity from a different weapon when the activity we were originally looking for does not exist on this character. -// Created to give NPCs the ability to use weapons they are not otherwise allowed to use. -// Right now, everyone falls back to the SMG act table. +// This gives NPCs and players the ability to use weapons they are otherwise unable to use. //----------------------------------------------------------------------------- Activity CBaseCombatCharacter::Weapon_BackupActivity( Activity activity, bool weaponTranslationWasRequired, CBaseCombatWeapon *pSpecificWeapon ) { @@ -2741,26 +2770,27 @@ Activity CBaseCombatCharacter::Weapon_BackupActivity( Activity activity, bool we if (!pWeapon->SupportsBackupActivity(activity)) return activity; - // Sometimes, the NPC is supposed to use the default activity. Return that if the weapon translation was "not required" and we have an original activity. - if (!weaponTranslationWasRequired && GetModelPtr()->HaveSequenceForActivity(activity)) + // UNDONE: Sometimes, a NPC is supposed to use the default activity. Return that if the weapon translation was "not required" and we have an original activity. + /* + if (!weaponTranslationWasRequired && GetModelPtr()->HaveSequenceForActivity(activity) && !IsPlayer()) { return activity; } + */ - acttable_t *pTable = GetSMG1Acttable(); - int actCount = GetSMG1ActtableCount(); - for ( int i = 0; i < actCount; i++, pTable++ ) + acttable_t *pTable = pWeapon->GetBackupActivityList(); + int actCount = pWeapon->GetBackupActivityListCount(); + if (!pTable) { - if ( activity == pTable->baseAct ) - { - // Don't pick SMG animations we don't actually have an animation for. - if (GetModelPtr() ? !GetModelPtr()->HaveSequenceForActivity(pTable->weaponAct) : false) - { - return activity; - } + // Look for a default list + actCount = pWeapon->ActivityListCount(); + pTable = CBaseCombatWeapon::GetDefaultBackupActivityList( pWeapon->ActivityList(), actCount ); + } - return (Activity)pTable->weaponAct; - } + if (pTable && GetModelPtr()) + { + int actCount = pWeapon->GetBackupActivityListCount(); + return Weapon_BackupActivityFromList( this, pTable, actCount, activity, weaponTranslationWasRequired, pWeapon ); } return activity; @@ -2893,6 +2923,12 @@ int CBaseCombatCharacter::OnTakeDamage( const CTakeDamageInfo &info ) #endif if ( m_iHealth <= 0 ) { +#ifdef MAPBASE_VSCRIPT + // False = Cheat death + if (ScriptDeathHook( const_cast(&info) ) == false) + return retVal; +#endif + IPhysicsObject *pPhysics = VPhysicsGetObject(); if ( pPhysics ) { @@ -3296,7 +3332,24 @@ Relationship_t *CBaseCombatCharacter::FindEntityRelationship( CBaseEntity *pTarg Disposition_t CBaseCombatCharacter::IRelationType ( CBaseEntity *pTarget ) { if ( pTarget ) + { +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_RelationshipType.CanRunInScope( m_ScriptScope )) + { + // entity, default + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { ScriptVariant_t( pTarget->GetScriptInstance() ), FindEntityRelationship( pTarget )->disposition }; + if (g_Hook_RelationshipType.Call( m_ScriptScope, &functionReturn, args ) && (functionReturn.m_type == FIELD_INTEGER && functionReturn.m_int != D_ER)) + { + // Use the disposition returned by the script + return (Disposition_t)functionReturn.m_int; + } + } +#endif + return FindEntityRelationship( pTarget )->disposition; + } + return D_NU; } @@ -3308,7 +3361,24 @@ Disposition_t CBaseCombatCharacter::IRelationType ( CBaseEntity *pTarget ) int CBaseCombatCharacter::IRelationPriority( CBaseEntity *pTarget ) { if ( pTarget ) + { +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_RelationshipPriority.CanRunInScope( m_ScriptScope )) + { + // entity, default + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { ScriptVariant_t( pTarget->GetScriptInstance() ), FindEntityRelationship( pTarget )->priority }; + if (g_Hook_RelationshipPriority.Call( m_ScriptScope, &functionReturn, args ) && functionReturn.m_type == FIELD_INTEGER) + { + // Use the priority returned by the script + return functionReturn.m_int; + } + } +#endif + return FindEntityRelationship( pTarget )->priority; + } + return 0; } @@ -4068,14 +4138,13 @@ void CBaseCombatCharacter::InputKilledNPC( inputdata_t &inputdata ) #ifdef MAPBASE //----------------------------------------------------------------------------- -// Purpose: Handle enemy kills. This actually measures players too. +// Purpose: Handle enemy kills. (this technically measures players too) //----------------------------------------------------------------------------- void CBaseCombatCharacter::OnKilledNPC( CBaseCombatCharacter *pKilled ) { - // I know this can sometimes pass as NULL, but that can work here...right? m_OnKilledEnemy.Set(pKilled, pKilled, this); - // Fire an additional output if this was the player + // Fire an additional output if this was a player if (pKilled && pKilled->IsPlayer()) m_OnKilledPlayer.Set(pKilled, pKilled, this); } @@ -4335,9 +4404,8 @@ CBaseEntity *CBaseCombatCharacter::FindNamedEntity( const char *szName, IEntityF return GetActiveWeapon(); } - // FindEntityProcedural can go through this now, so running this code would likely cause an infinite loop or something. - // As a result, FindEntityProcedural identifies itself with this weird new entity filter. - // Hey, if you've got a better idea, go ahead. + // HACKHACK: FindEntityProcedural can go through this now, so running this code could cause an infinite loop. + // As a result, FindEntityProcedural currently identifies itself with this entity filter. else if (!pFilter || !dynamic_cast(pFilter)) { // search for up to 32 entities with the same name and choose one randomly @@ -4475,6 +4543,26 @@ void CBaseCombatCharacter::ScriptEquipWeapon( HSCRIPT hWeapon ) } } +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CBaseCombatCharacter::ScriptGiveAmmo( int iCount, int iAmmoIndex, bool bSuppressSound ) +{ + return GiveAmmo( iCount, iAmmoIndex, bSuppressSound ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::ScriptRemoveAmmo( int iCount, int iAmmoIndex ) +{ + if (iAmmoIndex == -1) + { + Warning( "%i is not a valid ammo type\n", iAmmoIndex ); + return; + } + + RemoveAmmo( iCount, iAmmoIndex ); +} + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- int CBaseCombatCharacter::ScriptGetAmmoCount( int iType ) const diff --git a/sp/src/game/server/basecombatcharacter.h b/sp/src/game/server/basecombatcharacter.h index 6d971ca3bb..bf07ee1510 100644 --- a/sp/src/game/server/basecombatcharacter.h +++ b/sp/src/game/server/basecombatcharacter.h @@ -412,6 +412,9 @@ class CBaseCombatCharacter : public CBaseFlex } void SetCurrentWeaponProficiency( WeaponProficiency_t iProficiency ) { m_CurrentWeaponProficiency = iProficiency; } virtual WeaponProficiency_t CalcWeaponProficiency( CBaseCombatWeapon *pWeapon ); +#ifdef MAPBASE + inline bool OverridingWeaponProficiency() { return (m_ProficiencyOverride > WEAPON_PROFICIENCY_INVALID); } +#endif virtual Vector GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget = NULL ); virtual float GetSpreadBias( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget ); virtual void DoMuzzleFlash(); @@ -425,7 +428,9 @@ class CBaseCombatCharacter : public CBaseFlex void ScriptDropWeapon( HSCRIPT hWeapon ); void ScriptEquipWeapon( HSCRIPT hWeapon ); - + + int ScriptGiveAmmo( int iCount, int iAmmoIndex, bool bSuppressSound = false ); + void ScriptRemoveAmmo( int iCount, int iAmmoIndex ); int ScriptGetAmmoCount( int iType ) const; void ScriptSetAmmoCount( int iType, int iCount ); @@ -445,6 +450,9 @@ class CBaseCombatCharacter : public CBaseFlex bool ScriptEntInAimCone( HSCRIPT pEntity ) { return FInAimCone( ToEnt( pEntity ) ); } const Vector& ScriptBodyAngles( void ) { static Vector vec; QAngle qa = BodyAngles(); vec.x = qa.x; vec.y = qa.y; vec.z = qa.z; return vec; } + + static ScriptHook_t g_Hook_RelationshipType; + static ScriptHook_t g_Hook_RelationshipPriority; #endif // Interactions diff --git a/sp/src/game/server/basecombatweapon.cpp b/sp/src/game/server/basecombatweapon.cpp index 1135aa8263..2863b747fd 100644 --- a/sp/src/game/server/basecombatweapon.cpp +++ b/sp/src/game/server/basecombatweapon.cpp @@ -373,7 +373,12 @@ bool CBaseCombatWeapon::WeaponLOSCondition( const Vector &ownerPos, const Vector //----------------------------------------------------------------------------- int CBaseCombatWeapon::WeaponRangeAttack1Condition( float flDot, float flDist ) { +#ifdef MAPBASE + // HACKHACK: HasPrimaryAmmo() checks the NPC's reserve ammo counts, which should not be evaluated here if we use clips + if ( UsesPrimaryAmmo() && (UsesClipsForAmmo1() ? !m_iClip1 : !HasPrimaryAmmo()) ) +#else if ( UsesPrimaryAmmo() && !HasPrimaryAmmo() ) +#endif { return COND_NO_PRIMARY_AMMO; } @@ -482,7 +487,11 @@ void CBaseCombatWeapon::Kill( void ) //----------------------------------------------------------------------------- void CBaseCombatWeapon::FallInit( void ) { +#ifdef MAPBASE + SetModel( (GetDroppedModel() && GetDroppedModel()[0]) ? GetDroppedModel() : GetWorldModel() ); +#else SetModel( GetWorldModel() ); +#endif VPhysicsDestroyObject(); if ( !VPhysicsInitNormal( SOLID_BBOX, GetSolidFlags() | FSOLID_TRIGGER, false ) ) diff --git a/sp/src/game/server/baseentity.cpp b/sp/src/game/server/baseentity.cpp index 1b71f9fd3b..42f857de8a 100644 --- a/sp/src/game/server/baseentity.cpp +++ b/sp/src/game/server/baseentity.cpp @@ -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" @@ -103,6 +106,10 @@ ConVar sv_netvisdist( "sv_netvisdist", "10000", FCVAR_CHEAT | FCVAR_DEVELOPMENTO ConVar sv_script_think_interval("sv_script_think_interval", "0.1"); +#ifdef MAPBASE_VSCRIPT +ConVar ent_text_allow_script( "ent_text_allow_script", "1" ); +#endif + // This table encodes edict data. void SendProxy_AnimTime( const SendProp *pProp, const void *pStruct, const void *pVarData, DVariant *pOut, int iElement, int objectID ) @@ -451,6 +458,17 @@ extern bool g_bDisableEhandleAccess; //----------------------------------------------------------------------------- CBaseEntity::~CBaseEntity( ) { +#ifdef MAPBASE_VSCRIPT + // HACKHACK: This is needed to fix a crash when an entity removes itself with Destroy() during its own think function. + // (see https://github.com/mapbase-source/source-sdk-2013/issues/138) + FOR_EACH_VEC( m_ScriptThinkFuncs, i ) + { + HSCRIPT h = m_ScriptThinkFuncs[i]->m_hfnThink; + if ( h ) g_pScriptVM->ReleaseScript( h ); + } + m_ScriptThinkFuncs.PurgeAndDeleteElements(); +#endif // MAPBASE_VSCRIPT + // FIXME: This can't be called from UpdateOnRemove! There's at least one // case where friction sounds are added between the call to UpdateOnRemove + ~CBaseEntity PhysCleanupFrictionSounds( this ); @@ -1057,6 +1075,28 @@ int CBaseEntity::DrawDebugTextOverlays(void) offset++; } #endif + +#ifdef MAPBASE_VSCRIPT + // 'OnEntText' hook inspired by later source games + if (m_ScriptScope.IsInitialized() && g_Hook_OnEntText.CanRunInScope( m_ScriptScope )) + { + if (ent_text_allow_script.GetBool()) + { + ScriptVariant_t functionReturn; + if ( g_Hook_OnEntText.Call( m_ScriptScope, &functionReturn, NULL ) && functionReturn.m_type == FIELD_CSTRING ) + { + CUtlStringList outStrings; + V_SplitString( functionReturn.m_pszString, "\n", outStrings ); + + FOR_EACH_VEC( outStrings, i ) + { + EntityText( offset, outStrings[i], 0, 224, 240, 255 ); + offset++; + } + } + } + } +#endif } if (m_debugOverlays & OVERLAY_VIEWOFFSET) @@ -1346,10 +1386,10 @@ float CBaseEntity::GetMaxOutputDelay( const char *pszOutput ) return 0; } -void CBaseEntity::CancelEventsByInput( const char *szInput ) -{ - g_EventQueue.CancelEventsByInput( this, szInput ); -} +//void CBaseEntity::CancelEventsByInput( const char *szInput ) +//{ +// g_EventQueue.CancelEventsByInput( this, szInput ); +//} #endif // MAPBASE_VSCRIPT CBaseEntityOutput *CBaseEntity::FindNamedOutput( const char *pszOutput ) @@ -1716,22 +1756,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(&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(&info) ) == false) + return; #endif if( info.GetAttacker() ) @@ -1768,6 +1795,22 @@ void CBaseEntity::SendOnKilledGameEvent( const CTakeDamageInfo &info ) } } +void CBaseEntity::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info ) +{ +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_OnKilledOther.CanRunInScope( m_ScriptScope )) + { + HSCRIPT hInfo = g_pScriptVM->RegisterInstance( const_cast(&info) ); + + // victim, info + ScriptVariant_t args[] = { ScriptVariant_t( pVictim->GetScriptInstance() ), ScriptVariant_t( hInfo ) }; + g_Hook_OnKilledOther.Call( m_ScriptScope, NULL, args ); + + g_pScriptVM->RemoveInstance( hInfo ); + } +#endif +} + bool CBaseEntity::HasTarget( string_t targetname ) { @@ -2203,10 +2246,15 @@ END_DATADESC() #ifdef MAPBASE_VSCRIPT ScriptHook_t CBaseEntity::g_Hook_UpdateOnRemove; +ScriptHook_t CBaseEntity::g_Hook_OnEntText; + ScriptHook_t CBaseEntity::g_Hook_VPhysicsCollision; ScriptHook_t CBaseEntity::g_Hook_FireBullets; ScriptHook_t CBaseEntity::g_Hook_OnDeath; +ScriptHook_t CBaseEntity::g_Hook_OnKilledOther; ScriptHook_t CBaseEntity::g_Hook_HandleInteraction; +ScriptHook_t CBaseEntity::g_Hook_ModifyEmitSoundParams; +ScriptHook_t CBaseEntity::g_Hook_ModifySentenceParams; #endif BEGIN_ENT_SCRIPTDESC_ROOT( CBaseEntity, "Root class of all server-side entities" ) @@ -2311,6 +2359,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." ) @@ -2339,7 +2389,7 @@ BEGIN_ENT_SCRIPTDESC_ROOT( CBaseEntity, "Root class of all server-side entities" DEFINE_SCRIPTFUNC_NAMED( ScriptAcceptInput, "AcceptInput", "" ) DEFINE_SCRIPTFUNC_NAMED( ScriptFireOutput, "FireOutput", "Fire an entity output" ) DEFINE_SCRIPTFUNC( GetMaxOutputDelay, "Get the longest delay for all events attached to an output" ) - DEFINE_SCRIPTFUNC( CancelEventsByInput, "Cancel all I/O events for this entity, match input" ) + //DEFINE_SCRIPTFUNC( CancelEventsByInput, "Cancel all I/O events for this entity, match input" ) // Commented out due to unpredictability and unknown risks DEFINE_SCRIPTFUNC_NAMED( ScriptAddOutput, "AddOutput", "Add an output" ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetKeyValue, "GetKeyValue", "Get a keyvalue" ) @@ -2408,7 +2458,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" ) @@ -2448,6 +2501,7 @@ BEGIN_ENT_SCRIPTDESC_ROOT( CBaseEntity, "Root class of all server-side entities" // Hooks // DEFINE_SIMPLE_SCRIPTHOOK( CBaseEntity::g_Hook_UpdateOnRemove, "UpdateOnRemove", FIELD_VOID, "Called when the entity is being removed." ) + DEFINE_SIMPLE_SCRIPTHOOK( CBaseEntity::g_Hook_OnEntText, "OnEntText", FIELD_CSTRING, "Called every frame when ent_text is enabled on the entity. Return a string to be added to the ent_text printout." ) BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_VPhysicsCollision, "VPhysicsCollision", FIELD_VOID, "Called for every single VPhysics-related collision experienced by this entity." ) DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT ) @@ -2467,11 +2521,24 @@ BEGIN_ENT_SCRIPTDESC_ROOT( CBaseEntity, "Root class of all server-side entities" DEFINE_SCRIPTHOOK_PARAM( "info", FIELD_HSCRIPT ) END_SCRIPTHOOK() + BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_OnKilledOther, "OnKilledOther", FIELD_VOID, "Called when the entity kills another entity." ) + DEFINE_SCRIPTHOOK_PARAM( "victim", FIELD_HSCRIPT ) + DEFINE_SCRIPTHOOK_PARAM( "info", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_HandleInteraction, "HandleInteraction", FIELD_BOOLEAN, "Called for internal game interactions. See the g_interaction set of constants for more information. Returning true or false will return that value without falling to any internal handling. Returning nothing will allow the interaction to fall to any internal handling." ) DEFINE_SCRIPTHOOK_PARAM( "interaction", FIELD_INTEGER ) //DEFINE_SCRIPTHOOK_PARAM( "data", FIELD_VARIANT ) DEFINE_SCRIPTHOOK_PARAM( "sourceEnt", FIELD_HSCRIPT ) END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_ModifyEmitSoundParams, "ModifyEmitSoundParams", FIELD_VOID, "Called every time a sound is emitted on this entity, allowing for its parameters to be modified." ) + DEFINE_SCRIPTHOOK_PARAM( "params", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_ModifySentenceParams, "ModifySentenceParams", FIELD_VOID, "Called every time a sentence is emitted on this entity, allowing for its parameters to be modified." ) + DEFINE_SCRIPTHOOK_PARAM( "params", FIELD_HSCRIPT ) + END_SCRIPTHOOK() #endif END_SCRIPTDESC(); @@ -2587,15 +2654,6 @@ void CBaseEntity::UpdateOnRemove( void ) g_pScriptVM->RemoveInstance( m_hScriptInstance ); m_hScriptInstance = NULL; - -#ifdef MAPBASE_VSCRIPT - FOR_EACH_VEC( m_ScriptThinkFuncs, i ) - { - HSCRIPT h = m_ScriptThinkFuncs[i]->m_hfnThink; - if ( h ) g_pScriptVM->ReleaseScript( h ); - } - m_ScriptThinkFuncs.PurgeAndDeleteElements(); -#endif // MAPBASE_VSCRIPT } } @@ -4616,6 +4674,11 @@ bool CBaseEntity::AcceptInput( const char *szInputName, CBaseEntity *pActivator, { (this->*pfnInput)( data ); } + + if ( m_ScriptScope.IsInitialized() ) + { + ScriptInputHookClearParams(); + } } else if ( dmap->dataDesc[i].flags & FTYPEDESC_KEY ) { @@ -4649,6 +4712,8 @@ bool CBaseEntity::AcceptInput( const char *szInputName, CBaseEntity *pActivator, if (functionReturn.m_bool) return true; } + + ScriptInputHookClearParams(); } #endif @@ -4660,6 +4725,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: //----------------------------------------------------------------------------- @@ -4682,22 +4757,42 @@ bool CBaseEntity::ScriptInputHook( const char *szInputName, CBaseEntity *pActiva bHandled = true; } + return bHandled; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseEntity::ScriptInputHookClearParams() +{ g_pScriptVM->ClearValue( "activator" ); g_pScriptVM->ClearValue( "caller" ); #ifdef MAPBASE_VSCRIPT g_pScriptVM->ClearValue( "parameter" ); #endif - - return bHandled; } #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 +7752,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 +7803,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 +8878,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 ); +#endif m_ResponseContexts[iIndex].m_fExpirationTime = duration; - return; } + else + { + ResponseContext_t newContext; + newContext.m_iszName = AllocPooledString( name ); - ResponseContext_t newContext; - newContext.m_iszName = AllocPooledString( name ); - newContext.m_iszValue = AllocPooledString( value ); - newContext.m_fExpirationTime = duration; +#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 ); + m_ResponseContexts.AddToTail( newContext ); + } } -#endif //----------------------------------------------------------------------------- // Purpose: @@ -8976,6 +9099,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 +9134,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 +9283,7 @@ void CBaseEntity::DispatchResponse( const char *conceptName ) // Don't know how to handle .vcds!!! break; } +#endif } //----------------------------------------------------------------------------- @@ -9426,6 +9610,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 +9633,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 ) diff --git a/sp/src/game/server/baseentity.h b/sp/src/game/server/baseentity.h index a96d443bdd..1f2f75e15b 100644 --- a/sp/src/game/server/baseentity.h +++ b/sp/src/game/server/baseentity.h @@ -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 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 @@ -599,7 +610,7 @@ class CBaseEntity : public IServerEntity #ifdef MAPBASE_VSCRIPT void ScriptFireOutput( const char *pszOutput, HSCRIPT hActivator, HSCRIPT hCaller, const char *szValue, float flDelay ); float GetMaxOutputDelay( const char *pszOutput ); - void CancelEventsByInput( const char *szInput ); + //void CancelEventsByInput( const char *szInput ); #endif @@ -682,6 +693,10 @@ class CBaseEntity : public IServerEntity #endif bool ScriptInputHook( const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t Value, ScriptVariant_t &functionReturn ); + void ScriptInputHookClearParams(); +#ifdef MAPBASE_VSCRIPT + bool ScriptDeathHook( CTakeDamageInfo *info ); +#endif // // Input handlers. @@ -1001,10 +1016,10 @@ class CBaseEntity : public IServerEntity 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; @@ -1079,7 +1094,7 @@ class CBaseEntity : public IServerEntity void SendOnKilledGameEvent( const CTakeDamageInfo &info ); // Notifier that I've killed some other entity. (called from Victim's Event_Killed). - virtual void Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info ) { return; } + virtual void Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info ); // UNDONE: Make this data? virtual int BloodColor( void ); @@ -1258,7 +1273,7 @@ class CBaseEntity : public IServerEntity #ifdef _DEBUG void FunctionCheck( void *pFunction, const char *name ); - ENTITYFUNCPTR TouchSet( ENTITYFUNCPTR func, char *name ) + ENTITYFUNCPTR TouchSet( ENTITYFUNCPTR func, const char *name ) { #ifdef GNUC COMPILE_TIME_ASSERT( sizeof(func) == 8 ); @@ -1269,7 +1284,7 @@ class CBaseEntity : public IServerEntity FunctionCheck( *(reinterpret_cast(&m_pfnTouch)), name ); return func; } - USEPTR UseSet( USEPTR func, char *name ) + USEPTR UseSet( USEPTR func, const char *name ) { #ifdef GNUC COMPILE_TIME_ASSERT( sizeof(func) == 8 ); @@ -1280,7 +1295,7 @@ class CBaseEntity : public IServerEntity FunctionCheck( *(reinterpret_cast(&m_pfnUse)), name ); return func; } - ENTITYFUNCPTR BlockedSet( ENTITYFUNCPTR func, char *name ) + ENTITYFUNCPTR BlockedSet( ENTITYFUNCPTR func, const char *name ) { #ifdef GNUC COMPILE_TIME_ASSERT( sizeof(func) == 8 ); @@ -1294,6 +1309,12 @@ class CBaseEntity : public IServerEntity #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 ); @@ -1517,6 +1538,12 @@ class CBaseEntity : public IServerEntity void GenderExpandString( char const *in, char *out, int maxlen ); virtual void ModifyEmitSoundParams( EmitSound_t ¶ms ); +#ifdef MAPBASE + // Same as above, but for sentences + // (which don't actually have EmitSound_t params) + virtual void ModifySentenceParams( int &iSentenceIndex, int &iChannel, float &flVolume, soundlevel_t &iSoundlevel, int &iFlags, int &iPitch, + const Vector **pOrigin, const Vector **pDirection, bool &bUpdatePositions, float &soundtime, int &iSpecialDSP, int &iSpeakerIndex ); +#endif static float GetSoundDuration( const char *soundname, char const *actormodel ); @@ -1546,7 +1573,11 @@ class CBaseEntity : public IServerEntity 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,16 +2146,24 @@ class CBaseEntity : public IServerEntity 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; } void ScriptSetTakeDamage( int val ) { m_takedamage = val; } static ScriptHook_t g_Hook_UpdateOnRemove; + static ScriptHook_t g_Hook_OnEntText; + static ScriptHook_t g_Hook_VPhysicsCollision; static ScriptHook_t g_Hook_FireBullets; static ScriptHook_t g_Hook_OnDeath; + static ScriptHook_t g_Hook_OnKilledOther; static ScriptHook_t g_Hook_HandleInteraction; + static ScriptHook_t g_Hook_ModifyEmitSoundParams; + static ScriptHook_t g_Hook_ModifySentenceParams; #endif string_t m_iszVScripts; diff --git a/sp/src/game/server/baseflex.cpp b/sp/src/game/server/baseflex.cpp index d908b15cc5..49f3c189fe 100644 --- a/sp/src/game/server/baseflex.cpp +++ b/sp/src/game/server/baseflex.cpp @@ -95,9 +95,17 @@ BEGIN_DATADESC( CBaseFlex ) END_DATADESC() +#ifdef MAPBASE_VSCRIPT +BEGIN_ENT_SCRIPTDESC( CBaseFlex, CBaseAnimatingOverlay, "Animated characters who have vertex flex capability." ) +#else BEGIN_ENT_SCRIPTDESC( CBaseFlex, CBaseAnimating, "Animated characters who have vertex flex capability." ) +#endif DEFINE_SCRIPTFUNC_NAMED( ScriptGetOldestScene, "GetCurrentScene", "Returns the instance of the oldest active scene entity (if any)." ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetSceneByIndex, "GetSceneByIndex", "Returns the instance of the scene entity at the specified index." ) + +#ifdef MAPBASE + DEFINE_SCRIPTFUNC( SetViewtarget, "Sets the entity's eye target." ) +#endif END_SCRIPTDESC(); @@ -799,6 +807,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 +2108,6 @@ float CBaseFlex::PlayAutoGeneratedSoundScene( const char *soundname ) //----------------------------------------------------------------------------- bool CBaseFlex::GetGameTextSpeechParams( hudtextparms_t ¶ms ) { - 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) { @@ -2328,8 +2336,13 @@ void CBaseFlex::DoBodyLean( void ) { m_vecPrevVelocity = vecDelta; float decay = ExponentialDecay( 0.5, 0.1, dt ); +#ifdef MAPBASE // From Alien Swarm SDK + m_vecShift = m_vecShift * decay; + m_vecLean = m_vecLean * decay; +#else m_vecShift = m_vecLean * decay; m_vecLean = m_vecShift * decay; +#endif } m_vecPrevOrigin = vecOrigin; diff --git a/sp/src/game/server/baseflex.h b/sp/src/game/server/baseflex.h index 215b13f55a..ec809534af 100644 --- a/sp/src/game/server/baseflex.h +++ b/sp/src/game/server/baseflex.h @@ -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 diff --git a/sp/src/game/server/basemultiplayerplayer.cpp b/sp/src/game/server/basemultiplayerplayer.cpp index b6ecd44c46..d75ec6279e 100644 --- a/sp/src/game/server/basemultiplayerplayer.cpp +++ b/sp/src/game/server/basemultiplayerplayer.cpp @@ -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 } //----------------------------------------------------------------------------- diff --git a/sp/src/game/server/basemultiplayerplayer.h b/sp/src/game/server/basemultiplayerplayer.h index 06a0e00ded..9550a86ac9 100644 --- a/sp/src/game/server/basemultiplayerplayer.h +++ b/sp/src/game/server/basemultiplayerplayer.h @@ -28,7 +28,7 @@ class CBaseMultiplayerPlayer : public CAI_ExpresserHost 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 ); diff --git a/sp/src/game/server/cbase.cpp b/sp/src/game/server/cbase.cpp index d98ea9cdda..abee7a5d85 100644 --- a/sp/src/game/server/cbase.cpp +++ b/sp/src/game/server/cbase.cpp @@ -987,83 +987,31 @@ void CEventQueue::ServiceEvents( void ) // In the context the event, the searching entity is also the caller CBaseEntity *pSearchingEntity = pe->m_pCaller; #ifdef MAPBASE - //=============================================================== - // - // This is the code that the I/O system uses to look for its targets. - // - // I wanted a good way to access a COutputEHANDLE's handle parameter. - // Sure, you could do it through logic_register_activator, but what if that's not good enough? - // - // Basic gEntList searches, which this originally used, would require extra implementation for another entity pointer to be passed. - // Without changing the way entity searching works, I just created a custom version of it here. - // - // Yes, all of this for mere "!output" support. - // - // I don't think there's much harm in this anyway. It's functionally identical and might even run a few nanoseconds faster - // since we don't need to check for filters or call the same loop over and over again. - // - //=============================================================== - const char *szName = STRING(pe->m_iTarget); - if ( szName[0] == '!' ) + // This is a hack to access the entity from a FIELD_EHANDLE input + if ( FStrEq( STRING( pe->m_iTarget ), "!output" ) ) { - CBaseEntity *target = gEntList.FindEntityProcedural( szName, pSearchingEntity, pe->m_pActivator, pe->m_pCaller ); + pe->m_VariantValue.Convert( FIELD_EHANDLE ); + CBaseEntity *target = pe->m_VariantValue.Entity(); - if (!target) - { - // Here's the !output I was talking about. - // It only checks for it if we're looking for a procedural entity ('!' confirmed) - // and we didn't find one from FindEntityProcedural. - if (FStrEq(szName, "!output")) - { - pe->m_VariantValue.Convert( FIELD_EHANDLE ); - target = pe->m_VariantValue.Entity(); - } - } - - if (target) - { - // pump the action into the target - target->AcceptInput( STRING(pe->m_iTargetInput), pe->m_pActivator, pe->m_pCaller, pe->m_VariantValue, pe->m_iOutputID ); - targetFound = true; - } + // pump the action into the target + target->AcceptInput( STRING( pe->m_iTargetInput ), pe->m_pActivator, pe->m_pCaller, pe->m_VariantValue, pe->m_iOutputID ); + targetFound = true; } else +#endif { - const CEntInfo *pInfo = gEntList.FirstEntInfo(); - - for ( ;pInfo; pInfo = pInfo->m_pNext ) + CBaseEntity *target = NULL; + while ( 1 ) { - CBaseEntity *ent = (CBaseEntity *)pInfo->m_pEntity; - if ( !ent ) - { - DevWarning( "NULL entity in global entity list!\n" ); - continue; - } - - if ( !ent->GetEntityName() ) - continue; + target = gEntList.FindEntityByName( target, pe->m_iTarget, pSearchingEntity, pe->m_pActivator, pe->m_pCaller ); + if ( !target ) + break; - if ( ent->NameMatches( szName ) ) - { - // pump the action into the target - ent->AcceptInput( STRING(pe->m_iTargetInput), pe->m_pActivator, pe->m_pCaller, pe->m_VariantValue, pe->m_iOutputID ); - targetFound = true; - } + // pump the action into the target + target->AcceptInput( STRING(pe->m_iTargetInput), pe->m_pActivator, pe->m_pCaller, pe->m_VariantValue, pe->m_iOutputID ); + targetFound = true; } } -#else - CBaseEntity *target = NULL; - while ( 1 ) - { - target = gEntList.FindEntityByName( target, pe->m_iTarget, pSearchingEntity, pe->m_pActivator, pe->m_pCaller ); - if ( !target ) - break; - - // pump the action into the target - target->AcceptInput( STRING(pe->m_iTargetInput), pe->m_pActivator, pe->m_pCaller, pe->m_VariantValue, pe->m_iOutputID ); - targetFound = true; - } -#endif } // direct pointer diff --git a/sp/src/game/server/cbase.h b/sp/src/game/server/cbase.h index 290e3b25aa..2b00af39ed 100644 --- a/sp/src/game/server/cbase.h +++ b/sp/src/game/server/cbase.h @@ -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 diff --git a/sp/src/game/server/colorcorrection.cpp b/sp/src/game/server/colorcorrection.cpp index 16d0122240..69e6db8580 100644 --- a/sp/src/game/server/colorcorrection.cpp +++ b/sp/src/game/server/colorcorrection.cpp @@ -5,9 +5,8 @@ // $NoKeywords: $ //===========================================================================// -#include - #include "cbase.h" +#include "colorcorrection.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -16,64 +15,6 @@ static const char *s_pFadeInContextThink = "ColorCorrectionFadeInThink"; static const char *s_pFadeOutContextThink = "ColorCorrectionFadeOutThink"; - -//------------------------------------------------------------------------------ -// FIXME: This really should inherit from something more lightweight -//------------------------------------------------------------------------------ - - -//------------------------------------------------------------------------------ -// Purpose : Shadow control entity -//------------------------------------------------------------------------------ -class CColorCorrection : public CBaseEntity -{ - DECLARE_CLASS( CColorCorrection, CBaseEntity ); -public: - DECLARE_SERVERCLASS(); - DECLARE_DATADESC(); - - CColorCorrection(); - - void Spawn( void ); - int UpdateTransmitState(); - void Activate( void ); - - virtual int ObjectCaps( void ) { return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } - - // Inputs - void InputEnable( inputdata_t &inputdata ); - void InputDisable( inputdata_t &inputdata ); - void InputSetFadeInDuration ( inputdata_t &inputdata ); - void InputSetFadeOutDuration ( inputdata_t &inputdata ); - -private: - void FadeIn ( void ); - void FadeOut ( void ); - - void FadeInThink( void ); // Fades lookup weight from Cur->MaxWeight - void FadeOutThink( void ); // Fades lookup weight from CurWeight->0.0 - - - - float m_flFadeInDuration; // Duration for a full 0->MaxWeight transition - float m_flFadeOutDuration; // Duration for a full Max->0 transition - float m_flStartFadeInWeight; - float m_flStartFadeOutWeight; - float m_flTimeStartFadeIn; - float m_flTimeStartFadeOut; - - float m_flMaxWeight; - - bool m_bStartDisabled; - CNetworkVar( bool, m_bEnabled ); - - CNetworkVar( float, m_MinFalloff ); - CNetworkVar( float, m_MaxFalloff ); - CNetworkVar( float, m_flCurWeight ); - CNetworkString( m_netlookupFilename, MAX_PATH ); - - string_t m_lookupFilename; -}; LINK_ENTITY_TO_CLASS(color_correction, CColorCorrection); @@ -97,12 +38,19 @@ BEGIN_DATADESC( CColorCorrection ) DEFINE_KEYFIELD( m_bEnabled, FIELD_BOOLEAN, "enabled" ), DEFINE_KEYFIELD( m_bStartDisabled, FIELD_BOOLEAN, "StartDisabled" ), +#ifdef MAPBASE // From Alien Swarm SDK + DEFINE_KEYFIELD( m_bExclusive, FIELD_BOOLEAN, "exclusive" ), +#endif // DEFINE_ARRAY( m_netlookupFilename, FIELD_CHARACTER, MAX_PATH ), DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFadeInDuration", InputSetFadeInDuration ), DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFadeOutDuration", InputSetFadeOutDuration ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetMinFalloff", InputSetMinFalloff ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetMaxFalloff", InputSetMaxFalloff ), +#endif END_DATADESC() @@ -112,8 +60,18 @@ IMPLEMENT_SERVERCLASS_ST_NOBASE(CColorCorrection, DT_ColorCorrection) SendPropFloat( SENDINFO(m_MinFalloff) ), SendPropFloat( SENDINFO(m_MaxFalloff) ), SendPropFloat( SENDINFO(m_flCurWeight) ), +#ifdef MAPBASE // From Alien Swarm SDK + SendPropFloat( SENDINFO(m_flMaxWeight) ), + SendPropFloat( SENDINFO(m_flFadeInDuration) ), + SendPropFloat( SENDINFO(m_flFadeOutDuration) ), +#endif SendPropString( SENDINFO(m_netlookupFilename) ), SendPropBool( SENDINFO(m_bEnabled) ), +#ifdef MAPBASE // From Alien Swarm SDK + SendPropBool( SENDINFO(m_bMaster) ), + SendPropBool( SENDINFO(m_bClientSide) ), + SendPropBool( SENDINFO(m_bExclusive) ), +#endif END_SEND_TABLE() @@ -132,6 +90,11 @@ CColorCorrection::CColorCorrection() : BaseClass() m_flTimeStartFadeOut = 0.0f; m_netlookupFilename.GetForModify()[0] = 0; m_lookupFilename = NULL_STRING; +#ifdef MAPBASE // From Alien Swarm SDK + m_bMaster = false; + m_bClientSide = false; + m_bExclusive = false; +#endif } @@ -175,6 +138,11 @@ void CColorCorrection::Activate( void ) { BaseClass::Activate(); +#ifdef MAPBASE // From Alien Swarm SDK (moved to Activate() for save/restore support) + m_bMaster = IsMaster(); + m_bClientSide = IsClientSide(); +#endif + Q_strncpy( m_netlookupFilename.GetForModify(), STRING( m_lookupFilename ), MAX_PATH ); } @@ -183,6 +151,11 @@ void CColorCorrection::Activate( void ) //----------------------------------------------------------------------------- void CColorCorrection::FadeIn ( void ) { +#ifdef MAPBASE // From Alien Swarm SDK + if ( m_bClientSide || ( m_bEnabled && m_flCurWeight >= m_flMaxWeight ) ) + return; +#endif + m_bEnabled = true; m_flTimeStartFadeIn = gpGlobals->curtime; m_flStartFadeInWeight = m_flCurWeight; @@ -194,6 +167,11 @@ void CColorCorrection::FadeIn ( void ) //----------------------------------------------------------------------------- void CColorCorrection::FadeOut ( void ) { +#ifdef MAPBASE // From Alien Swarm SDK + if ( m_bClientSide || ( !m_bEnabled && m_flCurWeight <= 0.0f ) ) + return; +#endif + m_bEnabled = false; m_flTimeStartFadeOut = gpGlobals->curtime; m_flStartFadeOutWeight = m_flCurWeight; @@ -230,7 +208,11 @@ void CColorCorrection::FadeInThink( void ) flFadeRatio = clamp ( flFadeRatio, 0.0f, 1.0f ); m_flStartFadeInWeight = clamp ( m_flStartFadeInWeight, 0.0f, 1.0f ); +#ifdef MAPBASE + m_flCurWeight = Lerp( flFadeRatio, m_flStartFadeInWeight, m_flMaxWeight.Get() ); +#else m_flCurWeight = Lerp( flFadeRatio, m_flStartFadeInWeight, m_flMaxWeight ); +#endif SetNextThink( gpGlobals->curtime + COLOR_CORRECTION_ENT_THINK_RATE, s_pFadeInContextThink ); } @@ -312,3 +294,94 @@ void CColorCorrection::InputSetFadeOutDuration( inputdata_t& inputdata ) { m_flFadeOutDuration = inputdata.value.Float(); } + +#ifdef MAPBASE +void CColorCorrection::InputSetMinFalloff( inputdata_t& inputdata ) +{ + m_MinFalloff = inputdata.value.Float(); +} + +void CColorCorrection::InputSetMaxFalloff( inputdata_t& inputdata ) +{ + m_MaxFalloff = inputdata.value.Float(); +} +#endif + +#ifdef MAPBASE // From Alien Swarm SDK +CColorCorrectionSystem s_ColorCorrectionSystem( "ColorCorrectionSystem" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CColorCorrectionSystem *ColorCorrectionSystem( void ) +{ + return &s_ColorCorrectionSystem; +} + + +//----------------------------------------------------------------------------- +// Purpose: Clear out the fog controller. +//----------------------------------------------------------------------------- +void CColorCorrectionSystem::LevelInitPreEntity( void ) +{ + m_hMasterController = NULL; + ListenForGameEvent( "round_start" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Find the master controller. If no controller is +// set as Master, use the first controller found. +//----------------------------------------------------------------------------- +void CColorCorrectionSystem::InitMasterController( void ) +{ + CColorCorrection *pColorCorrection = NULL; + do + { + pColorCorrection = static_cast( gEntList.FindEntityByClassname( pColorCorrection, "color_correction" ) ); + if ( pColorCorrection ) + { + if ( m_hMasterController.Get() == NULL ) + { + m_hMasterController = pColorCorrection; + } + else + { + if ( pColorCorrection->IsMaster() ) + { + m_hMasterController = pColorCorrection; + } + } + } + } while ( pColorCorrection ); +} + +//----------------------------------------------------------------------------- +// Purpose: On a multiplayer map restart, re-find the master controller. +//----------------------------------------------------------------------------- +void CColorCorrectionSystem::FireGameEvent( IGameEvent *pEvent ) +{ + InitMasterController(); +} + +//----------------------------------------------------------------------------- +// Purpose: On level load find the master fog controller. If no controller is +// set as Master, use the first fog controller found. +//----------------------------------------------------------------------------- +void CColorCorrectionSystem::LevelInitPostEntity( void ) +{ + InitMasterController(); + + // HACK: Singleplayer games don't get a call to CBasePlayer::Spawn on level transitions. + // CBasePlayer::Activate is called before this is called so that's too soon to set up the fog controller. + // We don't have a hook similar to Activate that happens after LevelInitPostEntity + // is called, or we could just do this in the player itself. + if ( gpGlobals->maxClients == 1 ) + { + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + if ( pPlayer && ( pPlayer->m_hColorCorrectionCtrl.Get() == NULL ) ) + { + pPlayer->InitColorCorrectionController(); + } + } +} +#endif diff --git a/sp/src/game/server/colorcorrection.h b/sp/src/game/server/colorcorrection.h new file mode 100644 index 0000000000..2e96eb7c56 --- /dev/null +++ b/sp/src/game/server/colorcorrection.h @@ -0,0 +1,147 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Note that this header exists in the Alien Swarm SDK, but not in stock Source SDK 2013. +// Although technically a new Mapbase file, it only serves to move otherwise identical code, +// so most code and repo conventions will pretend it was always there. +// +// -------------------------------------------------------------------- +// +// Purpose: Color correction entity. +// +//=============================================================================// + +#ifndef COLOR_CORRECTION_H +#define COLOR_CORRECTION_H +#ifdef _WIN32 +#pragma once +#endif + +#include +#include "cbase.h" +#ifdef MAPBASE // From Alien Swarm SDK +#include "GameEventListener.h" + +// Spawn Flags +#define SF_COLORCORRECTION_MASTER 0x0001 +#define SF_COLORCORRECTION_CLIENTSIDE 0x0002 +#endif + +//------------------------------------------------------------------------------ +// FIXME: This really should inherit from something more lightweight +//------------------------------------------------------------------------------ + + +//------------------------------------------------------------------------------ +// Purpose : Shadow control entity +//------------------------------------------------------------------------------ +class CColorCorrection : public CBaseEntity +{ + DECLARE_CLASS( CColorCorrection, CBaseEntity ); +public: + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + + CColorCorrection(); + + void Spawn( void ); + int UpdateTransmitState(); + void Activate( void ); + + virtual int ObjectCaps( void ) { return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + +#ifdef MAPBASE // From Alien Swarm SDK + bool IsMaster( void ) const { return HasSpawnFlags( SF_COLORCORRECTION_MASTER ); } + + bool IsClientSide( void ) const { return HasSpawnFlags( SF_COLORCORRECTION_CLIENTSIDE ); } + + bool IsExclusive( void ) const { return m_bExclusive; } +#endif + + // Inputs + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + void InputSetFadeInDuration ( inputdata_t &inputdata ); + void InputSetFadeOutDuration ( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetMinFalloff( inputdata_t &inputdata ); + void InputSetMaxFalloff( inputdata_t &inputdata ); +#endif + +private: + void FadeIn ( void ); + void FadeOut ( void ); + + void FadeInThink( void ); // Fades lookup weight from Cur->MaxWeight + void FadeOutThink( void ); // Fades lookup weight from CurWeight->0.0 + + + +#ifdef MAPBASE // From Alien Swarm SDK + CNetworkVar( float, m_flFadeInDuration ); // Duration for a full 0->MaxWeight transition + CNetworkVar( float, m_flFadeOutDuration ); // Duration for a full Max->0 transition +#else + float m_flFadeInDuration; // Duration for a full 0->MaxWeight transition + float m_flFadeOutDuration; // Duration for a full Max->0 transition +#endif + float m_flStartFadeInWeight; + float m_flStartFadeOutWeight; + float m_flTimeStartFadeIn; + float m_flTimeStartFadeOut; + +#ifdef MAPBASE // From Alien Swarm SDK + CNetworkVar( float, m_flMaxWeight ); +#else + float m_flMaxWeight; +#endif + + bool m_bStartDisabled; + CNetworkVar( bool, m_bEnabled ); +#ifdef MAPBASE // From Alien Swarm SDK + CNetworkVar( bool, m_bMaster ); + CNetworkVar( bool, m_bClientSide ); + CNetworkVar( bool, m_bExclusive ); +#endif + + CNetworkVar( float, m_MinFalloff ); + CNetworkVar( float, m_MaxFalloff ); + CNetworkVar( float, m_flCurWeight ); + CNetworkString( m_netlookupFilename, MAX_PATH ); + + string_t m_lookupFilename; +}; + +#ifdef MAPBASE // From Alien Swarm SDK +//============================================================================= +// +// ColorCorrection Controller System. Just a place to store a master controller +// +class CColorCorrectionSystem : public CAutoGameSystem, public CGameEventListener +{ +public: + + // Creation/Init. + CColorCorrectionSystem( char const *name ) : CAutoGameSystem( name ) + { + m_hMasterController = NULL; + } + + ~CColorCorrectionSystem() + { + m_hMasterController = NULL; + } + + virtual void LevelInitPreEntity(); + virtual void LevelInitPostEntity(); + virtual void FireGameEvent( IGameEvent *pEvent ); + CColorCorrection *GetMasterColorCorrection( void ) { return m_hMasterController; } + +private: + + void InitMasterController( void ); + CHandle< CColorCorrection > m_hMasterController; +}; + +CColorCorrectionSystem *ColorCorrectionSystem( void ); +#endif + +#endif // COLOR_CORRECTION_H diff --git a/sp/src/game/server/colorcorrectionvolume.cpp b/sp/src/game/server/colorcorrectionvolume.cpp index a56cd533e3..abc55d75ff 100644 --- a/sp/src/game/server/colorcorrectionvolume.cpp +++ b/sp/src/game/server/colorcorrectionvolume.cpp @@ -48,7 +48,11 @@ class CColorCorrectionVolume : public CBaseTrigger private: +#ifdef MAPBASE // From Alien Swarm SDK + CNetworkVar( bool, m_bEnabled ); +#else bool m_bEnabled; +#endif bool m_bStartDisabled; CNetworkVar( float, m_Weight ); @@ -61,7 +65,11 @@ class CColorCorrectionVolume : public CBaseTrigger float m_LastExitWeight; float m_LastExitTime; +#ifdef MAPBASE // From Alien Swarm SDK + CNetworkVar( float, m_FadeDuration ); +#else float m_FadeDuration; +#endif }; LINK_ENTITY_TO_CLASS(color_correction_volume, CColorCorrectionVolume); @@ -90,6 +98,11 @@ END_DATADESC() IMPLEMENT_SERVERCLASS_ST_NOBASE(CColorCorrectionVolume, DT_ColorCorrectionVolume) +#ifdef MAPBASE // From Alien Swarm SDK + SendPropBool( SENDINFO(m_bEnabled) ), + SendPropFloat( SENDINFO(m_MaxWeight) ), + SendPropFloat( SENDINFO(m_FadeDuration) ), +#endif SendPropFloat( SENDINFO(m_Weight) ), SendPropString( SENDINFO(m_lookupFilename) ), END_SEND_TABLE() diff --git a/sp/src/game/server/effects.cpp b/sp/src/game/server/effects.cpp index 7b07e15709..cbe0519083 100644 --- a/sp/src/game/server/effects.cpp +++ b/sp/src/game/server/effects.cpp @@ -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; diff --git a/sp/src/game/server/entity_tools_server.cpp b/sp/src/game/server/entity_tools_server.cpp index 5032ca0406..8b76e88883 100644 --- a/sp/src/game/server/entity_tools_server.cpp +++ b/sp/src/game/server/entity_tools_server.cpp @@ -14,6 +14,13 @@ #include "sceneentity.h" #include "particles/particles.h" +#if _MSC_VER >= 1900 +#include "icommandline.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + //----------------------------------------------------------------------------- // Interface from engine to tools for manipulating entities diff --git a/sp/src/game/server/entitylist.cpp b/sp/src/game/server/entitylist.cpp index bb555a1faf..dd886ab315 100644 --- a/sp/src/game/server/entitylist.cpp +++ b/sp/src/game/server/entitylist.cpp @@ -22,6 +22,7 @@ #include "npc_playercompanion.h" #ifdef MAPBASE #include "hl2_player.h" +#include "mapbase_matchers_base.h" #endif #endif // HL2_DLL @@ -78,6 +79,15 @@ class CAimTargetManager : public IEntityListener // IEntityListener virtual void OnEntityCreated( CBaseEntity *pEntity ) {} + virtual void OnEntitySpawned( CBaseEntity *pEntity ) + { + // From Alien Swarm SDK + if ( ShouldAddEntity( pEntity ) ) + { + RemoveEntity( pEntity ); + AddEntity( pEntity ); + } + } virtual void OnEntityDeleted( CBaseEntity *pEntity ) { if ( !(pEntity->GetFlags() & FL_AIMTARGET) ) @@ -105,6 +115,10 @@ class CAimTargetManager : public IEntityListener memcpy( pList, m_targetList.Base(), sizeof(CBaseEntity *) * count ); return count; } + CBaseEntity *ListElement( int iIndex ) // From Alien Swarm SDK + { + return m_targetList[iIndex]; + } private: CUtlVector m_targetList; @@ -120,6 +134,10 @@ int AimTarget_ListCopy( CBaseEntity *pList[], int listMax ) { return g_AimManager.ListCopy( pList, listMax ); } +CBaseEntity *AimTarget_ListElement( int iIndex ) +{ + return g_AimManager.ListElement( iIndex ); +} void AimTarget_ForceRepopulateList() { g_AimManager.ForceRepopulateList(); @@ -293,6 +311,10 @@ CBaseEntityClassList::~CBaseEntityClassList() { } +#ifdef MAPBASE_VSCRIPT +static CUtlVector g_CustomProcedurals; +#endif + CGlobalEntityList::CGlobalEntityList() { m_iHighestEnt = m_iNumEnts = m_iNumEdicts = 0; @@ -380,6 +402,18 @@ void CGlobalEntityList::Clear( void ) // free the memory g_DeleteList.Purge(); +#ifdef _DEBUG // From Alien Swarm SDK + for ( UtlHashHandle_t handle = g_EntsByClassname.GetFirstHandle(); g_EntsByClassname.IsValidHandle(handle); handle = g_EntsByClassname.GetNextHandle(handle) ) + { + EntsByStringList_t &element = g_EntsByClassname[handle]; + Assert( element.pHead == NULL ); + } +#endif + +#ifdef MAPBASE_VSCRIPT + g_CustomProcedurals.Purge(); +#endif + CBaseEntity::m_nDebugPlayer = -1; CBaseEntity::m_bInDebugSelect = false; m_iHighestEnt = 0; @@ -516,6 +550,43 @@ CBaseEntity *CGlobalEntityList::FindEntityByClassname( CBaseEntity *pStartEntity return NULL; } +// From Alien Swarm SDK +CBaseEntity *CGlobalEntityList::FindEntityByClassnameFast( CBaseEntity *pStartEntity, string_t iszClassname ) +{ + /* + if ( pStartEntity ) + { + return pStartEntity->m_pNextByClass; + } + + EntsByStringList_t key = { iszClassname }; + UtlHashHandle_t hEntry = g_EntsByClassname.Find( key ); + if ( hEntry != g_EntsByClassname.InvalidHandle() ) + { + return g_EntsByClassname[hEntry].pHead; + } + */ + + const CEntInfo *pInfo = pStartEntity ? GetEntInfoPtr( pStartEntity->GetRefEHandle() )->m_pNext : FirstEntInfo(); + + for ( ;pInfo; pInfo = pInfo->m_pNext ) + { + CBaseEntity *pEntity = (CBaseEntity *)pInfo->m_pEntity; + if ( !pEntity ) + { + DevWarning( "NULL entity in global entity list!\n" ); + continue; + } + + if ( pEntity->m_iClassname == iszClassname) + { + return pEntity; + } + } + + return NULL; +} + //----------------------------------------------------------------------------- // Purpose: Finds an entity given a procedural name. @@ -633,6 +704,93 @@ CBaseEntity *CGlobalEntityList::FindEntityProcedural( const char *szName, CBaseE } +#ifdef MAPBASE_VSCRIPT +//----------------------------------------------------------------------------- +// Purpose: Finds an entity given a custom procedural name. +// Input : szName - The procedural name to search for, should start with '!'. +// pSearchingEntity - +// pActivator - The activator entity if this was called from an input +// or Use handler. +//----------------------------------------------------------------------------- +CBaseEntity *CGlobalEntityList::FindEntityCustomProcedural( CBaseEntity *pStartEntity, const char *szName, CBaseEntity *pSearchingEntity, CBaseEntity *pActivator, CBaseEntity *pCaller ) +{ + const char *pName = szName; + + // + // Check for the name escape character. + // + if ( szName[0] == '!' ) + pName = szName + 1; + + // + // Look through the custom procedural list. + // + for (int i = 0; i < g_CustomProcedurals.Count(); i++) + { + if (Matcher_NamesMatch( g_CustomProcedurals[i].szName, pName )) + { + if (pStartEntity && g_CustomProcedurals[i].bCanReturnMultiple == false) + return NULL; + + // name, startEntity, searchingEntity, activator, caller + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { + ScriptVariant_t( pName ), + ScriptVariant_t( ToHScript( pStartEntity ) ), + ScriptVariant_t( ToHScript( pSearchingEntity ) ), + ScriptVariant_t( ToHScript( pActivator ) ), + ScriptVariant_t( ToHScript( pCaller ) ), + }; + + g_pScriptVM->ExecuteFunction( g_CustomProcedurals[i].hFunc, args, 5, &functionReturn, NULL, true ); + + return ToEnt( functionReturn.m_hScript ); + } + } + + return NULL; +} + +void CGlobalEntityList::AddCustomProcedural( const char *pszName, HSCRIPT hFunc, bool bCanReturnMultiple ) +{ + // + // Check for any existing custom procedurals to replace. + // + for (int i = 0; i < g_CustomProcedurals.Count(); i++) + { + if (FStrEq( pszName, g_CustomProcedurals[i].szName )) + { + g_CustomProcedurals.FastRemove( i ); + break; + } + } + + // + // Add a new custom procedural. + // + int i = g_CustomProcedurals.AddToTail(); + g_CustomProcedurals[i].szName = pszName; + g_CustomProcedurals[i].hFunc = hFunc; + g_CustomProcedurals[i].bCanReturnMultiple = bCanReturnMultiple; +} + +void CGlobalEntityList::RemoveCustomProcedural( const char *pszName ) +{ + // + // Remove the specified custom procedural. + // + for (int i = 0; i < g_CustomProcedurals.Count(); i++) + { + if (FStrEq( pszName, g_CustomProcedurals[i].szName )) + { + g_CustomProcedurals.FastRemove( i ); + break; + } + } +} +#endif + + //----------------------------------------------------------------------------- // Purpose: Iterates the entities with a given name. // Input : pStartEntity - Last entity found, NULL to start a new iteration. @@ -647,6 +805,16 @@ CBaseEntity *CGlobalEntityList::FindEntityByName( CBaseEntity *pStartEntity, con if ( szName[0] == '!' ) { +#ifdef MAPBASE + if (g_CustomProcedurals.Count() > 0) + { + // Search for a custom procedural + CBaseEntity *ent = FindEntityCustomProcedural( pStartEntity, szName, pSearchingEntity, pActivator, pCaller ); + if (ent != NULL) + return ent; + } +#endif + // // Avoid an infinite loop, only find one match per procedural search! // @@ -682,6 +850,35 @@ CBaseEntity *CGlobalEntityList::FindEntityByName( CBaseEntity *pStartEntity, con return NULL; } +// From Alien Swarm SDK +CBaseEntity *CGlobalEntityList::FindEntityByNameFast( CBaseEntity *pStartEntity, string_t iszName ) +{ + if ( iszName == NULL_STRING || STRING(iszName)[0] == 0 ) + return NULL; + + const CEntInfo *pInfo = pStartEntity ? GetEntInfoPtr( pStartEntity->GetRefEHandle() )->m_pNext : FirstEntInfo(); + + for ( ;pInfo; pInfo = pInfo->m_pNext ) + { + CBaseEntity *ent = (CBaseEntity *)pInfo->m_pEntity; + if ( !ent ) + { + DevWarning( "NULL entity in global entity list!\n" ); + continue; + } + + if ( !ent->m_iName.Get() ) + continue; + + if ( ent->m_iName.Get() == iszName ) + { + return ent; + } + } + + return NULL; +} + //----------------------------------------------------------------------------- // Purpose: // Input : pStartEntity - @@ -899,6 +1096,80 @@ CBaseEntity *CGlobalEntityList::FindEntityByClassnameNearest( const char *szName } +// From Alien Swarm SDK +CBaseEntity *CGlobalEntityList::FindEntityByClassnameNearestFast( string_t iszName, const Vector &vecSrc, float flRadius ) +{ + CBaseEntity *pEntity = NULL; + + // + // Check for matching class names within the search radius. + // + float flMaxDist2 = flRadius * flRadius; + if (flMaxDist2 == 0) + { + flMaxDist2 = MAX_TRACE_LENGTH * MAX_TRACE_LENGTH; + } + + CBaseEntity *pSearch = NULL; + while ((pSearch = gEntList.FindEntityByClassnameFast( pSearch, iszName )) != NULL) + { + if ( !pSearch->edict() ) + continue; + + float flDist2 = (pSearch->GetAbsOrigin() - vecSrc).LengthSqr(); + + if (flMaxDist2 > flDist2) + { + pEntity = pSearch; + flMaxDist2 = flDist2; + } + } + + return pEntity; +} + + +//----------------------------------------------------------------------------- +// Purpose: Finds the nearest entity by class name withing given search radius. +// From Alien Swarm SDK +// Input : szName - Entity name to search for. Treated as a target name first, +// then as an entity class name, ie "info_target". +// vecSrc - Center of search radius. +// flRadius - Search radius for classname search, 0 to search everywhere. +// Output : Returns a pointer to the found entity, NULL if none. +//----------------------------------------------------------------------------- +CBaseEntity *CGlobalEntityList::FindEntityByClassnameNearest2D( const char *szName, const Vector &vecSrc, float flRadius ) +{ + CBaseEntity *pEntity = NULL; + + // + // Check for matching class names within the search radius. + // + float flMaxDist2 = flRadius * flRadius; + if (flMaxDist2 == 0) + { + flMaxDist2 = MAX_TRACE_LENGTH * MAX_TRACE_LENGTH; + } + + CBaseEntity *pSearch = NULL; + while ((pSearch = gEntList.FindEntityByClassname( pSearch, szName )) != NULL) + { + if ( !pSearch->edict() ) + continue; + + float flDist2 = (pSearch->GetAbsOrigin().AsVector2D() - vecSrc.AsVector2D()).LengthSqr(); + + if (flMaxDist2 > flDist2) + { + pEntity = pSearch; + flMaxDist2 = flDist2; + } + } + + return pEntity; +} + + //----------------------------------------------------------------------------- // Purpose: Finds the first entity within radius distance by class name. diff --git a/sp/src/game/server/entitylist.h b/sp/src/game/server/entitylist.h index 505b196820..e2339ada1e 100644 --- a/sp/src/game/server/entitylist.h +++ b/sp/src/game/server/entitylist.h @@ -74,6 +74,26 @@ class CNullEntityFilter : public IEntityFindFilter virtual bool ShouldFindEntity( CBaseEntity *pEntity ) { return false; } virtual CBaseEntity *GetFilterResult( void ) { return NULL; } }; + +//----------------------------------------------------------------------------- +// Custom procedural names via VScript. This allows users to reference new '!' targets +// or override existing ones. It is capable of returning multiple entities when needed. +// +// This is useful if you want I/O and beyond to reference an entity/a number of entities +// which may possess differing or ambiguous targetnames, or if you want to reference an +// entity specific to the context of the operation (e.g. getting the searching entity's +// current weapon). +// +// For example, you could add a new procedural called "!first_child" which uses VScript to +// return the searching entity's first child entity. You could also add a procedural called +// "!children" which returns all of the searching entity's child entities. +//----------------------------------------------------------------------------- +struct CustomProcedural_t +{ + HSCRIPT hFunc; + const char *szName; + bool bCanReturnMultiple; +}; #endif //----------------------------------------------------------------------------- @@ -160,6 +180,7 @@ class CGlobalEntityList : public CBaseEntityList CBaseEntity *FindEntityByNameNearest( const char *szName, const Vector &vecSrc, float flRadius, CBaseEntity *pSearchingEntity = NULL, CBaseEntity *pActivator = NULL, CBaseEntity *pCaller = NULL ); CBaseEntity *FindEntityByNameWithin( CBaseEntity *pStartEntity, const char *szName, const Vector &vecSrc, float flRadius, CBaseEntity *pSearchingEntity = NULL, CBaseEntity *pActivator = NULL, CBaseEntity *pCaller = NULL ); CBaseEntity *FindEntityByClassnameNearest( const char *szName, const Vector &vecSrc, float flRadius ); + CBaseEntity *FindEntityByClassnameNearest2D( const char *szName, const Vector &vecSrc, float flRadius ); // From Alien Swarm SDK CBaseEntity *FindEntityByClassnameWithin( CBaseEntity *pStartEntity , const char *szName, const Vector &vecSrc, float flRadius ); CBaseEntity *FindEntityByClassnameWithin( CBaseEntity *pStartEntity , const char *szName, const Vector &vecMins, const Vector &vecMaxs ); @@ -176,7 +197,19 @@ class CGlobalEntityList : public CBaseEntityList CBaseEntity *FindEntityByNetname( CBaseEntity *pStartEntity, const char *szModelName ); CBaseEntity *FindEntityProcedural( const char *szName, CBaseEntity *pSearchingEntity = NULL, CBaseEntity *pActivator = NULL, CBaseEntity *pCaller = NULL ); - +#ifdef MAPBASE_VSCRIPT + CBaseEntity *FindEntityCustomProcedural( CBaseEntity *pStartEntity, const char *szName, CBaseEntity *pSearchingEntity = NULL, CBaseEntity *pActivator = NULL, CBaseEntity *pCaller = NULL ); + + void AddCustomProcedural( const char *pszName, HSCRIPT hFunc, bool bCanReturnMultiple ); + void RemoveCustomProcedural( const char *pszName ); +#endif + + // Fast versions that require a (real) string_t, and won't do wildcarding + // From Alien Swarm SDK + CBaseEntity *FindEntityByClassnameFast( CBaseEntity *pStartEntity, string_t iszClassname ); + CBaseEntity *FindEntityByClassnameNearestFast( string_t iszClassname, const Vector &vecSrc, float flRadius ); + CBaseEntity *FindEntityByNameFast( CBaseEntity *pStartEntity, string_t iszName ); + CGlobalEntityList(); // CBaseEntityList overrides. @@ -373,6 +406,7 @@ extern INotify *g_pNotify; void EntityTouch_Add( CBaseEntity *pEntity ); int AimTarget_ListCount(); int AimTarget_ListCopy( CBaseEntity *pList[], int listMax ); +CBaseEntity *AimTarget_ListElement( int iIndex ); void AimTarget_ForceRepopulateList(); void SimThink_EntityChanged( CBaseEntity *pEntity ); diff --git a/sp/src/game/server/env_instructor_hint.cpp b/sp/src/game/server/env_instructor_hint.cpp index 32b7504142..3ee20fb2c2 100644 --- a/sp/src/game/server/env_instructor_hint.cpp +++ b/sp/src/game/server/env_instructor_hint.cpp @@ -8,6 +8,9 @@ #include "cbase.h" #include "baseentity.h" #include "world.h" +#ifdef MAPBASE +#include "eventqueue.h" +#endif #ifdef INFESTED_DLL #include "asw_marine.h" @@ -26,6 +29,15 @@ class CEnvInstructorHint : public CPointEntity DECLARE_CLASS( CEnvInstructorHint, CPointEntity ); DECLARE_DATADESC(); +#ifdef MAPBASE + CEnvInstructorHint( void ); +#endif + virtual ~CEnvInstructorHint( void ) {} + +#ifdef MAPBASE + virtual void OnRestore( void ); +#endif + private: void InputShowHint( inputdata_t &inputdata ); void InputEndHint( inputdata_t &inputdata ); @@ -56,6 +68,10 @@ class CEnvInstructorHint : public CPointEntity #ifdef MAPBASE string_t m_iszStartSound; int m_iHintTargetPos; + float m_flActiveUntil; + CHandle m_hActivator; + EHANDLE m_hTarget; + bool m_bFilterByActivator; #endif }; @@ -85,8 +101,13 @@ BEGIN_DATADESC( CEnvInstructorHint ) #ifdef MAPBASE DEFINE_KEYFIELD( m_iszStartSound, FIELD_STRING, "hint_start_sound" ), DEFINE_KEYFIELD( m_iHintTargetPos, FIELD_INTEGER, "hint_target_pos" ), + + DEFINE_FIELD( m_flActiveUntil, FIELD_TIME ), + DEFINE_FIELD( m_hActivator, FIELD_EHANDLE ), + DEFINE_FIELD( m_hTarget, FIELD_EHANDLE ), + DEFINE_FIELD( m_bFilterByActivator, FIELD_BOOLEAN ), #endif - + DEFINE_INPUTFUNC( FIELD_STRING, "ShowHint", InputShowHint ), DEFINE_INPUTFUNC( FIELD_VOID, "EndHint", InputEndHint ), @@ -102,6 +123,44 @@ END_DATADESC() #define LOCATOR_ICON_FX_SHAKE_NARROW 0x00000040 #define LOCATOR_ICON_FX_STATIC 0x00000100 // This icon draws at a fixed location on the HUD. +#ifdef MAPBASE +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CEnvInstructorHint::CEnvInstructorHint( void ) +{ + m_hActivator = NULL; + m_hTarget = NULL; + m_bFilterByActivator = false; + m_flActiveUntil = -1.0f; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CEnvInstructorHint::OnRestore( void ) +{ + BaseClass::OnRestore(); + + int iTimeLeft = 0; + if ( m_flActiveUntil < 0.0f ) + { + return; + } + if ( m_iTimeout != 0 ) + { + iTimeLeft = static_cast( m_flActiveUntil - gpGlobals->curtime ); + if ( iTimeLeft <= 0 ) + { + return; + } + } + + int iOriginalTimeout = m_iTimeout; + m_iTimeout = iTimeLeft; + g_EventQueue.AddEvent( this, "ShowHint", 0.01f, NULL, this ); + m_iTimeout = iOriginalTimeout; +} +#endif + //----------------------------------------------------------------------------- // Purpose: Input handler for showing the message and/or playing the sound. //----------------------------------------------------------------------------- @@ -110,7 +169,15 @@ void CEnvInstructorHint::InputShowHint( inputdata_t &inputdata ) IGameEvent * event = gameeventmanager->CreateEvent( "instructor_server_hint_create", false ); if ( event ) { - CBaseEntity *pTargetEntity = gEntList.FindEntityByName( NULL, m_iszHintTargetEntity ); + CBaseEntity *pTargetEntity = NULL; + +#ifdef MAPBASE + pTargetEntity = m_hTarget; + + if ( pTargetEntity == NULL ) +#endif + pTargetEntity = gEntList.FindEntityByName( NULL, m_iszHintTargetEntity ); + if( pTargetEntity == NULL && !m_bStatic ) pTargetEntity = inputdata.pActivator; @@ -137,6 +204,15 @@ void CEnvInstructorHint::InputShowHint( inputdata_t &inputdata ) pActivator = pMarine->GetCommander(); } #else +#ifdef MAPBASE + if ( m_hActivator ) + { + pActivator = m_hActivator; + bFilterByActivator = m_bFilterByActivator; + } + else +#endif + if ( inputdata.value.StringID() != NULL_STRING ) { CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, inputdata.value.String() ); @@ -150,7 +226,7 @@ void CEnvInstructorHint::InputShowHint( inputdata_t &inputdata ) { if ( GameRules()->IsMultiplayer() == false ) { - pActivator = UTIL_GetLocalPlayer(); + pActivator = UTIL_GetLocalPlayer(); } else { @@ -190,6 +266,13 @@ void CEnvInstructorHint::InputShowHint( inputdata_t &inputdata ) #endif gameeventmanager->FireEvent( event ); + +#ifdef MAPBASE + m_flActiveUntil = gpGlobals->curtime + m_iTimeout; + m_hTarget = pTargetEntity; + m_hActivator = pActivator; + m_bFilterByActivator = bFilterByActivator; +#endif } } @@ -203,6 +286,13 @@ void CEnvInstructorHint::InputEndHint( inputdata_t &inputdata ) event->SetString( "hint_name", GetEntityName().ToCStr() ); gameeventmanager->FireEvent( event ); + +#ifdef MAPBASE + m_flActiveUntil = -1.0f; + m_hActivator = NULL; + m_hTarget = NULL; + m_bFilterByActivator = false; +#endif } } diff --git a/sp/src/game/server/env_screenoverlay.cpp b/sp/src/game/server/env_screenoverlay.cpp index 07bbf08df4..259a6fdcc2 100644 --- a/sp/src/game/server/env_screenoverlay.cpp +++ b/sp/src/game/server/env_screenoverlay.cpp @@ -39,6 +39,9 @@ class CEnvScreenOverlay : public CPointEntity CNetworkVar( float, m_flStartTime ); CNetworkVar( int, m_iDesiredOverlay ); CNetworkVar( bool, m_bIsActive ); +#ifdef MAPBASE + CNetworkVar( int, m_iOverlayIndex ); +#endif }; LINK_ENTITY_TO_CLASS( env_screenoverlay, CEnvScreenOverlay ); @@ -74,6 +77,9 @@ BEGIN_DATADESC( CEnvScreenOverlay ) DEFINE_FIELD( m_iDesiredOverlay, FIELD_INTEGER ), DEFINE_FIELD( m_flStartTime, FIELD_TIME ), DEFINE_FIELD( m_bIsActive, FIELD_BOOLEAN ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_iOverlayIndex, FIELD_INTEGER, "OverlayIndex" ), +#endif DEFINE_INPUTFUNC( FIELD_VOID, "StartOverlays", InputStartOverlay ), DEFINE_INPUTFUNC( FIELD_VOID, "StopOverlays", InputStopOverlay ), @@ -93,6 +99,9 @@ IMPLEMENT_SERVERCLASS_ST( CEnvScreenOverlay, DT_EnvScreenOverlay ) SendPropFloat( SENDINFO( m_flStartTime ), 32, SPROP_NOSCALE ), SendPropInt( SENDINFO( m_iDesiredOverlay ), 5 ), SendPropBool( SENDINFO( m_bIsActive ) ), +#ifdef MAPBASE + SendPropInt( SENDINFO( m_iOverlayIndex ), 5 ), +#endif END_SEND_TABLE() //----------------------------------------------------------------------------- @@ -103,6 +112,9 @@ CEnvScreenOverlay::CEnvScreenOverlay( void ) m_flStartTime = 0; m_iDesiredOverlay = 0; m_bIsActive = false; +#ifdef MAPBASE + m_iOverlayIndex = -1; +#endif } //----------------------------------------------------------------------------- diff --git a/sp/src/game/server/env_tonemap_controller.cpp b/sp/src/game/server/env_tonemap_controller.cpp index 39998e4ba4..95cf61358f 100644 --- a/sp/src/game/server/env_tonemap_controller.cpp +++ b/sp/src/game/server/env_tonemap_controller.cpp @@ -5,6 +5,7 @@ //============================================================================= #include "cbase.h" +#include "env_tonemap_controller.h" #include "baseentity.h" #include "entityoutput.h" #include "convar.h" @@ -16,50 +17,6 @@ ConVar mat_hdr_tonemapscale( "mat_hdr_tonemapscale", "1.0", FCVAR_CHEAT, "The HDR tonemap scale. 1 = Use autoexposure, 0 = eyes fully closed, 16 = eyes wide open." ); -// 0 - eyes fully closed / fully black -// 1 - nominal -// 16 - eyes wide open / fully white - -//----------------------------------------------------------------------------- -// Purpose: Entity that controls player's tonemap -//----------------------------------------------------------------------------- -class CEnvTonemapController : public CPointEntity -{ - DECLARE_CLASS( CEnvTonemapController, CPointEntity ); -public: - DECLARE_DATADESC(); - DECLARE_SERVERCLASS(); - - void Spawn( void ); - int UpdateTransmitState( void ); - void UpdateTonemapScaleBlend( void ); - - // Inputs - void InputSetTonemapScale( inputdata_t &inputdata ); - void InputBlendTonemapScale( inputdata_t &inputdata ); - void InputSetTonemapRate( inputdata_t &inputdata ); - void InputSetAutoExposureMin( inputdata_t &inputdata ); - void InputSetAutoExposureMax( inputdata_t &inputdata ); - void InputUseDefaultAutoExposure( inputdata_t &inputdata ); - void InputSetBloomScale( inputdata_t &inputdata ); - void InputUseDefaultBloomScale( inputdata_t &inputdata ); - void InputSetBloomScaleRange( inputdata_t &inputdata ); - -private: - float m_flBlendTonemapStart; // HDR Tonemap at the start of the blend - float m_flBlendTonemapEnd; // Target HDR Tonemap at the end of the blend - float m_flBlendEndTime; // Time at which the blend ends - float m_flBlendStartTime; // Time at which the blend started - - CNetworkVar( bool, m_bUseCustomAutoExposureMin ); - CNetworkVar( bool, m_bUseCustomAutoExposureMax ); - CNetworkVar( bool, m_bUseCustomBloomScale ); - CNetworkVar( float, m_flCustomAutoExposureMin ); - CNetworkVar( float, m_flCustomAutoExposureMax ); - CNetworkVar( float, m_flCustomBloomScale); - CNetworkVar( float, m_flCustomBloomScaleMinimum); -}; - LINK_ENTITY_TO_CLASS( env_tonemap_controller, CEnvTonemapController ); BEGIN_DATADESC( CEnvTonemapController ) @@ -120,27 +77,72 @@ int CEnvTonemapController::UpdateTransmitState() return SetTransmitState( FL_EDICT_ALWAYS ); } +#ifdef MAPBASE //----------------------------------------------------------------------------- -// Purpose: Set the tonemap scale to the specified value +// Purpose: //----------------------------------------------------------------------------- -void CEnvTonemapController::InputSetTonemapScale( inputdata_t &inputdata ) +bool CEnvTonemapController::KeyValue( const char *szKeyName, const char *szValue ) { - //Tony; in multiplayer, we check to see if the activator is a player, if they are, we trigger an input on them, and then get out. - //if there is no activator, or the activator is not a player; ie: LogicAuto, we set the 'global' values. - if ( ( gpGlobals->maxClients > 1 ) ) + if ( FStrEq( szKeyName, "TonemapScale" ) ) + { + float flTonemapScale = atof( szValue ); + if (flTonemapScale != -1.0f) + { + mat_hdr_tonemapscale.SetValue( flTonemapScale ); + } + } + else if (FStrEq( szKeyName, "TonemapRate" )) { - if ( inputdata.pActivator != NULL && inputdata.pActivator->IsPlayer() ) + float flTonemapRate = atof( szValue ); + if (flTonemapRate != -1.0f) { -// DevMsg("activator is a player: InputSetTonemapScale\n"); - CBasePlayer *pPlayer = ToBasePlayer(inputdata.pActivator); - if (pPlayer) + ConVarRef mat_hdr_manual_tonemap_rate( "mat_hdr_manual_tonemap_rate" ); + if ( mat_hdr_manual_tonemap_rate.IsValid() ) { - pPlayer->InputSetTonemapScale( inputdata ); - return; + mat_hdr_manual_tonemap_rate.SetValue( flTonemapRate ); } } } + else if (FStrEq( szKeyName, "AutoExposureMin" )) + { + float flAutoExposureMin = atof( szValue ); + if (flAutoExposureMin != -1.0f) + { + m_flCustomAutoExposureMin = flAutoExposureMin; + m_bUseCustomAutoExposureMin = true; + } + } + else if (FStrEq( szKeyName, "AutoExposureMax" )) + { + float flAutoExposureMax = atof( szValue ); + if (flAutoExposureMax != -1.0f) + { + m_flCustomAutoExposureMax = flAutoExposureMax; + m_bUseCustomAutoExposureMax = true; + } + } + else if (FStrEq( szKeyName, "BloomScale" )) + { + float flBloomScale = atof( szValue ); + if (flBloomScale != -1.0f) + { + m_flCustomBloomScale = flBloomScale; + m_flCustomBloomScaleMinimum = flBloomScale; + m_bUseCustomBloomScale = true; + } + } + else + return BaseClass::KeyValue( szKeyName, szValue ); + return true; +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: Set the tonemap scale to the specified value +//----------------------------------------------------------------------------- +void CEnvTonemapController::InputSetTonemapScale( inputdata_t &inputdata ) +{ float flRemapped = inputdata.value.Float(); mat_hdr_tonemapscale.SetValue( flRemapped ); } @@ -150,10 +152,6 @@ void CEnvTonemapController::InputSetTonemapScale( inputdata_t &inputdata ) //----------------------------------------------------------------------------- void CEnvTonemapController::InputBlendTonemapScale( inputdata_t &inputdata ) { - //Tony; TODO!!! -- tonemap scale blending does _not_ work properly in multiplayer.. - if ( ( gpGlobals->maxClients > 1 ) ) - return; - char parseString[255]; Q_strncpy(parseString, inputdata.value.String(), sizeof(parseString)); @@ -189,7 +187,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: \n", GetClassname(), GetDebugName() ); @@ -197,6 +195,9 @@ void CEnvTonemapController::InputSetBloomScaleRange( inputdata_t &inputdata ) } m_flCustomBloomScale=bloom_max; m_flCustomBloomScaleMinimum=bloom_min; +#ifdef MAPBASE + m_bUseCustomBloomScale = true; +#endif } //----------------------------------------------------------------------------- @@ -204,22 +205,6 @@ void CEnvTonemapController::InputSetBloomScaleRange( inputdata_t &inputdata ) //----------------------------------------------------------------------------- void CEnvTonemapController::InputSetTonemapRate( inputdata_t &inputdata ) { - //Tony; in multiplayer, we check to see if the activator is a player, if they are, we trigger an input on them, and then get out. - //if there is no activator, or the activator is not a player; ie: LogicAuto, we set the 'global' values. - if ( ( gpGlobals->maxClients > 1 ) ) - { - if ( inputdata.pActivator != NULL && inputdata.pActivator->IsPlayer() ) - { -// DevMsg("activator is a player: InputSetTonemapRate\n"); - CBasePlayer *pPlayer = ToBasePlayer(inputdata.pActivator); - if (pPlayer) - { - pPlayer->InputSetTonemapRate( inputdata ); - return; - } - } - } - // TODO: There should be a better way to do this. ConVarRef mat_hdr_manual_tonemap_rate( "mat_hdr_manual_tonemap_rate" ); if ( mat_hdr_manual_tonemap_rate.IsValid() ) @@ -251,22 +236,6 @@ void CEnvTonemapController::UpdateTonemapScaleBlend( void ) //----------------------------------------------------------------------------- void CEnvTonemapController::InputSetAutoExposureMin( inputdata_t &inputdata ) { - //Tony; in multiplayer, we check to see if the activator is a player, if they are, we trigger an input on them, and then get out. - //if there is no activator, or the activator is not a player; ie: LogicAuto, we set the 'global' values. - if ( ( gpGlobals->maxClients > 1 ) ) - { - if ( inputdata.pActivator != NULL && inputdata.pActivator->IsPlayer() ) - { -// DevMsg("activator is a player: InputSetAutoExposureMin\n"); - CBasePlayer *pPlayer = ToBasePlayer(inputdata.pActivator); - if (pPlayer) - { - pPlayer->InputSetAutoExposureMin( inputdata ); - return; - } - } - } - m_flCustomAutoExposureMin = inputdata.value.Float(); m_bUseCustomAutoExposureMin = true; } @@ -276,22 +245,6 @@ void CEnvTonemapController::InputSetAutoExposureMin( inputdata_t &inputdata ) //----------------------------------------------------------------------------- void CEnvTonemapController::InputSetAutoExposureMax( inputdata_t &inputdata ) { - //Tony; in multiplayer, we check to see if the activator is a player, if they are, we trigger an input on them, and then get out. - //if there is no activator, or the activator is not a player; ie: LogicAuto, we set the 'global' values. - if ( ( gpGlobals->maxClients > 1 ) ) - { - if ( inputdata.pActivator != NULL && inputdata.pActivator->IsPlayer() ) - { -// DevMsg("activator is a player: InputSetAutoExposureMax\n"); - CBasePlayer *pPlayer = ToBasePlayer(inputdata.pActivator); - if (pPlayer) - { - pPlayer->InputSetAutoExposureMax( inputdata ); - return; - } - } - } - m_flCustomAutoExposureMax = inputdata.value.Float(); m_bUseCustomAutoExposureMax = true; } @@ -301,22 +254,6 @@ void CEnvTonemapController::InputSetAutoExposureMax( inputdata_t &inputdata ) //----------------------------------------------------------------------------- void CEnvTonemapController::InputUseDefaultAutoExposure( inputdata_t &inputdata ) { - //Tony; in multiplayer, we check to see if the activator is a player, if they are, we trigger an input on them, and then get out. - //if there is no activator, or the activator is not a player; ie: LogicAuto, we set the 'global' values. - if ( ( gpGlobals->maxClients > 1 ) ) - { - if ( inputdata.pActivator != NULL && inputdata.pActivator->IsPlayer() ) - { -// DevMsg("activator is a player: InputUseDefaultAutoExposure\n"); - CBasePlayer *pPlayer = ToBasePlayer(inputdata.pActivator); - if (pPlayer) - { - pPlayer->InputUseDefaultAutoExposure( inputdata ); - return; - } - } - } - m_bUseCustomAutoExposureMin = false; m_bUseCustomAutoExposureMax = false; } @@ -326,22 +263,6 @@ void CEnvTonemapController::InputUseDefaultAutoExposure( inputdata_t &inputdata //----------------------------------------------------------------------------- void CEnvTonemapController::InputSetBloomScale( inputdata_t &inputdata ) { - //Tony; in multiplayer, we check to see if the activator is a player, if they are, we trigger an input on them, and then get out. - //if there is no activator, or the activator is not a player; ie: LogicAuto, we set the 'global' values. - if ( ( gpGlobals->maxClients > 1 ) ) - { - if ( inputdata.pActivator != NULL && inputdata.pActivator->IsPlayer() ) - { -// DevMsg("activator is a player: InputSetBloomScale\n"); - CBasePlayer *pPlayer = ToBasePlayer(inputdata.pActivator); - if (pPlayer) - { - pPlayer->InputSetBloomScale( inputdata ); - return; - } - } - } - m_flCustomBloomScale = inputdata.value.Float(); m_flCustomBloomScaleMinimum = m_flCustomBloomScale; m_bUseCustomBloomScale = true; @@ -352,21 +273,111 @@ void CEnvTonemapController::InputSetBloomScale( inputdata_t &inputdata ) //----------------------------------------------------------------------------- void CEnvTonemapController::InputUseDefaultBloomScale( inputdata_t &inputdata ) { - //Tony; in multiplayer, we check to see if the activator is a player, if they are, we trigger an input on them, and then get out. - //if there is no activator, or the activator is not a player; ie: LogicAuto, we set the 'global' values. - if ( ( gpGlobals->maxClients > 1 ) ) + m_bUseCustomBloomScale = false; +} + +#ifdef MAPBASE // From Alien Swarm SDK +//-------------------------------------------------------------------------------------------------------- +LINK_ENTITY_TO_CLASS( trigger_tonemap, CTonemapTrigger ); + +BEGIN_DATADESC( CTonemapTrigger ) + DEFINE_KEYFIELD( m_tonemapControllerName, FIELD_STRING, "TonemapName" ), +END_DATADESC() + + +//-------------------------------------------------------------------------------------------------------- +void CTonemapTrigger::Spawn( void ) +{ + AddSpawnFlags( SF_TRIGGER_ALLOW_CLIENTS ); + + BaseClass::Spawn(); + InitTrigger(); + + m_hTonemapController = gEntList.FindEntityByName( NULL, m_tonemapControllerName ); +} + + +//-------------------------------------------------------------------------------------------------------- +void CTonemapTrigger::StartTouch( CBaseEntity *other ) +{ + if ( !PassesTriggerFilters( other ) ) + return; + + BaseClass::StartTouch( other ); + + CBasePlayer *player = ToBasePlayer( other ); + if ( !player ) + return; + + player->OnTonemapTriggerStartTouch( this ); +} + + +//-------------------------------------------------------------------------------------------------------- +void CTonemapTrigger::EndTouch( CBaseEntity *other ) +{ + if ( !PassesTriggerFilters( other ) ) + return; + + BaseClass::EndTouch( other ); + + CBasePlayer *player = ToBasePlayer( other ); + if ( !player ) + return; + + player->OnTonemapTriggerEndTouch( this ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Clear out the tonemap controller. +//----------------------------------------------------------------------------- +void CTonemapSystem::LevelInitPreEntity( void ) +{ + m_hMasterController = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: On level load find the master fog controller. If no controller is +// set as Master, use the first fog controller found. +//----------------------------------------------------------------------------- +void CTonemapSystem::LevelInitPostEntity( void ) +{ + // Overall master controller + CEnvTonemapController *pTonemapController = NULL; + do { - if ( inputdata.pActivator != NULL && inputdata.pActivator->IsPlayer() ) + pTonemapController = static_cast( gEntList.FindEntityByClassname( pTonemapController, "env_tonemap_controller" ) ); + if ( pTonemapController ) { -// DevMsg("activator is a player: InputUseDefaultBloomScale\n"); - CBasePlayer *pPlayer = ToBasePlayer(inputdata.pActivator); - if (pPlayer) + if ( m_hMasterController == NULL ) + { + m_hMasterController = pTonemapController; + } + else { - pPlayer->InputUseDefaultBloomScale( inputdata ); - return; + if ( pTonemapController->IsMaster() ) + { + m_hMasterController = pTonemapController; + } } } - } + } while ( pTonemapController ); - m_bUseCustomBloomScale = false; + +} + + +//-------------------------------------------------------------------------------------------------------- +CTonemapSystem s_TonemapSystem( "TonemapSystem" ); + + +//-------------------------------------------------------------------------------------------------------- +CTonemapSystem *TheTonemapSystem( void ) +{ + return &s_TonemapSystem; } + + +//-------------------------------------------------------------------------------------------------------- +#endif diff --git a/sp/src/game/server/env_tonemap_controller.h b/sp/src/game/server/env_tonemap_controller.h new file mode 100644 index 0000000000..94e3e0f424 --- /dev/null +++ b/sp/src/game/server/env_tonemap_controller.h @@ -0,0 +1,140 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Note that this header exists in the Alien Swarm SDK, but not in stock Source SDK 2013. +// Although technically a new Mapbase file, it only serves to move otherwise identical code, +// so most code and repo conventions will pretend it was always there. +// +// -------------------------------------------------------------------- +// +// Purpose: +// +//=============================================================================// + +#ifndef ENV_TONEMAP_CONTROLLER_H +#define ENV_TONEMAP_CONTROLLER_H + +#include "triggers.h" + +// 0 - eyes fully closed / fully black +// 1 - nominal +// 16 - eyes wide open / fully white + +#ifdef MAPBASE // From Alien Swarm SDK +// Spawn Flags +#define SF_TONEMAP_MASTER 0x0001 +#endif + +//----------------------------------------------------------------------------- +// Purpose: Entity that controls player's tonemap +//----------------------------------------------------------------------------- +class CEnvTonemapController : public CPointEntity +{ + DECLARE_CLASS( CEnvTonemapController, CPointEntity ); +public: + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + void Spawn( void ); + int UpdateTransmitState( void ); + void UpdateTonemapScaleBlend( void ); + +#ifdef MAPBASE + bool IsMaster( void ) const { return HasSpawnFlags( SF_TONEMAP_MASTER ); } // From Alien Swarm SDK + + bool KeyValue( const char *szKeyName, const char *szValue ); +#endif + + // Inputs + void InputSetTonemapScale( inputdata_t &inputdata ); + void InputBlendTonemapScale( inputdata_t &inputdata ); + void InputSetTonemapRate( inputdata_t &inputdata ); + void InputSetAutoExposureMin( inputdata_t &inputdata ); + void InputSetAutoExposureMax( inputdata_t &inputdata ); + void InputUseDefaultAutoExposure( inputdata_t &inputdata ); + void InputSetBloomScale( inputdata_t &inputdata ); + void InputUseDefaultBloomScale( inputdata_t &inputdata ); + void InputSetBloomScaleRange( inputdata_t &inputdata ); + +private: + float m_flBlendTonemapStart; // HDR Tonemap at the start of the blend + float m_flBlendTonemapEnd; // Target HDR Tonemap at the end of the blend + float m_flBlendEndTime; // Time at which the blend ends + float m_flBlendStartTime; // Time at which the blend started + +#ifdef MAPBASE // From Alien Swarm SDK +public: +#endif + CNetworkVar( bool, m_bUseCustomAutoExposureMin ); + CNetworkVar( bool, m_bUseCustomAutoExposureMax ); + CNetworkVar( bool, m_bUseCustomBloomScale ); + CNetworkVar( float, m_flCustomAutoExposureMin ); + CNetworkVar( float, m_flCustomAutoExposureMax ); + CNetworkVar( float, m_flCustomBloomScale); + CNetworkVar( float, m_flCustomBloomScaleMinimum); +}; + +#ifdef MAPBASE // From Alien Swarm SDK +//-------------------------------------------------------------------------------------------------------- +class CTonemapTrigger : public CBaseTrigger +{ +public: + DECLARE_CLASS( CTonemapTrigger, CBaseTrigger ); + DECLARE_DATADESC(); + + virtual void Spawn( void ); + virtual void StartTouch( CBaseEntity *other ); + virtual void EndTouch( CBaseEntity *other ); + + CBaseEntity *GetTonemapController( void ) const; + +private: + string_t m_tonemapControllerName; + EHANDLE m_hTonemapController; +}; + + +//-------------------------------------------------------------------------------------------------------- +inline CBaseEntity *CTonemapTrigger::GetTonemapController( void ) const +{ + return m_hTonemapController.Get(); +} + + +//-------------------------------------------------------------------------------------------------------- +// Tonemap Controller System. +class CTonemapSystem : public CAutoGameSystem +{ +public: + + // Creation/Init. + CTonemapSystem( char const *name ) : CAutoGameSystem( name ) + { + m_hMasterController = NULL; + } + + ~CTonemapSystem() + { + m_hMasterController = NULL; + } + + virtual void LevelInitPreEntity(); + virtual void LevelInitPostEntity(); + CBaseEntity *GetMasterTonemapController( void ) const; + +private: + + EHANDLE m_hMasterController; +}; + + +//-------------------------------------------------------------------------------------------------------- +inline CBaseEntity *CTonemapSystem::GetMasterTonemapController( void ) const +{ + return m_hMasterController.Get(); +} + +//-------------------------------------------------------------------------------------------------------- +CTonemapSystem *TheTonemapSystem( void ); +#endif + +#endif //ENV_TONEMAP_CONTROLLER_H \ No newline at end of file diff --git a/sp/src/game/server/envmicrophone.cpp b/sp/src/game/server/envmicrophone.cpp index 29a59ead9a..23b1758cdd 100644 --- a/sp/src/game/server/envmicrophone.cpp +++ b/sp/src/game/server/envmicrophone.cpp @@ -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 > 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; + 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 - 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; + 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_nPitch = iPitch; - ep.m_pOrigin = &m_hSpeaker->GetAbsOrigin(); + ep.m_nChannel = CHAN_STATIC; + ep.m_flVolume = flVolume; + ep.m_nPitch = iPitch; + ep.m_pOrigin = &m_hSpeaker->GetAbsOrigin(); #endif - ep.m_flSoundTime = soundtime; - ep.m_nSpeakerEntity = entindex; + 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 ); + 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 diff --git a/sp/src/game/server/envmicrophone.h b/sp/src/game/server/envmicrophone.h index 11527d584b..e330099c10 100644 --- a/sp/src/game/server/envmicrophone.h +++ b/sp/src/game/server/envmicrophone.h @@ -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 @@ class CEnvMicrophone : public CPointEntity 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 @@ class CEnvMicrophone : public CPointEntity 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 @@ class CEnvMicrophone : public CPointEntity 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. diff --git a/sp/src/game/server/episodic/npc_magnusson.cpp b/sp/src/game/server/episodic/npc_magnusson.cpp index 310083536d..30e1f69783 100644 --- a/sp/src/game/server/episodic/npc_magnusson.cpp +++ b/sp/src/game/server/episodic/npc_magnusson.cpp @@ -37,6 +37,11 @@ class CNPC_Magnusson : public CAI_BaseActor 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 ¶ms ) { params.r1 = 209; params.g1 = 178; params.b1 = 178; return BaseClass::GetGameTextSpeechParams( params ); } +#endif }; LINK_ENTITY_TO_CLASS( npc_magnusson, CNPC_Magnusson ); diff --git a/sp/src/game/server/explode.cpp b/sp/src/game/server/explode.cpp index d03213567a..d9031dd424 100644 --- a/sp/src/game/server/explode.cpp +++ b/sp/src/game/server/explode.cpp @@ -412,7 +412,7 @@ void ExplosionCreate( const Vector ¢er, const QAngle &angles, CEnvExplosion *pExplosion = (CEnvExplosion*)CBaseEntity::Create( "env_explosion", center, angles, pOwner ); Q_snprintf( buf,sizeof(buf), "%3d", magnitude ); - char *szKeyName = "iMagnitude"; + const char *szKeyName = "iMagnitude"; char *szValue = buf; pExplosion->KeyValue( szKeyName, szValue ); diff --git a/sp/src/game/server/filters.cpp b/sp/src/game/server/filters.cpp index 8b7dcfc94e..84021a952c 100644 --- a/sp/src/game/server/filters.cpp +++ b/sp/src/game/server/filters.cpp @@ -1522,6 +1522,7 @@ class CFilterSurfaceData : public CBaseFilter void Activate() { + BaseClass::Activate(); ParseSurfaceIndex(); } diff --git a/sp/src/game/server/fogvolume.cpp b/sp/src/game/server/fogvolume.cpp new file mode 100644 index 0000000000..f2f9a718b4 --- /dev/null +++ b/sp/src/game/server/fogvolume.cpp @@ -0,0 +1,153 @@ +//-------------------------------------------------------------------------------------------------------- +// Copyright (c) 2007 Turtle Rock Studios, Inc. - All Rights Reserved + +#include "cbase.h" +#include "fogvolume.h" +#include "collisionutils.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +CUtlVector< CFogVolume * > TheFogVolumes; + +ConVar fog_volume_debug( "fog_volume_debug", "0", 0, "If enabled, prints diagnostic information about the current fog volume" ); + +//-------------------------------------------------------------------------------------------------------- +LINK_ENTITY_TO_CLASS(fog_volume, CFogVolume); + +BEGIN_DATADESC( CFogVolume ) + + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + + DEFINE_KEYFIELD( m_fogName, FIELD_STRING, "FogName" ), + DEFINE_KEYFIELD( m_postProcessName, FIELD_STRING, "PostProcessName" ), + DEFINE_KEYFIELD( m_colorCorrectionName, FIELD_STRING, "ColorCorrectionName" ), + DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), + + DEFINE_FIELD( m_hFogController, FIELD_EHANDLE ), + DEFINE_FIELD( m_hPostProcessController, FIELD_EHANDLE ), + DEFINE_FIELD( m_hColorCorrectionController, FIELD_EHANDLE ), + +END_DATADESC() + + +//-------------------------------------------------------------------------------------------------------- +CFogVolume *CFogVolume::FindFogVolumeForPosition( const Vector &position ) +{ + CFogVolume *fogVolume = NULL; + for ( int i=0; iCollisionProp()->WorldToCollisionSpace( position, &vecRelativeCenter ); + if ( IsBoxIntersectingSphere( fogVolume->CollisionProp()->OBBMins(), fogVolume->CollisionProp()->OBBMaxs(), vecRelativeCenter, 1.0f ) ) + { + break; + } + fogVolume = NULL; + } + + // This doesn't work well if there are multiple players or multiple fog volume queries per frame; might want to relocate this if that's the case + if ( fog_volume_debug.GetBool() ) + { + if ( fogVolume ) + { + char fogVolumeName[256]; + fogVolume->GetKeyValue( "targetname", fogVolumeName, 256 ); + engine->Con_NPrintf( 0, "Fog Volume ""%s"" found at position (%f %f %f)", fogVolumeName, position.x, position.y, position.z ); + engine->Con_NPrintf( 1, "Fog: %s, post process: %s, color correct: %s", fogVolume->m_fogName, fogVolume->m_postProcessName, fogVolume->m_colorCorrectionName ); + } + else + { + engine->Con_NPrintf( 0, "No Fog Volume found at given position (%f %f %f)", position.x, position.y, position.z ); + } + } + + return fogVolume; +} + + +//-------------------------------------------------------------------------------------------------------- +CFogVolume::CFogVolume() : + BaseClass(), + m_bDisabled( false ), + m_bInFogVolumesList( false ) +{ +} + + +//-------------------------------------------------------------------------------------------------------- +CFogVolume::~CFogVolume() +{ + RemoveFromGlobalList(); +} + + +//-------------------------------------------------------------------------------------------------------- +void CFogVolume::Spawn( void ) +{ + BaseClass::Spawn(); + + SetSolid( SOLID_BSP ); + SetSolidFlags( FSOLID_NOT_SOLID ); + SetModel( STRING( GetModelName() ) ); +} + + +//-------------------------------------------------------------------------------------------------------- +void CFogVolume::AddToGlobalList() +{ + if ( !m_bInFogVolumesList ) + { + TheFogVolumes.AddToTail( this ); + m_bInFogVolumesList = true; + } +} + + +//-------------------------------------------------------------------------------------------------------- +void CFogVolume::RemoveFromGlobalList() +{ + if ( m_bInFogVolumesList ) + { + TheFogVolumes.FindAndRemove( this ); + m_bInFogVolumesList = false; + } +} + + +//---------------------------------------------------------------------------- +void CFogVolume::InputEnable( inputdata_t &data ) +{ + m_bDisabled = false; + AddToGlobalList(); +} + + +//---------------------------------------------------------------------------- +void CFogVolume::InputDisable( inputdata_t &data ) +{ + m_bDisabled = true; + RemoveFromGlobalList(); +} + + +//---------------------------------------------------------------------------- +// Called when the level loads or is restored +//---------------------------------------------------------------------------- +void CFogVolume::Activate() +{ + BaseClass::Activate(); + + m_hFogController = dynamic_cast< CFogController* >( gEntList.FindEntityByName( NULL, m_fogName ) ); + m_hPostProcessController = dynamic_cast< CPostProcessController* >( gEntList.FindEntityByName( NULL, m_postProcessName ) ); + m_hColorCorrectionController = dynamic_cast< CColorCorrection* >( gEntList.FindEntityByName( NULL, m_colorCorrectionName ) ); + + if ( !m_bDisabled ) + { + AddToGlobalList(); + } +} diff --git a/sp/src/game/server/fogvolume.h b/sp/src/game/server/fogvolume.h new file mode 100644 index 0000000000..6bd5880ae1 --- /dev/null +++ b/sp/src/game/server/fogvolume.h @@ -0,0 +1,74 @@ +//-------------------------------------------------------------------------------------------------------- +// Copyright (c) 2007 Turtle Rock Studios, Inc. - All Rights Reserved + +#ifndef FOG_VOLUME_H +#define FOG_VOLUME_H + +#ifdef _WIN32 +#pragma once +#endif + + +class CFogController; +class CPostProcessController; +class CColorCorrection; + + +//-------------------------------------------------------------------------------------------------------- +// Fog volume entity +class CFogVolume : public CServerOnlyEntity +{ + DECLARE_CLASS( CFogVolume, CServerOnlyEntity ); + DECLARE_DATADESC(); + +public: + CFogVolume(); + virtual ~CFogVolume(); + virtual void Spawn( void ); + virtual void Activate(); + + static CFogVolume *FindFogVolumeForPosition( const Vector &position ); + + const char *GetFogControllerName() const + { + return STRING( m_fogName ); + } + + CFogController* GetFogController( ) const + { + return m_hFogController.Get(); + } + + CPostProcessController* GetPostProcessController( ) const + { + return m_hPostProcessController.Get(); + } + + CColorCorrection* GetColorCorrectionController( ) const + { + return m_hColorCorrectionController.Get(); + } + + void InputEnable( inputdata_t &data ); + void InputDisable( inputdata_t &data ); + +private: + string_t m_fogName; + string_t m_postProcessName; + string_t m_colorCorrectionName; + + CHandle< CFogController > m_hFogController; + CHandle< CPostProcessController > m_hPostProcessController; + CHandle< CColorCorrection > m_hColorCorrectionController; + + bool m_bDisabled; + bool m_bInFogVolumesList; + + void AddToGlobalList(); + void RemoveFromGlobalList(); +}; + +extern CUtlVector< CFogVolume * > TheFogVolumes; + + +#endif // FOG_VOLUME_H \ No newline at end of file diff --git a/sp/src/game/server/func_break.cpp b/sp/src/game/server/func_break.cpp index bb7dff50fe..fd4839aec3 100644 --- a/sp/src/game/server/func_break.cpp +++ b/sp/src/game/server/func_break.cpp @@ -221,6 +221,10 @@ bool CBreakable::KeyValue( const char *szKeyName, const char *szValue ) if ( object > 0 && object < ARRAYSIZE(pSpawnObjects) ) m_iszSpawnObject = MAKE_STRING( pSpawnObjects[object] ); #ifdef MAPBASE + // "0" is the default value of a "choices" field in Hammer, representing nothing selected + // atoi() returning 0 may also indicate a failed conversion, so check szValue directly + else if ( FStrEq( szValue, "0" ) ) + m_iszSpawnObject = NULL_STRING; else m_iszSpawnObject = AllocPooledString(szValue); #endif diff --git a/sp/src/game/server/func_lod.cpp b/sp/src/game/server/func_lod.cpp index e1c41c1238..7a71e270bb 100644 --- a/sp/src/game/server/func_lod.cpp +++ b/sp/src/game/server/func_lod.cpp @@ -112,6 +112,10 @@ bool CFunc_LOD::KeyValue( const char *szKeyName, const char *szValue ) { m_fDisappearMaxDist = (float)atof(szValue); } + else if (FStrEq(szKeyName, "DisappearMinDist")) // Forwards compatibility + { + m_fDisappearDist = (float)atof(szValue); + } #endif else if (FStrEq(szKeyName, "Solid")) { diff --git a/sp/src/game/server/genericactor.cpp b/sp/src/game/server/genericactor.cpp index bb0dfe0bc7..8f60651996 100644 --- a/sp/src/game/server/genericactor.cpp +++ b/sp/src/game/server/genericactor.cpp @@ -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 ); } //----------------------------------------------------------------------------- diff --git a/sp/src/game/server/hl2/ai_behavior_actbusy.cpp b/sp/src/game/server/hl2/ai_behavior_actbusy.cpp index 9a4f09e6d9..c5ad733d8b 100644 --- a/sp/src/game/server/hl2/ai_behavior_actbusy.cpp +++ b/sp/src/game/server/hl2/ai_behavior_actbusy.cpp @@ -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 diff --git a/sp/src/game/server/hl2/ai_behavior_actbusy.h b/sp/src/game/server/hl2/ai_behavior_actbusy.h index a33dd438ff..264fdb3d20 100644 --- a/sp/src/game/server/hl2/ai_behavior_actbusy.h +++ b/sp/src/game/server/hl2/ai_behavior_actbusy.h @@ -153,6 +153,10 @@ class CAI_ActBusyBehavior : public CAI_SimpleBehavior bool IsInSafeZone( CBaseEntity *pEntity ); int CountEnemiesInSafeZone(); +#ifdef MAPBASE + CAI_ActBusyGoal *GetActBusyGoal() const { return m_hActBusyGoal; } +#endif + private: virtual int SelectSchedule( void ); int SelectScheduleForLeaving( void ); diff --git a/sp/src/game/server/hl2/ai_behavior_functank.cpp b/sp/src/game/server/hl2/ai_behavior_functank.cpp index f0a3260171..89f7922872 100644 --- a/sp/src/game/server/hl2/ai_behavior_functank.cpp +++ b/sp/src/game/server/hl2/ai_behavior_functank.cpp @@ -110,6 +110,28 @@ bool CAI_FuncTankBehavior::IsInterruptable( void ) return BaseClass::IsInterruptable(); } + +ConVar ai_tank_allow_expanded_npcs( "ai_tank_allow_expanded_npcs", "1", FCVAR_NONE, "Allows Father Grigori, Barney, and vortigaunts to automatically man func_tanks." ); + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAI_FuncTankBehavior::CanManTank( CFuncTank *pTank, bool bForced ) +{ + if (!bForced) + { + // In order to prevent potential problems in existing maps, Father Grigori, Barney, and vortigaunts can be set to not automatically man func_tanks by default. + if (ai_tank_allow_expanded_npcs.GetBool() == false) + { + const char *pszClass = GetOuter()->GetClassname(); + if ( FStrEq( pszClass, "npc_monk" ) || FStrEq( pszClass, "npc_barney" ) || FStrEq( pszClass, "npc_vortigaunt" ) ) + return false; + } + } + + return true; +} #endif //----------------------------------------------------------------------------- diff --git a/sp/src/game/server/hl2/ai_behavior_functank.h b/sp/src/game/server/hl2/ai_behavior_functank.h index a9f1949697..75d5b8dfb7 100644 --- a/sp/src/game/server/hl2/ai_behavior_functank.h +++ b/sp/src/game/server/hl2/ai_behavior_functank.h @@ -51,6 +51,8 @@ class CAI_FuncTankBehavior : public CAI_SimpleBehavior void PrescheduleThink(); #ifdef MAPBASE bool IsInterruptable( void ); + + bool CanManTank( CFuncTank *pTank, bool bForced ); #endif Activity NPC_TranslateActivity( Activity activity ); diff --git a/sp/src/game/server/hl2/ai_behavior_police.cpp b/sp/src/game/server/hl2/ai_behavior_police.cpp index ffb8dd9648..8dc5ec4008 100644 --- a/sp/src/game/server/hl2/ai_behavior_police.cpp +++ b/sp/src/game/server/hl2/ai_behavior_police.cpp @@ -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 } diff --git a/sp/src/game/server/hl2/basehlcombatweapon.cpp b/sp/src/game/server/hl2/basehlcombatweapon.cpp index 33a700fc8f..894c290b22 100644 --- a/sp/src/game/server/hl2/basehlcombatweapon.cpp +++ b/sp/src/game/server/hl2/basehlcombatweapon.cpp @@ -394,6 +394,10 @@ void CHLSelectFireMachineGun::SecondaryAttack( void ) { m_iSecondaryAttacks++; gamestats->Event_WeaponFired( pOwner, false, GetClassname() ); + +#ifdef MAPBASE + pOwner->SetAnimation( PLAYER_ATTACK2 ); +#endif } } diff --git a/sp/src/game/server/hl2/cbasehelicopter.h b/sp/src/game/server/hl2/cbasehelicopter.h index fcf7429107..714f62f400 100644 --- a/sp/src/game/server/hl2/cbasehelicopter.h +++ b/sp/src/game/server/hl2/cbasehelicopter.h @@ -267,6 +267,10 @@ class CAvoidSphere : public CBaseEntity typedef CHandle AvoidSphereHandle_t; float m_flRadius; +#ifdef MAPBASE + string_t m_iszAvoidFilter; + EHANDLE m_hAvoidFilter; +#endif static CUtlVector< AvoidSphereHandle_t > s_AvoidSpheres; }; diff --git a/sp/src/game/server/hl2/combine_mine.cpp b/sp/src/game/server/hl2/combine_mine.cpp index 4e86eb8541..aa9bdf2961 100644 --- a/sp/src/game/server/hl2/combine_mine.cpp +++ b/sp/src/game/server/hl2/combine_mine.cpp @@ -58,6 +58,10 @@ char *pszMineStateNames[] = // Approximate radius of the bomb's model #define BOUNCEBOMB_RADIUS 24 +#ifdef MAPBASE +ConVar combine_mine_trace_dist( "combine_mine_trace_dist", "1024" ); +#endif + BEGIN_DATADESC( CBounceBomb ) DEFINE_THINKFUNC( ExplodeThink ), DEFINE_ENTITYFUNC( ExplodeTouch ), @@ -92,6 +96,7 @@ BEGIN_DATADESC( CBounceBomb ) DEFINE_KEYFIELD( m_bCheapWarnSound, FIELD_BOOLEAN, "CheapWarnSound" ), DEFINE_KEYFIELD( m_iLOSMask, FIELD_INTEGER, "LOSMask" ), DEFINE_INPUT( m_bUnavoidable, FIELD_BOOLEAN, "SetUnavoidable" ), + DEFINE_KEYFIELD( m_vecPlantOrientation, FIELD_VECTOR, "PlantOrientation" ), #endif DEFINE_KEYFIELD( m_iModification, FIELD_INTEGER, "Modification" ), @@ -129,6 +134,8 @@ BEGIN_DATADESC( CBounceBomb ) #ifdef MAPBASE DEFINE_INPUTFUNC( FIELD_VOID, "Bounce", InputBounce ), DEFINE_INPUTFUNC( FIELD_EHANDLE, "BounceAtTarget", InputBounceAtTarget ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetPlantOrientation", InputSetPlantOrientation ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetPlantOrientationRaw", InputSetPlantOrientationRaw ), DEFINE_OUTPUT( m_OnTriggered, "OnTriggered" ), DEFINE_OUTPUT( m_OnExplode, "OnExplode" ), @@ -266,6 +273,14 @@ void CBounceBomb::Spawn() // pretend like the player set me down. m_bPlacedByPlayer = true; } + +#ifdef MAPBASE + if (m_vecPlantOrientation != vec3_invalid) + { + // Turn angles into direction + AngleVectors( QAngle( m_vecPlantOrientation.x, m_vecPlantOrientation.y, m_vecPlantOrientation.z ), &m_vecPlantOrientation ); + } +#endif } //--------------------------------------------------------- @@ -694,7 +709,20 @@ void CBounceBomb::SettleThink() { // If i'm not resting on the world, jump randomly. trace_t tr; - UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, 1024 ), MASK_SHOT|CONTENTS_GRATE, this, COLLISION_GROUP_NONE, &tr ); +#ifdef MAPBASE + Vector vecTraceDir; + if (m_vecPlantOrientation != vec3_invalid) + { + vecTraceDir = m_vecPlantOrientation * combine_mine_trace_dist.GetFloat(); + } + else + { + vecTraceDir = Vector( 0, 0, combine_mine_trace_dist.GetFloat() ); + } +#else + Vector vecTraceDir = Vector( 0, 0, 1024 ); +#endif + UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - vecTraceDir, MASK_SHOT|CONTENTS_GRATE, this, COLLISION_GROUP_NONE, &tr ); bool bHop = false; if( tr.m_pEnt ) @@ -728,6 +756,20 @@ void CBounceBomb::SettleThink() // Check for upside-down Vector vecUp; GetVectors( NULL, NULL, &vecUp ); +#ifdef MAPBASE + if (m_vecPlantOrientation != vec3_invalid) + { + float flDiff = abs(m_vecPlantOrientation.z - vecUp.z); + if ( flDiff >= 0.2f ) + { + // Landed upside down. Right self + Vector vecForce( 0, 0, 2500 ); + Flip( vecForce, AngularImpulse( 60, 0, 0 ) ); + return; + } + } + else +#endif if( vecUp.z <= 0.8 ) { // Landed upside down. Right self @@ -1442,6 +1484,22 @@ void CBounceBomb::InputBounceAtTarget( inputdata_t &inputdata ) m_hNearestNPC = inputdata.value.Entity(); SetMineState(MINE_STATE_TRIGGERED); } + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CBounceBomb::InputSetPlantOrientation( inputdata_t &inputdata ) +{ + Vector vecInput; + inputdata.value.Vector3D( vecInput ); + AngleVectors( QAngle(vecInput.x, vecInput.y, vecInput.z), &m_vecPlantOrientation ); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CBounceBomb::InputSetPlantOrientationRaw( inputdata_t &inputdata ) +{ + inputdata.value.Vector3D( m_vecPlantOrientation ); +} #endif //--------------------------------------------------------- diff --git a/sp/src/game/server/hl2/combine_mine.h b/sp/src/game/server/hl2/combine_mine.h index 4495afb358..097968dbe7 100644 --- a/sp/src/game/server/hl2/combine_mine.h +++ b/sp/src/game/server/hl2/combine_mine.h @@ -34,7 +34,7 @@ class CBounceBomb : public CBaseAnimating, public CDefaultPlayerPickupVPhysics public: #ifdef MAPBASE - CBounceBomb() { m_pWarnSound = NULL; m_bPlacedByPlayer = false; m_flExplosionDelay = 0.5f; m_iLOSMask = MASK_SOLID_BRUSHONLY; } + CBounceBomb() { m_pWarnSound = NULL; m_bPlacedByPlayer = false; m_flExplosionDelay = 0.5f; m_iLOSMask = MASK_SOLID_BRUSHONLY; m_vecPlantOrientation = vec3_invalid; } #else CBounceBomb() { m_pWarnSound = NULL; m_bPlacedByPlayer = false; } #endif @@ -130,6 +130,10 @@ class CBounceBomb : public CBaseAnimating, public CDefaultPlayerPickupVPhysics int m_iLOSMask; bool m_bUnavoidable; + + // What direction the mine should be facing when planting itself (i.e. facing up, facing left, etc.) + // vec3_invalid = use default (0 0 1 or -90 0 0) + Vector m_vecPlantOrientation; #endif bool m_bPlacedByPlayer; @@ -164,6 +168,8 @@ class CBounceBomb : public CBaseAnimating, public CDefaultPlayerPickupVPhysics #ifdef MAPBASE void InputBounce( inputdata_t &inputdata ); void InputBounceAtTarget( inputdata_t &inputdata ); + void InputSetPlantOrientation( inputdata_t &inputdata ); + void InputSetPlantOrientationRaw( inputdata_t &inputdata ); COutputEvent m_OnTriggered; COutputEvent m_OnExplode; #endif diff --git a/sp/src/game/server/hl2/env_speaker.cpp b/sp/src/game/server/hl2/env_speaker.cpp index 7b193a9b7d..ad46a25c85 100644 --- a/sp/src/game/server/hl2/env_speaker.cpp +++ b/sp/src/game/server/hl2/env_speaker.cpp @@ -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(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); diff --git a/sp/src/game/server/hl2/func_tank.cpp b/sp/src/game/server/hl2/func_tank.cpp index 963a0e54a3..c446257597 100644 --- a/sp/src/game/server/hl2/func_tank.cpp +++ b/sp/src/game/server/hl2/func_tank.cpp @@ -402,7 +402,11 @@ void CFuncTank::InputFindNPCToManTank( inputdata_t &inputdata ) { // Verify the npc has the func_tank controller behavior. CAI_FuncTankBehavior *pBehavior; +#ifdef MAPBASE + if ( pNPC->GetBehavior( &pBehavior ) && pBehavior->CanManTank( this, true ) ) +#else if ( pNPC->GetBehavior( &pBehavior ) ) +#endif { m_hController = pNPC; pBehavior->SetFuncTank( this ); @@ -439,7 +443,7 @@ void CFuncTank::InputTeleportNPCToManTank( inputdata_t &inputdata ) { // Verify the npc has the func_tank controller behavior. CAI_FuncTankBehavior *pBehavior; - if ( pNPC->GetBehavior( &pBehavior ) ) + if ( pNPC->GetBehavior( &pBehavior ) && pBehavior->CanManTank( this, true ) ) { Vector vecVec; QAngle angAng; @@ -512,7 +516,7 @@ void CFuncTank::InputForceNPCToManTank( inputdata_t &inputdata ) { // Verify the npc has the func_tank controller behavior. CAI_FuncTankBehavior *pBehavior; - if ( pNPC->GetBehavior( &pBehavior ) ) + if ( pNPC->GetBehavior( &pBehavior ) && pBehavior->CanManTank( this, true ) ) { // Set the forced condition pBehavior->SetCondition( CAI_FuncTankBehavior::COND_FUNCTANK_FORCED ); @@ -627,7 +631,11 @@ void CFuncTank::NPC_FindController( void ) continue; CAI_FuncTankBehavior *pBehavior; +#ifdef MAPBASE + if ( pNPC->GetBehavior( &pBehavior ) && pBehavior->CanManTank( this, false ) ) +#else if ( pNPC->GetBehavior( &pBehavior ) ) +#endif { // Don't mount the func_tank if your "enemy" is within X feet or it or the npc. CBaseEntity *pEnemy = pNPC->GetEnemy(); diff --git a/sp/src/game/server/hl2/hl2_client.cpp b/sp/src/game/server/hl2/hl2_client.cpp index 4cd9dd7bd2..12dc248f5c 100644 --- a/sp/src/game/server/hl2/hl2_client.cpp +++ b/sp/src/game/server/hl2/hl2_client.cpp @@ -142,7 +142,7 @@ void respawn( CBaseEntity *pEdict, bool fCopyCorpse ) { // In SP respawns, only create corpse if drawing externally CBasePlayer *pPlayer = (CBasePlayer*)pEdict; - if ( fCopyCorpse && pPlayer->m_bDrawPlayerModelExternally ) + if ( fCopyCorpse && pPlayer->GetDrawPlayerModelExternally() ) { // make a copy of the dead body for appearances sake pPlayer->CreateCorpse(); diff --git a/sp/src/game/server/hl2/hl2_player.cpp b/sp/src/game/server/hl2/hl2_player.cpp index fe52380617..d44849e7f0 100644 --- a/sp/src/game/server/hl2/hl2_player.cpp +++ b/sp/src/game/server/hl2/hl2_player.cpp @@ -118,6 +118,11 @@ ConVar sv_stickysprint("sv_stickysprint", "0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBO #ifdef MAPBASE ConVar player_autoswitch_enabled( "player_autoswitch_enabled", "1", FCVAR_NONE, "This convar was added by Mapbase to toggle whether players automatically switch to their ''best'' weapon upon picking up ammo for it after it was dry." ); + +#ifdef SP_ANIM_STATE +ConVar hl2_use_sp_animstate( "hl2_use_sp_animstate", "1", FCVAR_NONE, "Allows SP HL2 players to use HL2:DM animations for custom player models. (changes may not apply until model is reloaded)" ); +#endif + #endif #define FLASH_DRAIN_TIME 1.1111 // 100 units / 90 secs @@ -259,6 +264,7 @@ class CLogicPlayerProxy : public CLogicalEntity void InputSetHandModel( inputdata_t &inputdata ); void InputSetHandModelSkin( inputdata_t &inputdata ); + void InputSetHandModelBodyGroup( inputdata_t &inputdata ); void InputSetPlayerModel( inputdata_t &inputdata ); void InputSetPlayerDrawExternally( inputdata_t &inputdata ); @@ -325,39 +331,41 @@ class CCommandRedirect : public CBaseTrigger } // Will the command point change? - // True = Judged guilty and changed. - // False = Judged not guilty and unchanged. - bool GetVerdict(Vector *defendant, CHL2_Player *pPlayer) + // True = Command point changes + // False = Comand point doesn't change + bool TestRedirect(Vector *vecNewCommandPoint, CHL2_Player *pPlayer) { - // Deliver goal to relevant destinations before sentencing. - m_OnCommandGoal.Set(*defendant, pPlayer, this); + // Output the goal before doing anything else. + m_OnCommandGoal.Set(*vecNewCommandPoint, pPlayer, this); if (m_target == NULL_STRING) { - // Abort sentencing. + // Not targeting anything. Don't redirect and just leave it at the output return false; } else if (FStrEq(STRING(m_target), "-1")) { - // Deliver verdict immediately. - *defendant = Vector(0, 0, 0); - return false; + // Completely cancel the squad command. + *vecNewCommandPoint = vec3_origin; + return true; } else { - // Locate entity of interest. // Player is caller. // Player squad representative is activator. CBaseEntity *pEntOfInterest = gEntList.FindEntityGeneric(NULL, STRING(m_target), this, pPlayer->GetSquadCommandRepresentative(), pPlayer); if (pEntOfInterest) { - // Deliver their local origin. - *defendant = pEntOfInterest->GetLocalOrigin(); + // Use the entity's absolute origin as the new command point. + *vecNewCommandPoint = pEntOfInterest->GetAbsOrigin(); return true; } + else + { + Warning("%s couldn't find target entity \"%s\"\n", GetDebugName(), STRING(m_target)); + } } - // No sentence. return false; } @@ -615,6 +623,10 @@ BEGIN_ENT_SCRIPTDESC( CHL2_Player, CBasePlayer, "The HL2 player entity." ) DEFINE_SCRIPTFUNC( RemoveCustomSuitDevice, "Removes a custom suit device ID. (1-3)" ) DEFINE_SCRIPTFUNC( IsCustomSuitDeviceActive, "Checks if a custom suit device is active." ) +#ifdef SP_ANIM_STATE + DEFINE_SCRIPTFUNC( AddAnimStateLayer, "Adds a custom sequence index as a misc. layer for the singleplayer anim state, wtih parameters for blending in/out, setting the playback rate, holding the animation at the end, and only playing when the player is still." ) +#endif + END_SCRIPTDESC(); #endif @@ -660,6 +672,9 @@ CSuitPowerDevice SuitDeviceCustom[] = IMPLEMENT_SERVERCLASS_ST(CHL2_Player, DT_HL2_Player) SendPropDataTable(SENDINFO_DT(m_HL2Local), &REFERENCE_SEND_TABLE(DT_HL2Local), SendProxy_SendLocalDataTable), SendPropBool( SENDINFO(m_fIsSprinting) ), +#ifdef SP_ANIM_STATE + SendPropFloat( SENDINFO(m_flAnimRenderYaw), 0, SPROP_NOSCALE ), +#endif END_SEND_TABLE() @@ -1143,6 +1158,16 @@ void CHL2_Player::PostThink( void ) { HandleAdmireGlovesAnimation(); } + +#ifdef SP_ANIM_STATE + if (m_pPlayerAnimState) + { + QAngle angEyeAngles = EyeAngles(); + m_pPlayerAnimState->Update( angEyeAngles.y, angEyeAngles.x ); + + m_flAnimRenderYaw.Set( m_pPlayerAnimState->GetRenderAngles().y ); + } +#endif } void CHL2_Player::StartAdmireGlovesAnimation( void ) @@ -1352,193 +1377,108 @@ void CHL2_Player::SpawnedAtPoint( CBaseEntity *pSpawnPoint ) //----------------------------------------------------------------------------- -ConVar hl2_use_hl2dm_anims( "hl2_use_hl2dm_anims", "0", FCVAR_NONE, "Allows SP HL2 players to use HL2:DM animations (for custom player models)" ); +ConVar player_use_anim_enabled( "player_use_anim_enabled", "1" ); +ConVar player_use_anim_heavy_mass( "player_use_anim_heavy_mass", "20.0" ); -void CHL2_Player::ResetAnimation( void ) +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Activity CHL2_Player::Weapon_TranslateActivity( Activity baseAct, bool *pRequired ) { - if (!hl2_use_hl2dm_anims.GetBool()) - return; - - if (IsAlive()) - { - SetSequence( -1 ); - SetActivity( ACT_INVALID ); - - if (!GetAbsVelocity().x && !GetAbsVelocity().y) - SetAnimation( PLAYER_IDLE ); - else if ((GetAbsVelocity().x || GetAbsVelocity().y) && (GetFlags() & FL_ONGROUND)) - SetAnimation( PLAYER_WALK ); - else if (GetWaterLevel() > 1) - SetAnimation( PLAYER_WALK ); + Activity weaponTranslation = BaseClass::Weapon_TranslateActivity( baseAct, pRequired ); + +#if EXPANDED_HL2DM_ACTIVITIES + // +USE activities + if ( m_hUseEntity && player_use_anim_enabled.GetBool() ) + { + CBaseEntity* pHeldEnt = GetPlayerHeldEntity( this ); + float flMass = pHeldEnt ? + PlayerPickupGetHeldObjectMass( m_hUseEntity, pHeldEnt->VPhysicsGetObject() ) : + (m_hUseEntity->VPhysicsGetObject() ? m_hUseEntity->GetMass() : player_use_anim_heavy_mass.GetFloat()); + if ( flMass >= player_use_anim_heavy_mass.GetFloat() ) + { + // Heavy versions + switch (baseAct) + { + case ACT_HL2MP_IDLE: weaponTranslation = ACT_HL2MP_IDLE_USE_HEAVY; break; + case ACT_HL2MP_RUN: weaponTranslation = ACT_HL2MP_RUN_USE_HEAVY; break; + case ACT_HL2MP_WALK: weaponTranslation = ACT_HL2MP_WALK_USE_HEAVY; break; + case ACT_HL2MP_IDLE_CROUCH: weaponTranslation = ACT_HL2MP_IDLE_CROUCH_USE_HEAVY; break; + case ACT_HL2MP_WALK_CROUCH: weaponTranslation = ACT_HL2MP_WALK_CROUCH_USE_HEAVY; break; + case ACT_HL2MP_JUMP: weaponTranslation = ACT_HL2MP_JUMP_USE_HEAVY; break; + } + } + else + { + switch (baseAct) + { + case ACT_HL2MP_IDLE: weaponTranslation = ACT_HL2MP_IDLE_USE; break; + case ACT_HL2MP_RUN: weaponTranslation = ACT_HL2MP_RUN_USE; break; + case ACT_HL2MP_WALK: weaponTranslation = ACT_HL2MP_WALK_USE; break; + case ACT_HL2MP_IDLE_CROUCH: weaponTranslation = ACT_HL2MP_IDLE_CROUCH_USE; break; + case ACT_HL2MP_WALK_CROUCH: weaponTranslation = ACT_HL2MP_WALK_CROUCH_USE; break; + case ACT_HL2MP_JUMP: weaponTranslation = ACT_HL2MP_JUMP_USE; break; + } + } } +#endif + + return weaponTranslation; } +#ifdef SP_ANIM_STATE // Set the activity based on an event or current state void CHL2_Player::SetAnimation( PLAYER_ANIM playerAnim ) { - if (!hl2_use_hl2dm_anims.GetBool()) + if (!m_pPlayerAnimState) { BaseClass::SetAnimation( playerAnim ); return; } - int animDesired; - - float speed; + m_pPlayerAnimState->SetPlayerAnimation( playerAnim ); +} - speed = GetAbsVelocity().Length2D(); +void CHL2_Player::AddAnimStateLayer( int iSequence, float flBlendIn, float flBlendOut, float flPlaybackRate, bool bHoldAtEnd, bool bOnlyWhenStill ) +{ + if (!m_pPlayerAnimState) + return; - - // bool bRunning = true; + m_pPlayerAnimState->AddMiscSequence( iSequence, flBlendIn, flBlendOut, flPlaybackRate, bHoldAtEnd, bOnlyWhenStill ); +} +#endif - //Revisit! -/* if ( ( m_nButtons & ( IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT ) ) ) - { - if ( speed > 1.0f && speed < hl2_normspeed.GetFloat() - 20.0f ) - { - bRunning = false; - } - }*/ +//----------------------------------------------------------------------------- +// Purpose: model-change notification. Fires on dynamic load completion as well +//----------------------------------------------------------------------------- +CStudioHdr *CHL2_Player::OnNewModel() +{ + CStudioHdr *hdr = BaseClass::OnNewModel(); - if ( GetFlags() & ( FL_FROZEN | FL_ATCONTROLS ) ) +#ifdef SP_ANIM_STATE + // Clears the animation state if we already have one. + if ( m_pPlayerAnimState != NULL ) { - speed = 0; - playerAnim = PLAYER_IDLE; + m_pPlayerAnimState->Release(); + m_pPlayerAnimState = NULL; } - Activity idealActivity = ACT_HL2MP_RUN; - - // This could stand to be redone. Why is playerAnim abstracted from activity? (sjb) - if ( playerAnim == PLAYER_JUMP ) + if ( hdr && hdr->HaveSequenceForActivity(ACT_HL2MP_IDLE) && hl2_use_sp_animstate.GetBool() ) { - idealActivity = ACT_HL2MP_JUMP; - } - else if ( playerAnim == PLAYER_DIE ) - { - if ( m_lifeState == LIFE_ALIVE ) - { - return; - } - } - else if ( playerAnim == PLAYER_ATTACK1 ) - { - if ( GetActivity( ) == ACT_HOVER || - GetActivity( ) == ACT_SWIM || - GetActivity( ) == ACT_HOP || - GetActivity( ) == ACT_LEAP || - GetActivity( ) == ACT_DIESIMPLE ) - { - idealActivity = GetActivity( ); - } - else - { - idealActivity = ACT_HL2MP_GESTURE_RANGE_ATTACK; - } - } - else if ( playerAnim == PLAYER_RELOAD ) - { - idealActivity = ACT_HL2MP_GESTURE_RELOAD; - } - else if ( playerAnim == PLAYER_IDLE || playerAnim == PLAYER_WALK ) - { - if ( !( GetFlags() & FL_ONGROUND ) && GetActivity( ) == ACT_HL2MP_JUMP ) // Still jumping - { - idealActivity = GetActivity( ); - } - /* - else if ( GetWaterLevel() > 1 ) - { - if ( speed == 0 ) - idealActivity = ACT_HOVER; - else - idealActivity = ACT_SWIM; - } - */ - else - { - if ( GetFlags() & FL_DUCKING ) - { - if ( speed > 0 ) - { - idealActivity = ACT_HL2MP_WALK_CROUCH; - } - else - { - idealActivity = ACT_HL2MP_IDLE_CROUCH; - } - } - else - { - if ( speed > 0 ) - { - /* - if ( bRunning == false ) - { - idealActivity = ACT_WALK; - } - else - */ - { - idealActivity = ACT_HL2MP_RUN; - } - } - else - { - idealActivity = ACT_HL2MP_IDLE; - } - } - } - } - - if ( idealActivity == ACT_HL2MP_GESTURE_RANGE_ATTACK ) - { - RestartGesture( Weapon_TranslateActivity( idealActivity ) ); - - // FIXME: this seems a bit wacked - Weapon_SetActivity( Weapon_TranslateActivity( ACT_RANGE_ATTACK1 ), 0 ); - - return; - } - else if ( idealActivity == ACT_HL2MP_GESTURE_RELOAD ) - { - RestartGesture( Weapon_TranslateActivity( idealActivity ) ); - return; + // Here we create and init the player animation state. + m_pPlayerAnimState = CreatePlayerAnimationState(this); } else { - SetActivity( idealActivity ); - - animDesired = SelectWeightedSequence( Weapon_TranslateActivity ( idealActivity ) ); - - if (animDesired == -1) - { - animDesired = SelectWeightedSequence( idealActivity ); - - if ( animDesired == -1 ) - { - animDesired = 0; - } - } - - // Already using the desired animation? - if ( GetSequence() == animDesired ) - return; - - m_flPlaybackRate = 1.0; - ResetSequence( animDesired ); - SetCycle( 0 ); - return; + m_flAnimRenderYaw = FLT_MAX; } +#endif - // Already using the desired animation? - if ( GetSequence() == animDesired ) - return; - - //Msg( "Set animation to %d\n", animDesired ); - // Reset to first frame of desired animation - ResetSequence( animDesired ); - SetCycle( 0 ); + return hdr; } + +extern char g_szDefaultPlayerModel[MAX_PATH]; +extern bool g_bDefaultPlayerDrawExternally; #endif //----------------------------------------------------------------------------- @@ -1549,8 +1489,13 @@ void CHL2_Player::Spawn(void) #ifndef HL2MP #ifndef PORTAL +#ifdef MAPBASE + if ( GetModelName() == NULL_STRING ) + SetModel( g_szDefaultPlayerModel ); +#else SetModel( "models/player.mdl" ); #endif +#endif #endif BaseClass::Spawn(); @@ -1564,6 +1509,8 @@ void CHL2_Player::Spawn(void) RemoveEffects( EF_NODRAW ); } + + SetDrawPlayerModelExternally( g_bDefaultPlayerDrawExternally ); #endif // @@ -1834,6 +1781,14 @@ void CHL2_Player::InitVCollision( const Vector &vecAbsOrigin, const Vector &vecA CHL2_Player::~CHL2_Player( void ) { +#ifdef SP_ANIM_STATE + // Clears the animation state. + if ( m_pPlayerAnimState != NULL ) + { + m_pPlayerAnimState->Release(); + m_pPlayerAnimState = NULL; + } +#endif } //----------------------------------------------------------------------------- @@ -1873,25 +1828,22 @@ bool CHL2_Player::CommanderFindGoal( commandgoal_t *pGoal ) if (!pCommandRedirect || pCommandRedirect->IsDisabled() || !pCommandRedirect->PointIsWithin(tr.endpos)) continue; - // First, GIVE IT OUR ALLIES so it could fire outputs + // First, give it our allies so it could fire outputs pCommandRedirect->HandleAllies(m_pPlayerAISquad, this); Vector vec = tr.endpos; - if (pCommandRedirect->GetVerdict(&vec, this)) + if (pCommandRedirect->TestRedirect(&vec, this)) { - // It doesn't want us moving, so just don't find a goal at all + // If it returned a 0 vector, cancel the command if (vec.IsZero()) { return false; } - // Just set our goal to this, the mapper didn't sign up for these checks + // Just set our goal to this and skip the code below which checks the target position's validity pGoal->m_vecGoalLocation = vec; return true; } - - // Only one should be necessary - break; } } //else @@ -4036,7 +3988,10 @@ Vector CHL2_Player::EyeDirection2D( void ) Vector CHL2_Player::EyeDirection3D( void ) { Vector vecForward; - +#ifdef MAPBASE + EyeVectors( &vecForward ); + return vecForward; +#else // Return the vehicle angles if we request them if ( GetVehicle() != NULL ) { @@ -4047,6 +4002,7 @@ Vector CHL2_Player::EyeDirection3D( void ) AngleVectors( EyeAngles(), &vecForward ); return vecForward; +#endif } @@ -4617,6 +4573,7 @@ BEGIN_DATADESC( CLogicPlayerProxy ) DEFINE_INPUTFUNC( FIELD_STRING, "GetAmmoOnWeapon", InputGetAmmoOnWeapon ), DEFINE_INPUTFUNC( FIELD_STRING, "SetHandModel", InputSetHandModel ), DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHandModelSkin", InputSetHandModelSkin ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHandModelBodyGroup", InputSetHandModelBodyGroup ), DEFINE_INPUTFUNC( FIELD_STRING, "SetPlayerModel", InputSetPlayerModel ), DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetPlayerDrawExternally", InputSetPlayerDrawExternally ), DEFINE_INPUT( m_MaxArmor, FIELD_INTEGER, "SetMaxInputArmor" ), @@ -4659,6 +4616,8 @@ bool CLogicPlayerProxy::KeyValue( const char *szKeyName, const char *szValue ) vm->SetModel(szValue); else if (FStrEq(szKeyName, "Skin")) // HandsVMSkin vm->m_nSkin = atoi(szValue); + else if (FStrEq(szKeyName, "Body")) // HandsVMBody + vm->m_nBody = atoi(szValue); } return true; } @@ -4734,7 +4693,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); } } @@ -5061,6 +5021,17 @@ void CLogicPlayerProxy::InputSetHandModelSkin( inputdata_t &inputdata ) vm->m_nSkin = inputdata.value.Int(); } +void CLogicPlayerProxy::InputSetHandModelBodyGroup( inputdata_t &inputdata ) +{ + if (!m_hPlayer) + return; + + CBasePlayer *pPlayer = static_cast( m_hPlayer.Get() ); + CBaseViewModel *vm = pPlayer->GetViewModel(1); + if (vm) + vm->m_nBody = inputdata.value.Int(); +} + void CLogicPlayerProxy::InputSetPlayerModel( inputdata_t &inputdata ) { if (!m_hPlayer) @@ -5088,6 +5059,6 @@ void CLogicPlayerProxy::InputSetPlayerDrawExternally( inputdata_t &inputdata ) return; CBasePlayer *pPlayer = static_cast(m_hPlayer.Get()); - pPlayer->m_bDrawPlayerModelExternally = inputdata.value.Bool(); + pPlayer->SetDrawPlayerModelExternally( inputdata.value.Bool() ); } #endif diff --git a/sp/src/game/server/hl2/hl2_player.h b/sp/src/game/server/hl2/hl2_player.h index 84ae23b9b2..12c0386c85 100644 --- a/sp/src/game/server/hl2/hl2_player.h +++ b/sp/src/game/server/hl2/hl2_player.h @@ -18,6 +18,8 @@ // In HL2MP we need to inherit from BaseMultiplayerPlayer! #if defined ( HL2MP ) #include "basemultiplayerplayer.h" +#elif defined ( MAPBASE ) +#include "mapbase/singleplayer_animstate.h" #endif class CAI_Squad; @@ -130,9 +132,16 @@ class CHL2_Player : public CBasePlayer // For the logic_playerproxy output void SpawnedAtPoint( CBaseEntity *pSpawnPoint ); - void ResetAnimation( void ); + Activity Weapon_TranslateActivity( Activity baseAct, bool *pRequired = NULL ); + +#ifdef SP_ANIM_STATE void SetAnimation( PLAYER_ANIM playerAnim ); + void AddAnimStateLayer( int iSequence, float flBlendIn = 0.0f, float flBlendOut = 0.0f, float flPlaybackRate = 1.0f, bool bHoldAtEnd = false, bool bOnlyWhenStill = false ); +#endif + + virtual CStudioHdr* OnNewModel(); + virtual const char *GetOverrideStepSound( const char *pszBaseStepSoundName ); #endif @@ -303,7 +312,7 @@ class CHL2_Player : public CBasePlayer 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 ); @@ -432,6 +441,14 @@ class CHL2_Player : public CBasePlayer float m_flTimeNextLadderHint; // Next time we're eligible to display a HUD hint about a ladder. friend class CHL2GameMovement; + +#ifdef SP_ANIM_STATE + CSinglePlayerAnimState* m_pPlayerAnimState; + + // At the moment, we network the render angles since almost none of the player anim stuff is done on the client in SP. + // If any of this is ever adapted for MP, this method should be replaced with replicating/moving the anim state to the client. + CNetworkVar( float, m_flAnimRenderYaw ); +#endif }; diff --git a/sp/src/game/server/hl2/item_battery.cpp b/sp/src/game/server/hl2/item_battery.cpp index 7e299fc59b..d5c8b41636 100644 --- a/sp/src/game/server/hl2/item_battery.cpp +++ b/sp/src/game/server/hl2/item_battery.cpp @@ -23,12 +23,12 @@ class CItemBattery : public CItem 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 @@ class CItemBattery : public CItem bool MyTouch( CBasePlayer *pPlayer ) { CHL2_Player *pHL2Player = dynamic_cast( 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 + diff --git a/sp/src/game/server/hl2/item_healthkit.cpp b/sp/src/game/server/hl2/item_healthkit.cpp index 628f873e68..54f961c75d 100644 --- a/sp/src/game/server/hl2/item_healthkit.cpp +++ b/sp/src/game/server/hl2/item_healthkit.cpp @@ -30,11 +30,29 @@ class CHealthKit : public CItem 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 @@ class CHealthVial : public CItem 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 @@ class CHealthVial : public CItem 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. //----------------------------------------------------------------------------- diff --git a/sp/src/game/server/hl2/npc_BaseZombie.cpp b/sp/src/game/server/hl2/npc_BaseZombie.cpp index bbe1f9d013..5d13c5e6c3 100644 --- a/sp/src/game/server/hl2/npc_BaseZombie.cpp +++ b/sp/src/game/server/hl2/npc_BaseZombie.cpp @@ -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 diff --git a/sp/src/game/server/hl2/npc_alyx.h b/sp/src/game/server/hl2/npc_alyx.h index 551460b296..266dca6d8e 100644 --- a/sp/src/game/server/hl2/npc_alyx.h +++ b/sp/src/game/server/hl2/npc_alyx.h @@ -36,6 +36,9 @@ class CNPC_Alyx : public CNPC_PlayerCompanion // 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 ¶ms ) { params.r1 = 255; params.g1 = 212; params.b1 = 255; return BaseClass::GetGameTextSpeechParams( params ); } #endif EHANDLE m_hEmpTool; diff --git a/sp/src/game/server/hl2/npc_alyx_episodic.cpp b/sp/src/game/server/hl2/npc_alyx_episodic.cpp index 19616e10ab..ee8b197ceb 100644 --- a/sp/src/game/server/hl2/npc_alyx_episodic.cpp +++ b/sp/src/game/server/hl2/npc_alyx_episodic.cpp @@ -156,6 +156,8 @@ ConVar npc_alyx_crouch( "npc_alyx_crouch", "1" ); #ifdef MAPBASE ConVar npc_alyx_interact_manhacks( "npc_alyx_interact_manhacks", "1" ); ConVar npc_alyx_interact_turrets( "npc_alyx_interact_turrets", "0" ); + +ConVar npc_alyx_allow_fly( "npc_alyx_allow_fly", "0", FCVAR_NONE, "Allows Alyx to use FL_FLY outside of scripted sequences, actbusy, or navigation." ); #endif // global pointer to Alyx for fast lookups @@ -319,7 +321,9 @@ CNPC_Alyx *CNPC_Alyx::GetAlyx( void ) //========================================================= bool CNPC_Alyx::CreateBehaviors() { +#ifndef MAPBASE // Moved to CNPC_PlayerCompanion AddBehavior( &m_FuncTankBehavior ); +#endif bool result = BaseClass::CreateBehaviors(); return result; @@ -885,7 +889,11 @@ void CNPC_Alyx::GatherConditions() // ROBIN: This was here to solve a problem in a playtest. We've since found what we think was the cause. // It's a useful piece of debug to have lying there, so I've left it in. - if ( (GetFlags() & FL_FLY) && m_NPCState != NPC_STATE_SCRIPT && !m_ActBusyBehavior.IsActive() && !m_PassengerBehavior.IsEnabled() ) + if ( (GetFlags() & FL_FLY) && m_NPCState != NPC_STATE_SCRIPT && !m_ActBusyBehavior.IsActive() && !m_PassengerBehavior.IsEnabled() +#ifdef MAPBASE + && GetNavType() != NAV_CLIMB && !npc_alyx_allow_fly.GetBool() +#endif + ) { Warning( "Removed FL_FLY from Alyx, who wasn't running a script or actbusy. Time %.2f, map %s.\n", gpGlobals->curtime, STRING(gpGlobals->mapname) ); RemoveFlag( FL_FLY ); @@ -1069,6 +1077,13 @@ void CNPC_Alyx::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo & pMemory->timeFirstSeen = gpGlobals->curtime - 10.0f; } } + +#ifdef MAPBASE + // This call has a side effect of causing Alyx to speak a regular companion TLK_ENEMY_DEAD, which may conflict with the TLK_ALYX_ENEMY_DEAD + // further up, but this is fine because concepts are protected against interrupting each other and Alyx may even be overridden + // to use TLK_ENEMY_DEAD instead, which is used by other NPCs and appends more modifiers. + BaseClass::Event_KilledOther( pVictim, info ); +#endif } //----------------------------------------------------------------------------- @@ -1680,9 +1695,88 @@ Activity CNPC_Alyx::NPC_TranslateActivity( Activity activity ) case ACT_DROP_WEAPON: if ( HasShotgun() ) return (Activity)ACT_DROP_WEAPON_SHOTGUN; } +#if EXPANDED_HL2_WEAPON_ACTIVITIES + // Alyx has her own pistol readiness animations which use the default activities + switch (activity) + { + case ACT_IDLE_PISTOL_RELAXED: + return ACT_IDLE_RELAXED; + case ACT_IDLE_PISTOL_STIMULATED: + return ACT_IDLE_STIMULATED; + case ACT_WALK_PISTOL_RELAXED: + return ACT_WALK; + case ACT_WALK_PISTOL_STIMULATED: + return ACT_WALK_PISTOL; + case ACT_RUN_PISTOL_RELAXED: + return ACT_RUN; + case ACT_RUN_PISTOL_STIMULATED: + return ACT_RUN_PISTOL; + } +#endif + + return activity; +} + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Activity CNPC_Alyx::Weapon_TranslateActivity( Activity activity, bool *pRequired ) +{ + activity = BaseClass::Weapon_TranslateActivity( activity, pRequired ); + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + // Alyx has her own pistol readiness animations which use the default activities + switch (activity) + { + case ACT_IDLE_PISTOL_RELAXED: + return ACT_IDLE_RELAXED; + case ACT_IDLE_PISTOL_STIMULATED: + return ACT_IDLE_STIMULATED; + case ACT_WALK_PISTOL_RELAXED: + return ACT_WALK; + case ACT_WALK_PISTOL_STIMULATED: + return ACT_WALK_PISTOL; + case ACT_RUN_PISTOL_RELAXED: + return ACT_RUN; + case ACT_RUN_PISTOL_STIMULATED: + return ACT_RUN_PISTOL; + } +#endif + return activity; } +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Activity CNPC_Alyx::Weapon_BackupActivity( Activity activity, bool weaponTranslationWasRequired, CBaseCombatWeapon *pSpecificWeapon ) +{ + activity = BaseClass::Weapon_BackupActivity( activity, weaponTranslationWasRequired, pSpecificWeapon ); + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + // Alyx has her own pistol readiness animations which use the default activities + switch (activity) + { + case ACT_IDLE_PISTOL_RELAXED: + return ACT_IDLE_RELAXED; + case ACT_IDLE_PISTOL_STIMULATED: + return ACT_IDLE_STIMULATED; + case ACT_WALK_PISTOL_RELAXED: + return ACT_WALK; + case ACT_WALK_PISTOL_STIMULATED: + return ACT_WALK_PISTOL; + case ACT_RUN_PISTOL_RELAXED: + return ACT_RUN; + case ACT_RUN_PISTOL_STIMULATED: + return ACT_RUN_PISTOL; + } +#endif + + return activity; +} +#endif + bool CNPC_Alyx::ShouldDeferToFollowBehavior() { return BaseClass::ShouldDeferToFollowBehavior(); diff --git a/sp/src/game/server/hl2/npc_alyx_episodic.h b/sp/src/game/server/hl2/npc_alyx_episodic.h index 9e72fd8de5..f24ddf6768 100644 --- a/sp/src/game/server/hl2/npc_alyx_episodic.h +++ b/sp/src/game/server/hl2/npc_alyx_episodic.h @@ -57,6 +57,9 @@ class CNPC_Alyx : public CNPC_PlayerCompanion #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 ¶ms ) { params.r1 = 255; params.g1 = 212; params.b1 = 255; return BaseClass::GetGameTextSpeechParams( params ); } #endif virtual float GetJumpGravity() const { return 1.8f; } @@ -88,6 +91,10 @@ class CNPC_Alyx : public CNPC_PlayerCompanion bool CanSeeEntityInDarkness( CBaseEntity *pEntity ); bool IsCoverPosition( const Vector &vecThreat, const Vector &vecPosition ); Activity NPC_TranslateActivity ( Activity activity ); +#ifdef MAPBASE + Activity Weapon_TranslateActivity( Activity baseAct, bool *pRequired = NULL ); + Activity Weapon_BackupActivity( Activity activity, bool weaponTranslationWasRequired = false, CBaseCombatWeapon *pSpecificWeapon = NULL ); +#endif bool ShouldDeferToFollowBehavior(); void BuildScheduleTestBits(); bool ShouldBehaviorSelectSchedule( CAI_BehaviorBase *pBehavior ); @@ -228,7 +235,9 @@ class CNPC_Alyx : public CNPC_PlayerCompanion bool m_bShouldHaveEMP; +#ifndef MAPBASE // Moved to CNPC_PlayerCompanion CAI_FuncTankBehavior m_FuncTankBehavior; +#endif COutputEvent m_OnFinishInteractWithObject; COutputEvent m_OnPlayerUse; diff --git a/sp/src/game/server/hl2/npc_antlion.cpp b/sp/src/game/server/hl2/npc_antlion.cpp index 66fd83674e..2d59c9a05f 100644 --- a/sp/src/game/server/hl2/npc_antlion.cpp +++ b/sp/src/game/server/hl2/npc_antlion.cpp @@ -65,6 +65,9 @@ ConVar sk_antlion_worker_burst_radius( "sk_antlion_worker_burst_radius", "160", ConVar g_test_new_antlion_jump( "g_test_new_antlion_jump", "1", FCVAR_ARCHIVE ); ConVar antlion_easycrush( "antlion_easycrush", "1" ); +#ifdef MAPBASE +ConVar antlion_no_ignite_die( "antlion_no_ignite_die", "0" ); +#endif ConVar g_antlion_cascade_push( "g_antlion_cascade_push", "1", FCVAR_ARCHIVE ); ConVar g_debug_antlion_worker( "g_debug_antlion_worker", "0" ); @@ -2623,6 +2626,15 @@ int CNPC_Antlion::SelectSchedule( void ) void CNPC_Antlion::Ignite ( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner ) { #ifdef HL2_EPISODIC + +#ifdef MAPBASE + if (antlion_no_ignite_die.GetBool()) + { + BaseClass::Ignite(flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner); + return; + } +#endif + float flDamage = m_iHealth + 1; CTakeDamageInfo dmgInfo( this, this, flDamage, DMG_GENERIC ); diff --git a/sp/src/game/server/hl2/npc_attackchopper.cpp b/sp/src/game/server/hl2/npc_attackchopper.cpp index ddb9752ab7..ac6efb1cbe 100644 --- a/sp/src/game/server/hl2/npc_attackchopper.cpp +++ b/sp/src/game/server/hl2/npc_attackchopper.cpp @@ -42,6 +42,10 @@ #include "physics_bone_follower.h" #endif // HL2_EPISODIC +#ifdef MAPBASE +#include "filters.h" +#endif + // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -5680,6 +5684,9 @@ LINK_ENTITY_TO_CLASS( npc_heli_avoidsphere, CAvoidSphere ); BEGIN_DATADESC( CAvoidSphere ) DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_iszAvoidFilter, FIELD_STRING, "AvoidFilter" ), +#endif END_DATADESC() @@ -5720,6 +5727,18 @@ void CAvoidSphere::Activate( ) { BaseClass::Activate(); s_AvoidSpheres.AddToTail( this ); + +#ifdef MAPBASE + m_hAvoidFilter = gEntList.FindEntityByName( NULL, m_iszAvoidFilter, this ); + if (m_hAvoidFilter) + { + if (dynamic_cast(m_hAvoidFilter.Get()) == NULL) + { + Warning( "%s: \"%s\" is not a valid filter", GetDebugName(), m_hAvoidFilter->GetDebugName() ); + m_hAvoidFilter = NULL; + } + } +#endif } void CAvoidSphere::UpdateOnRemove( ) @@ -5746,6 +5765,12 @@ void CAvoidSphere::ComputeAvoidanceForces( CBaseEntity *pEntity, float flEntityR CAvoidSphere *pSphere = s_AvoidSpheres[i].Get(); const Vector &vecAvoidCenter = pSphere->WorldSpaceCenter(); +#ifdef MAPBASE + // Continue if not passing the avoid sphere filter + if ( pSphere->m_hAvoidFilter && !(static_cast( pSphere->m_hAvoidFilter.Get())->PassesFilter(pSphere, pEntity )) ) + continue; +#endif + // NOTE: This test can be thought of sweeping a sphere through space // and seeing if it intersects the avoidance sphere float flTotalRadius = flEntityRadius + pSphere->m_flRadius; diff --git a/sp/src/game/server/hl2/npc_barnacle.cpp b/sp/src/game/server/hl2/npc_barnacle.cpp index 2ffca3bc07..d26f702a1a 100644 --- a/sp/src/game/server/hl2/npc_barnacle.cpp +++ b/sp/src/game/server/hl2/npc_barnacle.cpp @@ -689,6 +689,12 @@ bool CNPC_Barnacle::CanPickup( CBaseCombatCharacter *pBCC ) if( FClassnameIs( pBCC, "npc_turret_floor" ) ) return false; +#ifdef MAPBASE + // Don't pickup rollermines + if( FClassnameIs( pBCC, "npc_rollermine" ) ) + return false; +#endif + // Don't pick up a dead player or NPC if( !pBCC->IsAlive() ) return false; diff --git a/sp/src/game/server/hl2/npc_barney.cpp b/sp/src/game/server/hl2/npc_barney.cpp index 02afce18cf..cfb80afc0c 100644 --- a/sp/src/game/server/hl2/npc_barney.cpp +++ b/sp/src/game/server/hl2/npc_barney.cpp @@ -51,8 +51,10 @@ class CNPC_Barney : public CNPC_PlayerCompanion virtual void Precache() { +#ifndef MAPBASE // This is now done in CNPC_PlayerCompanion::Precache() // Prevents a warning SelectModel( ); +#endif BaseClass::Precache(); PrecacheScriptSound( "NPC_Barney.FootstepLeft" ); @@ -81,6 +83,11 @@ class CNPC_Barney : public CNPC_PlayerCompanion 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 ¶ms ) { params.r1 = 215; params.g1 = 255; params.b1 = 255; return BaseClass::GetGameTextSpeechParams( params ); } +#endif + CAI_FuncTankBehavior m_FuncTankBehavior; COutputEvent m_OnPlayerUse; diff --git a/sp/src/game/server/hl2/npc_basescanner.cpp b/sp/src/game/server/hl2/npc_basescanner.cpp index a94c77b6a9..fdae50dafc 100644 --- a/sp/src/game/server/hl2/npc_basescanner.cpp +++ b/sp/src/game/server/hl2/npc_basescanner.cpp @@ -1278,6 +1278,14 @@ void CNPC_BaseScanner::MoveToTarget( float flInterval, const Vector &vecMoveTarg myZAccel = flDist / flInterval; } +#ifdef MAPBASE + if (m_flSpeedModifier != 1.0f) + { + myAccel *= m_flSpeedModifier; + //myZAccel *= m_flSpeedModifier; + } +#endif + MoveInDirection( flInterval, targetDir, myAccel, myZAccel, myDecay ); // calc relative banking targets diff --git a/sp/src/game/server/hl2/npc_breen.cpp b/sp/src/game/server/hl2/npc_breen.cpp index e12bab0c2b..e1409c80cc 100644 --- a/sp/src/game/server/hl2/npc_breen.cpp +++ b/sp/src/game/server/hl2/npc_breen.cpp @@ -34,6 +34,11 @@ class CNPC_Breen : public CAI_BaseActor 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 ¶ms ) { params.r1 = 188; params.g1 = 188; params.b1 = 188; return BaseClass::GetGameTextSpeechParams( params ); } +#endif }; LINK_ENTITY_TO_CLASS( npc_breen, CNPC_Breen ); diff --git a/sp/src/game/server/hl2/npc_citizen17.cpp b/sp/src/game/server/hl2/npc_citizen17.cpp index 07ba67da42..42822d2ff3 100644 --- a/sp/src/game/server/hl2/npc_citizen17.cpp +++ b/sp/src/game/server/hl2/npc_citizen17.cpp @@ -407,6 +407,27 @@ BEGIN_DATADESC( CNPC_Citizen ) END_DATADESC() +#ifdef MAPBASE_VSCRIPT +ScriptHook_t CNPC_Citizen::g_Hook_SelectModel; + +BEGIN_ENT_SCRIPTDESC( CNPC_Citizen, CAI_BaseActor, "npc_citizen from Half-Life 2" ) + + DEFINE_SCRIPTFUNC( IsMedic, "Returns true if this citizen is a medic." ) + DEFINE_SCRIPTFUNC( IsAmmoResupplier, "Returns true if this citizen is an ammo resupplier." ) + DEFINE_SCRIPTFUNC( CanHeal, "Returns true if this citizen is a medic or ammo resupplier currently able to heal/give ammo." ) + + DEFINE_SCRIPTFUNC( GetCitizenType, "Gets the citizen's type. 1 = Downtrodden, 2 = Refugee, 3 = Rebel, 4 = Unique" ) + DEFINE_SCRIPTFUNC( SetCitizenType, "Sets the citizen's type. 1 = Downtrodden, 2 = Refugee, 3 = Rebel, 4 = Unique" ) + + BEGIN_SCRIPTHOOK( CNPC_Citizen::g_Hook_SelectModel, "SelectModel", FIELD_CSTRING, "Called when a citizen is selecting a random model. 'model_path' is the directory of the selected model and 'model_head' is the name. The 'gender' parameter uses the 'GENDER_' constants and is based only on the citizen's random head spawnflags. If a full model path string is returned, it will be used as the model instead." ) + DEFINE_SCRIPTHOOK_PARAM( "model_path", FIELD_CSTRING ) + DEFINE_SCRIPTHOOK_PARAM( "model_head", FIELD_CSTRING ) + DEFINE_SCRIPTHOOK_PARAM( "gender", FIELD_INTEGER ) + END_SCRIPTHOOK() + +END_SCRIPTDESC(); +#endif + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- @@ -418,10 +439,11 @@ CSimpleSimTimer CNPC_Citizen::gm_PlayerSquadEvaluateTimer; bool CNPC_Citizen::CreateBehaviors() { BaseClass::CreateBehaviors(); - AddBehavior( &m_FuncTankBehavior ); #ifdef MAPBASE AddBehavior( &m_RappelBehavior ); AddBehavior( &m_PolicingBehavior ); +#else // Moved to CNPC_PlayerCompanion + AddBehavior( &m_FuncTankBehavior ); #endif return true; @@ -431,7 +453,12 @@ bool CNPC_Citizen::CreateBehaviors() //----------------------------------------------------------------------------- void CNPC_Citizen::Precache() { +#ifdef MAPBASE + // CNPC_PlayerCompanion::Precache() is responsible for calling this now + BaseClass::Precache(); +#else SelectModel(); +#endif SelectExpressionType(); if ( !npc_citizen_dont_precache_all.GetBool() ) @@ -468,7 +495,9 @@ void CNPC_Citizen::Precache() } } +#ifndef MAPBASE // See above BaseClass::Precache(); +#endif } //----------------------------------------------------------------------------- @@ -769,6 +798,54 @@ void CNPC_Citizen::SelectModel() pszModelName = g_ppszRandomHeads[m_iHead]; SetModelName(NULL_STRING); } + +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_SelectModel.CanRunInScope( m_ScriptScope )) + { + gender_t scriptGender; + switch (gender) + { + case 'm': + scriptGender = GENDER_MALE; + break; + case 'f': + scriptGender = GENDER_FEMALE; + break; + default: + scriptGender = GENDER_NONE; + break; + } + + const char *pszModelPath = CFmtStr( "models/Humans/%s/", (const char *)(CFmtStr( g_ppszModelLocs[m_Type], (IsMedic()) ? "m" : "" )) ); + + // model_path, model_head, gender + ScriptVariant_t args[] = { pszModelPath, pszModelName, (int)scriptGender }; + ScriptVariant_t returnValue = NULL; + g_Hook_SelectModel.Call( m_ScriptScope, &returnValue, args ); + + if (returnValue.m_type == FIELD_CSTRING && returnValue.m_pszString[0] != '\0') + { + // Refresh the head if it's different + const char *pszNewHead = strrchr( returnValue.m_pszString, '/' ); + if ( pszNewHead && Q_stricmp(pszNewHead+1, pszModelName) != 0 ) + { + pszNewHead++; + for ( int i = 0; i < ARRAYSIZE(g_ppszRandomHeads); i++ ) + { + if ( Q_stricmp( g_ppszRandomHeads[i], pszModelName ) == 0 ) + { + m_iHead = i; + break; + } + } + } + + // Just set the model right here + SetModelName( AllocPooledString( returnValue.m_pszString ) ); + return; + } + } +#endif } Assert( pszModelName || GetModelName() != NULL_STRING ); @@ -1466,7 +1543,11 @@ int CNPC_Citizen::SelectScheduleRetrieveItem() // Been kicked out of the player squad since the time I located the health. ClearCondition( COND_HEALTH_ITEM_AVAILABLE ); } +#ifdef MAPBASE + else if ( m_FollowBehavior.GetFollowTarget() ) +#else else +#endif { CBaseEntity *pBase = FindHealthItem(m_FollowBehavior.GetFollowTarget()->GetAbsOrigin(), Vector( 120, 120, 120 ) ); CItem *pItem = dynamic_cast(pBase); @@ -1932,12 +2013,7 @@ void CNPC_Citizen::RunTask( const Task_t *pTask ) return; } // Add imprecision to avoid obvious robotic perfection stationary targets -#ifdef MAPBASE - // More imprecision with low-accuracy citizens - float imprecision = 18*sin(gpGlobals->curtime) + cosh(GetCurrentWeaponProficiency() - 4); -#else float imprecision = 18*sin(gpGlobals->curtime); -#endif vecLaserPos.x += imprecision; vecLaserPos.y += imprecision; vecLaserPos.z += imprecision; @@ -2014,6 +2090,13 @@ Activity CNPC_Citizen::NPC_TranslateActivity( Activity activity ) return ACT_RUN_AIM_AR2_STIMULATED; if (activity == ACT_WALK_AIM_AR2) return ACT_WALK_AIM_AR2_STIMULATED; + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + if (activity == ACT_RUN_AIM_PISTOL) + return ACT_RUN_AIM_PISTOL_STIMULATED; + if (activity == ACT_WALK_AIM_PISTOL) + return ACT_WALK_AIM_PISTOL_STIMULATED; +#endif } #endif @@ -2278,25 +2361,21 @@ bool CNPC_Citizen::IsManhackMeleeCombatant() //----------------------------------------------------------------------------- Vector CNPC_Citizen::GetActualShootPosition( const Vector &shootOrigin ) { -#ifdef MAPBASE - // The code below is probably broken. If not, it definitely isn't very effective. - return BaseClass::GetActualShootPosition( shootOrigin ); -#else Vector vecTarget = BaseClass::GetActualShootPosition( shootOrigin ); #ifdef MAPBASE - // If we're firing an RPG at a gunship, aim off to it's side, because we'll auger towards it. + // The gunship RPG code does not appear to be funcitonal, so only set the laser position. if ( GetActiveWeapon() && EntIsClass(GetActiveWeapon(), gm_isz_class_RPG) && GetEnemy() ) { CWeaponRPG *pRPG = static_cast(GetActiveWeapon()); - if ( EntIsClass( GetEnemy(), gm_isz_class_Gunship ) ) + pRPG->SetNPCLaserPosition( vecTarget ); + } #else CWeaponRPG *pRPG = dynamic_cast(GetActiveWeapon()); // If we're firing an RPG at a gunship, aim off to it's side, because we'll auger towards it. if ( pRPG && GetEnemy() ) { if ( FClassnameIs( GetEnemy(), "npc_combinegunship" ) ) -#endif { Vector vecRight; GetVectors( NULL, &vecRight, NULL ); @@ -2331,11 +2410,10 @@ Vector CNPC_Citizen::GetActualShootPosition( const Vector &shootOrigin ) { pRPG->SetNPCLaserPosition( vecTarget ); } - } +#endif return vecTarget; -#endif } //----------------------------------------------------------------------------- diff --git a/sp/src/game/server/hl2/npc_citizen17.h b/sp/src/game/server/hl2/npc_citizen17.h index 35f39a057f..5d23429d97 100644 --- a/sp/src/game/server/hl2/npc_citizen17.h +++ b/sp/src/game/server/hl2/npc_citizen17.h @@ -277,6 +277,11 @@ class CNPC_Citizen : public CNPC_PlayerCompanion virtual void OnChangeRunningBehavior( CAI_BehaviorBase *pOldBehavior, CAI_BehaviorBase *pNewBehavior ); +#ifdef MAPBASE + int GetCitizenType() { return (int)m_Type; } + void SetCitizenType( int iType ) { m_Type = (CitizenType_t)iType; } +#endif + private: //----------------------------------------------------- // Conditions, Schedules, Tasks @@ -365,7 +370,6 @@ class CNPC_Citizen : public CNPC_PlayerCompanion #endif //----------------------------------------------------- - CAI_FuncTankBehavior m_FuncTankBehavior; #ifdef MAPBASE CAI_RappelBehavior m_RappelBehavior; CAI_PolicingBehavior m_PolicingBehavior; @@ -373,6 +377,8 @@ class CNPC_Citizen : public CNPC_PlayerCompanion // Rappel virtual bool IsWaitingToRappel( void ) { return m_RappelBehavior.IsWaitingToRappel(); } void BeginRappel() { m_RappelBehavior.BeginRappel(); } +#else // Moved to CNPC_PlayerCompanion + CAI_FuncTankBehavior m_FuncTankBehavior; #endif CHandle m_hSavedFollowGoalEnt; @@ -382,6 +388,10 @@ class CNPC_Citizen : public CNPC_PlayerCompanion //----------------------------------------------------- +#ifdef MAPBASE_VSCRIPT + static ScriptHook_t g_Hook_SelectModel; + DECLARE_ENT_SCRIPTDESC(); +#endif DECLARE_DATADESC(); #ifdef _XBOX protected: diff --git a/sp/src/game/server/hl2/npc_combine.cpp b/sp/src/game/server/hl2/npc_combine.cpp index 7599a6ac33..50d109825b 100644 --- a/sp/src/game/server/hl2/npc_combine.cpp +++ b/sp/src/game/server/hl2/npc_combine.cpp @@ -43,6 +43,7 @@ int g_fCombineQuestion; // true if an idle grunt asked a question. Cleared wh #ifdef MAPBASE ConVar npc_combine_idle_walk_easy( "npc_combine_idle_walk_easy", "1", FCVAR_NONE, "Mapbase: Allows Combine soldiers to use ACT_WALK_EASY as a walking animation when idle." ); ConVar npc_combine_unarmed_anims( "npc_combine_unarmed_anims", "1", FCVAR_NONE, "Mapbase: Allows Combine soldiers to use unarmed idle/walk animations when they have no weapon." ); +ConVar npc_combine_protected_run( "npc_combine_protected_run", "0", FCVAR_NONE, "Mapbase: Allows Combine soldiers to use \"protected run\" animations." ); ConVar npc_combine_altfire_not_allies_only( "npc_combine_altfire_not_allies_only", "1", FCVAR_NONE, "Mapbase: Elites are normally only allowed to fire their alt-fire attack at the player and the player's allies; This allows elites to alt-fire at other enemies too." ); ConVar npc_combine_new_cover_behavior( "npc_combine_new_cover_behavior", "1", FCVAR_NONE, "Mapbase: Toggles small patches for parts of npc_combine AI related to soldiers failing to take cover. These patches are minimal and only change cases where npc_combine would otherwise look at an enemy without shooting or run up to the player to melee attack when they don't have to. Consult the Mapbase wiki for more information." ); @@ -116,19 +117,20 @@ int COMBINE_AE_ALTFIRE; //Activity ACT_COMBINE_WALKING_AR2; //Activity ACT_COMBINE_STANDING_SHOTGUN; //Activity ACT_COMBINE_CROUCHING_SHOTGUN; -#ifndef SHARED_COMBINE_ACTIVITIES +#if !SHARED_COMBINE_ACTIVITIES Activity ACT_COMBINE_THROW_GRENADE; #endif Activity ACT_COMBINE_LAUNCH_GRENADE; Activity ACT_COMBINE_BUGBAIT; -#ifndef SHARED_COMBINE_ACTIVITIES +#if !SHARED_COMBINE_ACTIVITIES Activity ACT_COMBINE_AR2_ALTFIRE; #endif Activity ACT_WALK_EASY; Activity ACT_WALK_MARCH; #ifdef MAPBASE -Activity ACT_IDLE_UNARMED; -Activity ACT_WALK_UNARMED; +Activity ACT_TURRET_CARRY_IDLE; +Activity ACT_TURRET_CARRY_WALK; +Activity ACT_TURRET_CARRY_RUN; #endif // ----------------------------------------------- @@ -230,7 +232,9 @@ DEFINE_INPUTFUNC( FIELD_STRING, "SetPoliceGoal", InputSetPoliceGoal ), DEFINE_AIGRENADE_DATADESC() #endif +#ifndef MAPBASE DEFINE_FIELD( m_iLastAnimEventHandled, FIELD_INTEGER ), +#endif DEFINE_FIELD( m_fIsElite, FIELD_BOOLEAN ), #ifndef MAPBASE DEFINE_FIELD( m_vecAltFireTarget, FIELD_VECTOR ), @@ -542,7 +546,12 @@ void CNPC_Combine::GatherConditions() if( GetState() == NPC_STATE_COMBAT ) { +#ifdef MAPBASE + // Don't override the standoff + if( IsCurSchedule( SCHED_COMBINE_WAIT_IN_COVER, false ) && !m_StandoffBehavior.IsActive() ) +#else if( IsCurSchedule( SCHED_COMBINE_WAIT_IN_COVER, false ) ) +#endif { // Soldiers that are standing around doing nothing poll for attack slots so // that they can respond quickly when one comes available. If they can @@ -746,7 +755,8 @@ Class_T CNPC_Combine::Classify ( void ) //----------------------------------------------------------------------------- bool CNPC_Combine::IsAltFireCapable( void ) { - return IsElite() || m_bAlternateCapable; + // The base class tells us if we're carrying an alt-fire-able weapon. + return (IsElite() || m_bAlternateCapable) && BaseClass::IsAltFireCapable(); } //----------------------------------------------------------------------------- @@ -1538,11 +1548,6 @@ void CNPC_Combine::BuildScheduleTestBits( void ) //----------------------------------------------------------------------------- Activity CNPC_Combine::Weapon_TranslateActivity( Activity eNewActivity, bool *pRequired ) { - // We have differing low animations and ACT_CROUCHIDLE is not friendly to weapon translation. - // ACT_CROUCHIDLE is pretty much deprecated at this point anyway. - if (eNewActivity == ACT_CROUCHIDLE) - eNewActivity = ACT_RANGE_AIM_LOW; - return BaseClass::Weapon_TranslateActivity(eNewActivity, pRequired); } @@ -1551,11 +1556,12 @@ Activity CNPC_Combine::Weapon_TranslateActivity( Activity eNewActivity, bool *pR //----------------------------------------------------------------------------- Activity CNPC_Combine::NPC_BackupActivity( Activity eNewActivity ) { - // Otherwise we move around, T-posing. - if (eNewActivity == ACT_WALK) - return ACT_WALK_UNARMED; - 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 ); } @@ -1588,7 +1594,7 @@ Activity CNPC_Combine::NPC_TranslateActivity( Activity eNewActivity ) else #endif { -#ifdef SHARED_COMBINE_ACTIVITIES +#if SHARED_COMBINE_ACTIVITIES return ACT_COMBINE_THROW_GRENADE; #else return ( Activity )ACT_COMBINE_THROW_GRENADE; @@ -1621,16 +1627,32 @@ Activity CNPC_Combine::NPC_TranslateActivity( Activity eNewActivity ) } } #ifdef MAPBASE - else if (!GetActiveWeapon() && npc_combine_unarmed_anims.GetBool() && HaveSequenceForActivity(ACT_IDLE_UNARMED)) + else if (!GetActiveWeapon() && !npc_combine_unarmed_anims.GetBool()) { if (eNewActivity == ACT_IDLE || eNewActivity == ACT_IDLE_ANGRY) - eNewActivity = ACT_IDLE_UNARMED; + eNewActivity = ACT_IDLE_SMG1; else if (eNewActivity == ACT_WALK) - eNewActivity = ACT_WALK_UNARMED; + eNewActivity = ACT_WALK_RIFLE; + else if (eNewActivity == ACT_RUN) + eNewActivity = ACT_RUN_RIFLE; } - else if (eNewActivity == ACT_WALK && m_NPCState == NPC_STATE_IDLE && npc_combine_idle_walk_easy.GetBool() && HaveSequenceForActivity(ACT_WALK_EASY)) + else if (m_NPCState == NPC_STATE_IDLE && eNewActivity == ACT_WALK) { - eNewActivity = ACT_WALK_EASY; + if (npc_combine_idle_walk_easy.GetBool()) + { + // ACT_WALK_EASY has been replaced with ACT_WALK_RELAXED for weapon translation purposes + eNewActivity = ACT_WALK_RELAXED; + } + else if (GetActiveWeapon()) + { + eNewActivity = ACT_WALK_RIFLE; + } + } + + if ( eNewActivity == ACT_RUN && ( IsCurSchedule( SCHED_TAKE_COVER_FROM_BEST_SOUND ) || IsCurSchedule( SCHED_FLEE_FROM_BEST_SOUND ) ) ) + { + if ( random->RandomInt( 0, 1 ) && npc_combine_protected_run.GetBool() && HaveSequenceForActivity( ACT_RUN_PROTECTED ) ) + eNewActivity = ACT_RUN_PROTECTED; } #endif @@ -2015,7 +2037,7 @@ int CNPC_Combine::SelectSchedule( void ) Vector vecTarget = m_hForcedGrenadeTarget->WorldSpaceCenter(); #ifdef MAPBASE - // I switched this to IsAltFireCapable() before, but m_bAlternateCapable makes it necessary to use IsElite() again. + // This was switched to IsAltFireCapable() before, but m_bAlternateCapable makes it necessary to use IsElite() again. #endif if ( IsElite() ) { @@ -2597,7 +2619,6 @@ int CNPC_Combine::TranslateSchedule( int scheduleType ) #ifdef MAPBASE // SCHED_COMBINE_WAIT_IN_COVER uses INCOVER, but only gets out of it when the soldier moves. // That seems to mess up shooting, so this Forget() attempts to fix that. - // I don't know if there's a better workaround. Forget( bits_MEMORY_INCOVER ); #endif @@ -3004,7 +3025,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 ); } @@ -3676,7 +3701,9 @@ void CNPC_Combine::SetActivity( Activity NewActivity ) { BaseClass::SetActivity( NewActivity ); +#ifndef MAPBASE // CAI_GrenadeUser m_iLastAnimEventHandled = -1; +#endif } //----------------------------------------------------------------------------- @@ -3776,6 +3803,13 @@ WeaponProficiency_t CNPC_Combine::CalcWeaponProficiency( CBaseCombatWeapon *pWea { return WEAPON_PROFICIENCY_GOOD; } +#ifdef MAPBASE + else if ( pWeapon->ClassMatches( gm_isz_class_Pistol ) ) + { + // Mods which need a lower soldier pistol accuracy can either change this value or use proficiency override in Hammer. + return WEAPON_PROFICIENCY_VERY_GOOD; + } +#endif return BaseClass::CalcWeaponProficiency( pWeapon ); } @@ -3891,7 +3925,12 @@ bool CNPC_Combine::IsRunningApproachEnemySchedule() bool CNPC_Combine::ShouldPickADeathPose( void ) { +#ifdef MAPBASE + // Check base class as well + return !IsCrouching() && BaseClass::ShouldPickADeathPose(); +#else return !IsCrouching(); +#endif } //----------------------------------------------------------------------------- @@ -3914,19 +3953,20 @@ DECLARE_TASK( TASK_COMBINE_GET_PATH_TO_FORCED_GREN_LOS ) DECLARE_TASK( TASK_COMBINE_SET_STANDING ) //Activities -#ifndef SHARED_COMBINE_ACTIVITIES +#if !SHARED_COMBINE_ACTIVITIES DECLARE_ACTIVITY( ACT_COMBINE_THROW_GRENADE ) #endif DECLARE_ACTIVITY( ACT_COMBINE_LAUNCH_GRENADE ) DECLARE_ACTIVITY( ACT_COMBINE_BUGBAIT ) -#ifndef SHARED_COMBINE_ACTIVITIES +#if !SHARED_COMBINE_ACTIVITIES DECLARE_ACTIVITY( ACT_COMBINE_AR2_ALTFIRE ) #endif DECLARE_ACTIVITY( ACT_WALK_EASY ) DECLARE_ACTIVITY( ACT_WALK_MARCH ) #ifdef MAPBASE -DECLARE_ACTIVITY( ACT_IDLE_UNARMED ) -DECLARE_ACTIVITY( ACT_WALK_UNARMED ) +DECLARE_ACTIVITY( ACT_TURRET_CARRY_IDLE ) +DECLARE_ACTIVITY( ACT_TURRET_CARRY_WALK ) +DECLARE_ACTIVITY( ACT_TURRET_CARRY_RUN ) #endif DECLARE_ANIMEVENT( COMBINE_AE_BEGIN_ALTFIRE ) diff --git a/sp/src/game/server/hl2/npc_combine.h b/sp/src/game/server/hl2/npc_combine.h index ec541cca2f..c9af801063 100644 --- a/sp/src/game/server/hl2/npc_combine.h +++ b/sp/src/game/server/hl2/npc_combine.h @@ -59,7 +59,7 @@ class CNPC_Combine : public CAI_BaseActor // Create components virtual bool CreateComponents(); -#ifndef MAPBASE +#ifndef MAPBASE // CAI_GrenadeUser bool CanThrowGrenade( const Vector &vecTarget ); bool CheckCanThrowGrenade( const Vector &vecTarget ); #endif @@ -118,7 +118,7 @@ class CNPC_Combine : public CAI_BaseActor const char* GetGrenadeAttachment() { return "lefthand"; } #else #endif -#ifndef MAPBASE +#ifndef MAPBASE // CAI_GrenadeUser void DelayAltFireAttack( float flDelay ); void DelaySquadAltFireAttack( float flDelay ); #endif @@ -131,7 +131,7 @@ class CNPC_Combine : public CAI_BaseActor Vector EyeOffset( Activity nActivity ); Vector EyePosition( void ); Vector BodyTarget( const Vector &posSrc, bool bNoisy = true ); -#ifndef MAPBASE +#ifndef MAPBASE // CAI_GrenadeUser Vector GetAltFireTarget(); #endif @@ -320,7 +320,7 @@ class CNPC_Combine : public CAI_BaseActor private: int m_nKickDamage; -#ifndef MAPBASE +#ifndef MAPBASE // CAI_GrenadeUser Vector m_vecTossVelocity; EHANDLE m_hForcedGrenadeTarget; #else @@ -334,12 +334,12 @@ class CNPC_Combine : public CAI_BaseActor // Time Variables float m_flNextPainSoundTime; float m_flNextAlertSoundTime; -#ifndef MAPBASE +#ifndef MAPBASE // CAI_GrenadeUser float m_flNextGrenadeCheck; #endif float m_flNextLostSoundTime; float m_flAlertPatrolTime; // When to stop doing alert patrol -#ifndef MAPBASE +#ifndef MAPBASE // CAI_GrenadeUser float m_flNextAltFireTime; // Elites only. Next time to begin considering alt-fire attack. #endif @@ -351,7 +351,7 @@ class CNPC_Combine : public CAI_BaseActor CAI_Sentence< CNPC_Combine > m_Sentences; #endif -#ifndef MAPBASE +#ifndef MAPBASE // CAI_GrenadeUser int m_iNumGrenades; #endif CAI_AssaultBehavior m_AssaultBehavior; @@ -365,9 +365,11 @@ class CNPC_Combine : public CAI_BaseActor #endif public: +#ifndef MAPBASE // CAI_GrenadeUser int m_iLastAnimEventHandled; +#endif bool m_fIsElite; -#ifndef MAPBASE +#ifndef MAPBASE // CAI_GrenadeUser Vector m_vecAltFireTarget; #endif diff --git a/sp/src/game/server/hl2/npc_combinedropship.cpp b/sp/src/game/server/hl2/npc_combinedropship.cpp index fa40fd7630..b39dad9fb7 100644 --- a/sp/src/game/server/hl2/npc_combinedropship.cpp +++ b/sp/src/game/server/hl2/npc_combinedropship.cpp @@ -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(&info) ) != false) +#endif + { + m_iHealth = 0; + Event_Killed( dmgInfo ); + return 0; + } } // Spawn damage effects diff --git a/sp/src/game/server/hl2/npc_eli.cpp b/sp/src/game/server/hl2/npc_eli.cpp index 4b42c6c0a7..c66e927310 100644 --- a/sp/src/game/server/hl2/npc_eli.cpp +++ b/sp/src/game/server/hl2/npc_eli.cpp @@ -37,6 +37,11 @@ class CNPC_Eli : public CAI_BaseActor int GetSoundInterests( void ); void SetupWithoutParent( void ); void PrescheduleThink( void ); + +#ifdef MAPBASE + // Use Eli's default subtitle color (255,208,172) + bool GetGameTextSpeechParams( hudtextparms_t ¶ms ) { params.r1 = 255; params.g1 = 208; params.b1 = 172; return BaseClass::GetGameTextSpeechParams( params ); } +#endif }; LINK_ENTITY_TO_CLASS( npc_eli, CNPC_Eli ); diff --git a/sp/src/game/server/hl2/npc_fastzombie.cpp b/sp/src/game/server/hl2/npc_fastzombie.cpp index 0e15e2a839..6e5ec1362a 100644 --- a/sp/src/game/server/hl2/npc_fastzombie.cpp +++ b/sp/src/game/server/hl2/npc_fastzombie.cpp @@ -1296,7 +1296,11 @@ void CFastZombie::StartTask( const Task_t *pTask ) CBaseEntity *pEnemy = GetEnemy(); Vector vecJumpDir; - if ( GetActivity() == ACT_CLIMB_UP || GetActivity() == ACT_CLIMB_DOWN ) + if ( GetActivity() == ACT_CLIMB_UP || GetActivity() == ACT_CLIMB_DOWN +#if EXPANDED_NAVIGATION_ACTIVITIES + || GetActivity() == ACT_CLIMB_ALL +#endif + ) { // Jump off the pipe backwards! Vector forward; @@ -1449,7 +1453,11 @@ int CFastZombie::TranslateSchedule( int scheduleType ) break; case SCHED_FASTZOMBIE_UNSTICK_JUMP: - if ( GetActivity() == ACT_CLIMB_UP || GetActivity() == ACT_CLIMB_DOWN || GetActivity() == ACT_CLIMB_DISMOUNT ) + if ( GetActivity() == ACT_CLIMB_UP || GetActivity() == ACT_CLIMB_DOWN || GetActivity() == ACT_CLIMB_DISMOUNT +#if EXPANDED_NAVIGATION_ACTIVITIES + || (GetActivity() >= ACT_CLIMB_ALL && GetActivity() <= ACT_CLIMB_DISMOUNT_BOTTOM) +#endif + ) { return SCHED_FASTZOMBIE_CLIMBING_UNSTICK_JUMP; } @@ -1477,8 +1485,10 @@ int CFastZombie::TranslateSchedule( int scheduleType ) //--------------------------------------------------------- Activity CFastZombie::NPC_TranslateActivity( Activity baseAct ) { +#ifndef MAPBASE // Now covered by CAI_BaseNPC::NPC_BackupActivity if ( baseAct == ACT_CLIMB_DOWN ) return ACT_CLIMB_UP; +#endif return BaseClass::NPC_TranslateActivity( baseAct ); } diff --git a/sp/src/game/server/hl2/npc_fisherman.cpp b/sp/src/game/server/hl2/npc_fisherman.cpp index 4530f26133..967c57953a 100644 --- a/sp/src/game/server/hl2/npc_fisherman.cpp +++ b/sp/src/game/server/hl2/npc_fisherman.cpp @@ -64,8 +64,10 @@ class CNPC_Fisherman : public CNPC_PlayerCompanion virtual void Precache() { +#ifndef MAPBASE // This is now done in CNPC_PlayerCompanion::Precache() // Prevents a warning - SelectModel( ); + SelectModel(); +#endif BaseClass::Precache(); PrecacheScriptSound( "NPC_Fisherman.FootstepLeft" ); diff --git a/sp/src/game/server/hl2/npc_kleiner.cpp b/sp/src/game/server/hl2/npc_kleiner.cpp index e808176205..4e5e5cf8ed 100644 --- a/sp/src/game/server/hl2/npc_kleiner.cpp +++ b/sp/src/game/server/hl2/npc_kleiner.cpp @@ -35,6 +35,11 @@ class CNPC_Kleiner : public CAI_BaseActor 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 ¶ms ) { params.r1 = 255; params.g1 = 255; params.b1 = 200; return BaseClass::GetGameTextSpeechParams( params ); } +#endif }; LINK_ENTITY_TO_CLASS( npc_kleiner, CNPC_Kleiner ); diff --git a/sp/src/game/server/hl2/npc_metropolice.cpp b/sp/src/game/server/hl2/npc_metropolice.cpp index 8588c04370..229db7b048 100644 --- a/sp/src/game/server/hl2/npc_metropolice.cpp +++ b/sp/src/game/server/hl2/npc_metropolice.cpp @@ -261,6 +261,7 @@ BEGIN_DATADESC( CNPC_MetroPolice ) #ifdef MAPBASE DEFINE_AIGRENADE_DATADESC() DEFINE_INPUT( m_iGrenadeCapabilities, FIELD_INTEGER, "SetGrenadeCapabilities" ), + DEFINE_INPUT( m_iGrenadeDropCapabilities, FIELD_INTEGER, "SetGrenadeDropCapabilities" ), #endif END_DATADESC() @@ -517,6 +518,11 @@ CNPC_MetroPolice::CNPC_MetroPolice() { #ifdef MAPBASE m_iGrenadeCapabilities = GRENCAP_GRENADE; + + if (ai_grenade_always_drop.GetBool()) + { + m_iGrenadeDropCapabilities = (eGrenadeDropCapabilities)(GRENDROPCAP_GRENADE | GRENDROPCAP_ALTFIRE | GRENDROPCAP_INTERRUPTED); + } #endif } @@ -882,7 +888,7 @@ void CNPC_MetroPolice::SpeakStandoffSentence( int nSentenceType ) break; case STANDOFF_SENTENCE_FORCED_TAKE_COVER: - SpeakIfAllowed( TLK_COP_SO_END ); + SpeakIfAllowed( TLK_COP_SO_FORCE_COVER ); break; case STANDOFF_SENTENCE_STAND_CHECK_TARGET: @@ -1008,7 +1014,12 @@ void CNPC_MetroPolice::SpeakSentence( int nSentenceType ) return; } +#ifdef MAPBASE + // Fixed issues with standoff sentences not playing when they should + if ( m_StandoffBehavior.IsActive() ) +#else if ( GetRunningBehavior() == &m_StandoffBehavior ) +#endif { SpeakStandoffSentence( nSentenceType ); return; @@ -1156,7 +1167,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 ); } @@ -1522,7 +1537,12 @@ void CNPC_MetroPolice::OnUpdateShotRegulator( ) BaseClass::OnUpdateShotRegulator(); // FIXME: This code (except the burst interval) could be used for all weapon types +#ifdef MAPBASE + // Only if we actually have the pistol out + if ( EntIsClass( GetActiveWeapon(), gm_isz_class_Pistol ) ) +#else if( Weapon_OwnsThisType( "weapon_pistol" ) ) +#endif { if ( m_nBurstMode == BURST_NOT_ACTIVE ) { @@ -1622,6 +1642,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 ); @@ -3667,6 +3692,11 @@ void CNPC_MetroPolice::Event_Killed( const CTakeDamageInfo &info ) DropItem( "item_healthvial", WorldSpaceCenter()+RandomVector(-4,4), RandomAngle(0,360) ); pHL2GameRules->NPC_DroppedHealth(); } + +#ifdef MAPBASE + // Drop grenades if we should + DropGrenadeItemsOnDeath( info, pPlayer ); +#endif } BaseClass::Event_Killed( info ); @@ -3874,6 +3904,15 @@ int CNPC_MetroPolice::SelectScheduleNoDirectEnemy() return SCHED_METROPOLICE_SMASH_PROP; } +#ifdef MAPBASE + // If you see your enemy and you're still arming yourself, wait and don't just charge in + // (if your weapon is holstered, you're probably about to arm yourself) + if ( HasCondition( COND_SEE_ENEMY ) && GetWeapon(0) && (IsWeaponHolstered() || FindGestureLayer( TranslateActivity( ACT_ARM ) ) != -1) ) + { + return SCHED_COMBAT_FACE; + } +#endif + return SCHED_METROPOLICE_CHASE_ENEMY; } @@ -4497,34 +4536,7 @@ int CNPC_MetroPolice::SelectBehaviorOverrideSchedule() //----------------------------------------------------------------------------- bool CNPC_MetroPolice::IsCrouchedActivity( Activity activity ) { - Activity realActivity = TranslateActivity(activity); - - switch ( realActivity ) - { - case ACT_RELOAD_LOW: - case ACT_COVER_LOW: - case ACT_COVER_PISTOL_LOW: - case ACT_COVER_SMG1_LOW: - case ACT_RELOAD_SMG1_LOW: - //case ACT_RELOAD_AR2_LOW: - case ACT_RELOAD_PISTOL_LOW: - case ACT_RELOAD_SHOTGUN_LOW: - - // These animations aren't actually "low" on metrocops - //case ACT_RANGE_AIM_LOW: - //case ACT_RANGE_AIM_AR2_LOW: - //case ACT_RANGE_AIM_SMG1_LOW: - //case ACT_RANGE_AIM_PISTOL_LOW: - - //case ACT_RANGE_ATTACK1_LOW: - //case ACT_RANGE_ATTACK_AR2_LOW: - //case ACT_RANGE_ATTACK_SMG1_LOW: - //case ACT_RANGE_ATTACK_PISTOL_LOW: - //case ACT_RANGE_ATTACK2_LOW: - return true; - } - - return false; + return BaseClass::IsCrouchedActivity( activity ); } //----------------------------------------------------------------------------- @@ -4920,7 +4932,12 @@ int CNPC_MetroPolice::SelectSchedule( void ) // This will cause the cops to run backwards + shoot at the same time if ( !bHighHealth && !HasBaton() ) { +#ifdef MAPBASE + // Don't do this with low-capacity weapons or weapons which don't use clips + if ( GetActiveWeapon() && GetActiveWeapon()->UsesClipsForAmmo1() && GetActiveWeapon()->GetMaxClip1() > 10 && (GetActiveWeapon()->m_iClip1 <= 5) ) +#else if ( GetActiveWeapon() && (GetActiveWeapon()->m_iClip1 <= 5) ) +#endif { #ifdef METROPOLICE_USES_RESPONSE_SYSTEM SpeakIfAllowed( TLK_COP_LOWAMMO ); diff --git a/sp/src/game/server/hl2/npc_metropolice.h b/sp/src/game/server/hl2/npc_metropolice.h index 1f3f9517c3..742de7e3a7 100644 --- a/sp/src/game/server/hl2/npc_metropolice.h +++ b/sp/src/game/server/hl2/npc_metropolice.h @@ -67,8 +67,12 @@ class CNPC_MetroPolice : public CAI_BaseActor const char* GetGrenadeAttachment() { return "LHand"; } - virtual bool IsAltFireCapable() { return (m_iGrenadeCapabilities & GRENCAP_ALTFIRE) != 0; } + virtual bool IsAltFireCapable() { return (m_iGrenadeCapabilities & GRENCAP_ALTFIRE) != 0 && BaseClass::IsAltFireCapable(); } virtual bool IsGrenadeCapable() { return (m_iGrenadeCapabilities & GRENCAP_GRENADE) != 0; } + + virtual bool ShouldDropGrenades() { return (m_iGrenadeDropCapabilities & GRENDROPCAP_GRENADE) != 0 && BaseClass::ShouldDropGrenades(); } + virtual bool ShouldDropInterruptedGrenades() { return (m_iGrenadeDropCapabilities & GRENDROPCAP_INTERRUPTED) != 0 && BaseClass::ShouldDropInterruptedGrenades(); } + virtual bool ShouldDropAltFire() { return (m_iGrenadeDropCapabilities & GRENDROPCAP_ALTFIRE) != 0 && BaseClass::ShouldDropAltFire(); } #endif Vector EyeDirection3D( void ) { return CAI_BaseHumanoid::EyeDirection3D(); } // cops don't have eyes @@ -520,6 +524,7 @@ class CNPC_MetroPolice : public CAI_BaseActor // Determines whether this NPC is allowed to use grenades or alt-fire stuff. eGrenadeCapabilities m_iGrenadeCapabilities; + eGrenadeDropCapabilities m_iGrenadeDropCapabilities; #endif AIHANDLE m_hManhack; diff --git a/sp/src/game/server/hl2/npc_monk.cpp b/sp/src/game/server/hl2/npc_monk.cpp index 63a4ee6d63..e517a6fe69 100644 --- a/sp/src/game/server/hl2/npc_monk.cpp +++ b/sp/src/game/server/hl2/npc_monk.cpp @@ -196,6 +196,9 @@ Class_T CNPC_Monk::Classify( void ) return CLASS_PLAYER_ALLY_VITAL; } +#ifdef MAPBASE +ConVar npc_monk_use_old_acts( "npc_monk_use_old_acts", "1" ); +#endif //----------------------------------------------------------------------------- // Purpose: @@ -229,6 +232,45 @@ Activity CNPC_Monk::NPC_TranslateActivity( Activity eNewActivity ) } } +#if defined(EXPANDED_HL2_WEAPON_ACTIVITIES) && AR2_ACTIVITY_FIX == 1 + if (npc_monk_use_old_acts.GetBool()) + { + // HACKHACK: Don't break the balcony scene + if ( FStrEq( STRING(gpGlobals->mapname), "d1_town_02" ) && eNewActivity == ACT_IDLE ) + { + eNewActivity = ACT_IDLE_SMG1; + } + else + { + switch (eNewActivity) + { + case ACT_IDLE_AR2: + eNewActivity = ACT_IDLE_SMG1; + break; + + case ACT_IDLE_ANGRY_SHOTGUN: + case ACT_IDLE_ANGRY_AR2: + eNewActivity = ACT_IDLE_ANGRY_SMG1; + break; + + case ACT_WALK_AIM_SHOTGUN: + case ACT_WALK_AIM_AR2: + eNewActivity = ACT_WALK_AIM_RIFLE; + break; + + case ACT_RUN_AIM_SHOTGUN: + case ACT_RUN_AIM_AR2: + eNewActivity = ACT_RUN_AIM_RIFLE; + break; + + case ACT_RANGE_ATTACK_SHOTGUN_LOW: + case ACT_RANGE_ATTACK_AR2_LOW: + eNewActivity = ACT_RANGE_ATTACK_SMG1_LOW; + break; + } + } + } +#else // We need these so that we can pick up the shotgun to throw it in the balcony scene if ( eNewActivity == ACT_IDLE_ANGRY_SHOTGUN ) { @@ -246,6 +288,7 @@ Activity CNPC_Monk::NPC_TranslateActivity( Activity eNewActivity ) { return ACT_RANGE_ATTACK_SMG1_LOW; } +#endif return eNewActivity; } diff --git a/sp/src/game/server/hl2/npc_mossman.cpp b/sp/src/game/server/hl2/npc_mossman.cpp index ff924cd256..f92a8f51d9 100644 --- a/sp/src/game/server/hl2/npc_mossman.cpp +++ b/sp/src/game/server/hl2/npc_mossman.cpp @@ -41,6 +41,11 @@ class CNPC_Mossman : public CAI_PlayerAlly bool CreateBehaviors( void ); int SelectSchedule( void ); +#ifdef MAPBASE + // Use Mossman's default subtitle color (220,255,198) + bool GetGameTextSpeechParams( hudtextparms_t ¶ms ) { params.r1 = 220; params.g1 = 255; params.b1 = 198; return BaseClass::GetGameTextSpeechParams( params ); } +#endif + private: CAI_FollowBehavior m_FollowBehavior; }; diff --git a/sp/src/game/server/hl2/npc_playercompanion.cpp b/sp/src/game/server/hl2/npc_playercompanion.cpp index 76166b1ba4..68568a7283 100644 --- a/sp/src/game/server/hl2/npc_playercompanion.cpp +++ b/sp/src/game/server/hl2/npc_playercompanion.cpp @@ -34,6 +34,7 @@ #ifdef MAPBASE #include "mapbase/GlobalStrings.h" #include "world.h" +#include "vehicle_base.h" #endif ConVar ai_debug_readiness("ai_debug_readiness", "0" ); @@ -60,6 +61,10 @@ int AE_COMPANION_RELEASE_FLARE; #define COMPANION_MELEE_DIST 64.0 #endif +#ifdef MAPBASE +ConVar ai_allow_new_weapons( "ai_allow_new_weapons", "1", FCVAR_NONE, "Allows companion NPCs to automatically pick up and use weapons they were unable pick up before, i.e. 357s or crossbows." ); +#endif + #define MAX_TIME_BETWEEN_BARRELS_EXPLODING 5.0f #define MAX_TIME_BETWEEN_CONSECUTIVE_PLAYER_KILLS 3.0f @@ -148,6 +153,11 @@ BEGIN_DATADESC( CNPC_PlayerCompanion ) #ifdef MAPBASE DEFINE_AIGRENADE_DATADESC() DEFINE_INPUT( m_iGrenadeCapabilities, FIELD_INTEGER, "SetGrenadeCapabilities" ), + DEFINE_INPUT( m_iGrenadeDropCapabilities, FIELD_INTEGER, "SetGrenadeDropCapabilities" ), +#endif + +#ifdef COMPANION_MELEE_ATTACK + DEFINE_FIELD( m_nMeleeDamage, FIELD_INTEGER ), #endif END_DATADESC() @@ -175,6 +185,19 @@ string_t CNPC_PlayerCompanion::gm_iszAR2Classname; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- +CNPC_PlayerCompanion::CNPC_PlayerCompanion() +{ +#ifdef MAPBASE + if (ai_grenade_always_drop.GetBool()) + { + m_iGrenadeDropCapabilities = (eGrenadeDropCapabilities)(GRENDROPCAP_GRENADE | GRENDROPCAP_ALTFIRE | GRENDROPCAP_INTERRUPTED); + } +#endif +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + bool CNPC_PlayerCompanion::CreateBehaviors() { #ifdef HL2_EPISODIC @@ -196,6 +219,10 @@ bool CNPC_PlayerCompanion::CreateBehaviors() AddBehavior( &m_FollowBehavior ); AddBehavior( &m_LeadBehavior ); #endif//HL2_EPISODIC + +#ifdef MAPBASE + AddBehavior( &m_FuncTankBehavior ); +#endif return BaseClass::CreateBehaviors(); } @@ -219,6 +246,11 @@ void CNPC_PlayerCompanion::Precache() #endif #endif +#ifdef MAPBASE + // Moved from Spawn() + SelectModel(); +#endif + PrecacheModel( STRING( GetModelName() ) ); #ifdef HL2_EPISODIC @@ -237,7 +269,9 @@ void CNPC_PlayerCompanion::Precache() //----------------------------------------------------------------------------- void CNPC_PlayerCompanion::Spawn() { +#ifndef MAPBASE // Moved to Precache() SelectModel(); +#endif Precache(); @@ -1620,6 +1654,19 @@ Activity CNPC_PlayerCompanion::TranslateActivityReadiness( Activity activity ) continue; } +#ifdef MAPBASE + // If we don't have the readiness activity we selected and there's no backup activity available, break the loop and return the base act. + bool bRequired; + if ( !HaveSequenceForActivity( actremap.mappedActivity ) && !HaveSequenceForActivity( Weapon_TranslateActivity( actremap.mappedActivity, &bRequired ) ) ) + { + Activity backupAct = Weapon_BackupActivity( actremap.mappedActivity, bRequired ); + if ( backupAct != actremap.mappedActivity ) + return backupAct; + else + break; + } +#endif + // We've successfully passed all criteria for remapping this return actremap.mappedActivity; } @@ -1643,21 +1690,6 @@ Activity CNPC_PlayerCompanion::NPC_TranslateActivity( Activity activity ) activity = ACT_RUN_PROTECTED; } -#ifdef COMPANION_HOLSTER_WORKAROUND - if (activity == ACT_DISARM || activity == ACT_ARM) - { - CBaseCombatWeapon *pWeapon = GetActiveWeapon() ? GetActiveWeapon() : m_hWeapons[m_iLastHolsteredWeapon]; - if (pWeapon && pWeapon->WeaponClassify() != WEPCLASS_HANDGUN) - { - switch (activity) - { - case ACT_DISARM: return ACT_DISARM_RIFLE; - case ACT_ARM: return ACT_ARM_RIFLE; - } - } - } -#endif - activity = BaseClass::NPC_TranslateActivity( activity ); if ( activity == ACT_IDLE ) @@ -1928,10 +1960,9 @@ bool CNPC_PlayerCompanion::IsReadinessCapable() { // Rather than looking up the activity string, we just make sure our weapon accepts a few basic readiness activity overrides. // This lets us make sure our weapon is readiness-capable to begin with. - CBaseCombatWeapon *pWeapon = GetActiveWeapon(); - if ( pWeapon->ActivityOverride(ACT_IDLE_RELAXED, NULL) == ACT_IDLE_RELAXED && - pWeapon->ActivityOverride( ACT_IDLE_STIMULATED, NULL ) == ACT_IDLE_STIMULATED && - pWeapon->ActivityOverride( ACT_IDLE_AGITATED, NULL ) == ACT_IDLE_AGITATED ) + if ( TranslateActivity( ACT_IDLE_RELAXED ) == ACT_IDLE_RELAXED && + TranslateActivity( ACT_IDLE_STIMULATED ) == ACT_IDLE_STIMULATED && + TranslateActivity( ACT_IDLE_AGITATED ) == ACT_IDLE_AGITATED ) return false; if (LookupActivity( "ACT_IDLE_AIM_RIFLE_STIMULATED" ) == ACT_INVALID) @@ -2726,6 +2757,13 @@ bool CNPC_PlayerCompanion::Weapon_CanUse( CBaseCombatWeapon *pWeapon ) { return (NumWeaponsInSquad("weapon_shotgun") < 1 ); } +#ifdef MAPBASE + else if (EntIsClass( pWeapon, gm_isz_class_Pistol ) || EntIsClass( pWeapon, gm_isz_class_357 ) || EntIsClass( pWeapon, gm_isz_class_Crossbow )) + { + // The AI automatically detects these weapons as usable now that there's animations for them, so ensure this behavior can be toggled in situations where that's not desirable + return ai_allow_new_weapons.GetBool(); + } +#endif else { return true; @@ -2762,6 +2800,21 @@ void CNPC_PlayerCompanion::Weapon_Equip( CBaseCombatWeapon *pWeapon ) m_bReadinessCapable = IsReadinessCapable(); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CNPC_PlayerCompanion::DoUnholster() +{ + if ( BaseClass::DoUnholster() ) + { + m_bReadinessCapable = IsReadinessCapable(); + return true; + } + + return false; +} +#endif + //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void CNPC_PlayerCompanion::PickupWeapon( CBaseCombatWeapon *pWeapon ) @@ -4179,10 +4232,44 @@ void CNPC_PlayerCompanion::OnPlayerKilledOther( CBaseEntity *pVictim, const CTak } #ifdef MAPBASE +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CNPC_PlayerCompanion::Event_Killed( const CTakeDamageInfo &info ) +{ + // For now, allied player companions are set to always drop grenades and other items + // even if the player did not kill them + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + if (!IsPlayerAlly( pPlayer )) + { + pPlayer = ToBasePlayer( info.GetAttacker() ); + + // See if there's a player in a vehicle instead (from CNPC_CombineS) + if ( !pPlayer ) + { + CPropVehicleDriveable *pVehicle = dynamic_cast( info.GetAttacker() ) ; + if ( pVehicle && pVehicle->GetDriver() && pVehicle->GetDriver()->IsPlayer() ) + { + pPlayer = assert_cast( pVehicle->GetDriver() ); + } + } + } + + if ( pPlayer != NULL ) + { + // Drop grenades if we should + DropGrenadeItemsOnDeath( info, pPlayer ); + } + + BaseClass::Event_Killed( info ); +} + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CNPC_PlayerCompanion::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info ) { + BaseClass::Event_KilledOther( pVictim, info ); + if ( pVictim ) { if (pVictim->IsPlayer() || (pVictim->IsNPC() && @@ -4223,17 +4310,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 +4376,6 @@ void CNPC_PlayerCompanion::DoCustomCombatAI( void ) { SpeakIfAllowed( TLK_MANY_ENEMIES ); } - */ } #endif diff --git a/sp/src/game/server/hl2/npc_playercompanion.h b/sp/src/game/server/hl2/npc_playercompanion.h index 81ba080c84..8dcf1aa167 100644 --- a/sp/src/game/server/hl2/npc_playercompanion.h +++ b/sp/src/game/server/hl2/npc_playercompanion.h @@ -23,6 +23,7 @@ #endif #ifdef MAPBASE +#include "ai_behavior_functank.h" #include "mapbase/ai_grenade.h" #endif @@ -108,8 +109,10 @@ class CNPC_PlayerCompanion : public CAI_PlayerAlly { DECLARE_CLASS( CNPC_PlayerCompanion, CAI_PlayerAlly ); #endif - public: + + CNPC_PlayerCompanion(); + //--------------------------------- bool CreateBehaviors(); void Precache(); @@ -237,6 +240,7 @@ class CNPC_PlayerCompanion : public CAI_PlayerAlly // This is just here to overwrite ai_playerally's TLK_ENEMY_DEAD virtual void OnKilledNPC(CBaseCombatCharacter *pKilled) {} + virtual void Event_Killed( const CTakeDamageInfo &info ); virtual void Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info ); virtual void DoCustomCombatAI( void ); #endif @@ -278,6 +282,9 @@ class CNPC_PlayerCompanion : public CAI_PlayerAlly bool ShouldLookForBetterWeapon(); bool Weapon_CanUse( CBaseCombatWeapon *pWeapon ); void Weapon_Equip( CBaseCombatWeapon *pWeapon ); +#ifdef MAPBASE + bool DoUnholster( void ); +#endif void PickupWeapon( CBaseCombatWeapon *pWeapon ); #if COMPANION_MELEE_ATTACK @@ -337,13 +344,18 @@ class CNPC_PlayerCompanion : public CAI_PlayerAlly bool AllowReadinessValueChange( void ); #ifdef MAPBASE - virtual bool IsAltFireCapable() { return (m_iGrenadeCapabilities & GRENCAP_ALTFIRE) != 0; } + virtual bool IsAltFireCapable() { return (m_iGrenadeCapabilities & GRENCAP_ALTFIRE) != 0 && BaseClass::IsAltFireCapable(); } virtual bool IsGrenadeCapable() { return (m_iGrenadeCapabilities & GRENCAP_GRENADE) != 0; } + virtual bool ShouldDropGrenades() { return (m_iGrenadeDropCapabilities & GRENDROPCAP_GRENADE) != 0 && BaseClass::ShouldDropGrenades(); } + virtual bool ShouldDropInterruptedGrenades() { return (m_iGrenadeDropCapabilities & GRENDROPCAP_INTERRUPTED) != 0 && BaseClass::ShouldDropInterruptedGrenades(); } + virtual bool ShouldDropAltFire() { return (m_iGrenadeDropCapabilities & GRENDROPCAP_ALTFIRE) != 0 && BaseClass::ShouldDropAltFire(); } + private: // Determines whether this NPC is allowed to use grenades or alt-fire stuff. eGrenadeCapabilities m_iGrenadeCapabilities; + eGrenadeDropCapabilities m_iGrenadeDropCapabilities; #endif protected: @@ -424,6 +436,9 @@ class CNPC_PlayerCompanion : public CAI_PlayerAlly CAI_OperatorBehavior m_OperatorBehavior; CAI_PassengerBehaviorCompanion m_PassengerBehavior; CAI_FearBehavior m_FearBehavior; +#endif +#ifdef MAPBASE + CAI_FuncTankBehavior m_FuncTankBehavior; #endif //----------------------------------------------------- diff --git a/sp/src/game/server/hl2/npc_rollermine.cpp b/sp/src/game/server/hl2/npc_rollermine.cpp index ec9be919cd..fc803f1f67 100644 --- a/sp/src/game/server/hl2/npc_rollermine.cpp +++ b/sp/src/game/server/hl2/npc_rollermine.cpp @@ -310,6 +310,16 @@ class CNPC_RollerMine : public CNPCBaseInteractive, public CDefault bool IsActive() { return m_flActiveTime > gpGlobals->curtime ? false : true; } + inline float GetForwardSpeed() const + { +#ifdef MAPBASE + if (m_flSpeedModifier != 1.0f) + return m_flForwardSpeed * m_flSpeedModifier; + else +#endif + return m_flForwardSpeed; + } + // INPCInteractive Functions virtual bool CanInteractWith( CAI_BaseNPC *pUser ) { return true; } virtual bool HasBeenInteractedWith() { return m_bHackedByAlyx; } @@ -1313,7 +1323,7 @@ void CNPC_RollerMine::RunTask( const Task_t *pTask ) Vector vecRight; AngleVectors( QAngle( 0, yaw, 0 ), NULL, &vecRight, NULL ); - m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, -m_flForwardSpeed * 5 ); + m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, -GetForwardSpeed() * 5 ); TaskComplete(); return; @@ -1323,6 +1333,8 @@ void CNPC_RollerMine::RunTask( const Task_t *pTask ) } { + float flForwardSpeed = GetForwardSpeed(); + float yaw = UTIL_VecToYaw( GetNavigator()->GetCurWaypointPos() - GetLocalOrigin() ); Vector vecRight; @@ -1362,17 +1374,17 @@ void CNPC_RollerMine::RunTask( const Task_t *pTask ) vecCompensate.y = -vecVelocity.x; vecCompensate.z = 0; - m_RollerController.m_vecAngular = WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecCompensate, m_flForwardSpeed * -0.75 ); + m_RollerController.m_vecAngular = WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecCompensate, flForwardSpeed * -0.75 ); } if( m_bHackedByAlyx ) { // Move faster. - m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, m_flForwardSpeed * 2.0f ); + m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, flForwardSpeed * 2.0f ); } else { - m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, m_flForwardSpeed ); + m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, flForwardSpeed ); } } break; @@ -1496,8 +1508,10 @@ void CNPC_RollerMine::RunTask( const Task_t *pTask ) vecCompensate.z = 0; VectorNormalize( vecCompensate ); - m_RollerController.m_vecAngular = WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecCompensate, m_flForwardSpeed * -0.75 ); - m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, m_flForwardSpeed * flTorqueFactor ); + float flForwardSpeed = GetForwardSpeed(); + + m_RollerController.m_vecAngular = WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecCompensate, flForwardSpeed * -0.75 ); + m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, flForwardSpeed * flTorqueFactor ); // Taunt when I get closer if( !(m_iSoundEventFlags & ROLLERMINE_SE_TAUNT) && UTIL_DistApprox( GetLocalOrigin(), vecTargetPosition ) <= 400 ) @@ -1615,8 +1629,10 @@ void CNPC_RollerMine::RunTask( const Task_t *pTask ) vecCompensate.z = 0; VectorNormalize( vecCompensate ); - m_RollerController.m_vecAngular = WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecCompensate, m_flForwardSpeed * -0.75 ); - m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, m_flForwardSpeed * flTorqueFactor ); + float flForwardSpeed = GetForwardSpeed(); + + m_RollerController.m_vecAngular = WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecCompensate, flForwardSpeed * -0.75 ); + m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, flForwardSpeed * flTorqueFactor ); // Once we're near the player, slow & stop if ( GetAbsOrigin().DistToSqr( vecTargetPosition ) < (ROLLERMINE_RETURN_TO_PLAYER_DIST*2.0) ) @@ -2572,6 +2588,12 @@ float CNPC_RollerMine::RollingSpeed() float rollingSpeed = angVel.Length() - 90; rollingSpeed = clamp( rollingSpeed, 1, MAX_ROLLING_SPEED ); rollingSpeed *= (1/MAX_ROLLING_SPEED); +#ifdef MAPBASE + if (m_flSpeedModifier != 1.0f) + { + rollingSpeed *= m_flSpeedModifier; + } +#endif return rollingSpeed; } return 0; diff --git a/sp/src/game/server/hl2/npc_scanner.cpp b/sp/src/game/server/hl2/npc_scanner.cpp index aac717c56e..0323afcc0c 100644 --- a/sp/src/game/server/hl2/npc_scanner.cpp +++ b/sp/src/game/server/hl2/npc_scanner.cpp @@ -2567,6 +2567,14 @@ void CNPC_CScanner::MoveToTarget( float flInterval, const Vector &vecMoveTarget myZAccel = flDist / flInterval; } +#ifdef MAPBASE + if (m_flSpeedModifier != 1.0f) + { + myAccel *= m_flSpeedModifier; + //myZAccel *= m_flSpeedModifier; + } +#endif + MoveInDirection( flInterval, targetDir, myAccel, myZAccel, myDecay ); // calc relative banking targets diff --git a/sp/src/game/server/hl2/npc_vortigaunt_episodic.cpp b/sp/src/game/server/hl2/npc_vortigaunt_episodic.cpp index 8dffd6dbac..218da1a552 100644 --- a/sp/src/game/server/hl2/npc_vortigaunt_episodic.cpp +++ b/sp/src/game/server/hl2/npc_vortigaunt_episodic.cpp @@ -582,8 +582,11 @@ bool CNPC_Vortigaunt::InnateWeaponLOSCondition( const Vector &ownerPos, const Ve UTIL_PredictedPosition( GetEnemy(), flTimeDelta, &vecNewTargetPos ); #ifdef MAPBASE - // This fix was created by DKY. - // His original comment is below. + // There's apparently a null pointer crash here + if (!GetEnemy()) + return false; + + // The fix below and its accompanying comment were created by DKY. /* @@ -1067,9 +1070,17 @@ Activity CNPC_Vortigaunt::NPC_TranslateActivity( Activity eNewActivity ) if ( GetReadinessLevel() >= AIRL_STIMULATED ) return ACT_IDLE_STIMULATED; } - + if ( eNewActivity == ACT_RANGE_ATTACK2 ) + { +#ifdef MAPBASE + // If we're capable of using grenades, use ACT_COMBINE_THROW_GRENADE + if (IsGrenadeCapable()) + return ACT_COMBINE_THROW_GRENADE; + else +#endif return (Activity) ACT_VORTIGAUNT_DISPEL; + } return BaseClass::NPC_TranslateActivity( eNewActivity ); } @@ -2715,6 +2726,15 @@ void CNPC_Vortigaunt::OnSquishedGrub( const CBaseEntity *pGrub ) //----------------------------------------------------------------------------- void CNPC_Vortigaunt::AimGun( void ) { +#ifdef MAPBASE + // Use base for func_tank + if (m_FuncTankBehavior.IsRunning()) + { + BaseClass::AimGun(); + return; + } +#endif + // If our aim lock is on, don't bother if ( m_flAimDelay >= gpGlobals->curtime ) return; diff --git a/sp/src/game/server/hl2/npc_vortigaunt_episodic.h b/sp/src/game/server/hl2/npc_vortigaunt_episodic.h index e0da23d3f3..91e6a3c639 100644 --- a/sp/src/game/server/hl2/npc_vortigaunt_episodic.h +++ b/sp/src/game/server/hl2/npc_vortigaunt_episodic.h @@ -138,6 +138,13 @@ class CNPC_Vortigaunt : public CNPC_PlayerCompanion // 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 ¶ms ) { params.r1 = 188; params.g1 = 241; params.b1 = 174; return BaseClass::GetGameTextSpeechParams( params ); } + + const char* GetGrenadeAttachment() { return "rightclaw"; } +#endif + private: int NumAntlionsInRadius( float flRadius ); diff --git a/sp/src/game/server/hl2/npc_zombie.cpp b/sp/src/game/server/hl2/npc_zombie.cpp index 382e2dd2ec..83a6ed9e3b 100644 --- a/sp/src/game/server/hl2/npc_zombie.cpp +++ b/sp/src/game/server/hl2/npc_zombie.cpp @@ -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 ); } //----------------------------------------------------------------------------- diff --git a/sp/src/game/server/hl2/proto_sniper.cpp b/sp/src/game/server/hl2/proto_sniper.cpp index 6146013075..84eba6df84 100644 --- a/sp/src/game/server/hl2/proto_sniper.cpp +++ b/sp/src/game/server/hl2/proto_sniper.cpp @@ -250,6 +250,10 @@ class CProtoSniper : public CAI_BaseNPC virtual int SelectSchedule( void ); virtual int TranslateSchedule( int scheduleType ); +#ifdef MAPBASE + Activity NPC_TranslateActivity( Activity eNewActivity ); +#endif + bool KeyValue( const char *szKeyName, const char *szValue ); void PrescheduleThink( void ); @@ -1519,6 +1523,8 @@ void CProtoSniper::Event_Killed( const CTakeDamageInfo &info ) void CProtoSniper::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info ) { #ifdef MAPBASE + BaseClass::Event_KilledOther( pVictim, info ); + if (pVictim == GetEnemy()) SetCondition(COND_SNIPER_KILLED_ENEMY); #endif @@ -2034,6 +2040,23 @@ int CProtoSniper::TranslateSchedule( int scheduleType ) return BaseClass::TranslateSchedule( scheduleType ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Activity CProtoSniper::NPC_TranslateActivity( Activity eNewActivity ) +{ + // ACT_IDLE is now just the soldier's unarmed idle animation. + // Use a gun-holding animation like what unhidden snipers were using before. + if (!HasSpawnFlags( SF_SNIPER_HIDDEN ) && eNewActivity == ACT_IDLE) + { + eNewActivity = ACT_IDLE_SMG1; + } + + return BaseClass::NPC_TranslateActivity( eNewActivity ); +} +#endif + //--------------------------------------------------------- //--------------------------------------------------------- void CProtoSniper::ScopeGlint() diff --git a/sp/src/game/server/hl2/vehicle_apc.cpp b/sp/src/game/server/hl2/vehicle_apc.cpp index 80c4291f7d..63d5e3ed8a 100644 --- a/sp/src/game/server/hl2/vehicle_apc.cpp +++ b/sp/src/game/server/hl2/vehicle_apc.cpp @@ -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(&info) ) != false) +#endif + { + m_iHealth = 0; + Event_Killed( dmgInfo ); + return 0; + } } // Chain diff --git a/sp/src/game/server/hl2/weapon_357.cpp b/sp/src/game/server/hl2/weapon_357.cpp index 3fa3fab842..a3c053719e 100644 --- a/sp/src/game/server/hl2/weapon_357.cpp +++ b/sp/src/game/server/hl2/weapon_357.cpp @@ -27,6 +27,11 @@ // CWeapon357 //----------------------------------------------------------------------------- +#ifdef MAPBASE +extern acttable_t *GetPistolActtable(); +extern int GetPistolActtableCount(); +#endif + class CWeapon357 : public CBaseHLCombatWeapon { DECLARE_CLASS( CWeapon357, CBaseHLCombatWeapon ); @@ -69,6 +74,9 @@ class CWeapon357 : public CBaseHLCombatWeapon void FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, Vector &vecShootOrigin, Vector &vecShootDir ); void Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ); + + virtual acttable_t *GetBackupActivityList() { return GetPistolActtable(); } + virtual int GetBackupActivityListCount() { return GetPistolActtableCount(); } #endif DECLARE_SERVERCLASS(); @@ -91,6 +99,22 @@ END_DATADESC() #ifdef MAPBASE acttable_t CWeapon357::m_acttable[] = { +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_IDLE, ACT_IDLE_REVOLVER, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_REVOLVER, true }, + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_REVOLVER, true }, + { ACT_RELOAD, ACT_RELOAD_REVOLVER, true }, + { ACT_WALK_AIM, ACT_WALK_AIM_REVOLVER, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_REVOLVER, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_REVOLVER, true }, + { ACT_RELOAD_LOW, ACT_RELOAD_REVOLVER_LOW, false }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_REVOLVER_LOW, false }, + { ACT_COVER_LOW, ACT_COVER_REVOLVER_LOW, false }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_REVOLVER_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_REVOLVER, false }, + { ACT_WALK, ACT_WALK_REVOLVER, true }, + { ACT_RUN, ACT_RUN_REVOLVER, true }, +#else { ACT_IDLE, ACT_IDLE_PISTOL, true }, { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_PISTOL, true }, { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_PISTOL, true }, @@ -105,10 +129,113 @@ acttable_t CWeapon357::m_acttable[] = { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_PISTOL, false }, { ACT_WALK, ACT_WALK_PISTOL, false }, { ACT_RUN, ACT_RUN_PISTOL, false }, +#endif + + // + // Activities ported from weapon_alyxgun below + // + + // Readiness activities (not aiming) +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_IDLE_RELAXED, ACT_IDLE_PISTOL_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_PISTOL_STIMULATED, false }, +#else + { ACT_IDLE_RELAXED, ACT_IDLE_PISTOL, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_STIMULATED, false }, +#endif + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims + { ACT_IDLE_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_WALK_RELAXED, ACT_WALK_PISTOL_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_PISTOL_STIMULATED, false }, +#else + { ACT_WALK_RELAXED, ACT_WALK, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_STIMULATED, false }, +#endif + { ACT_WALK_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims + { ACT_WALK_STEALTH, ACT_WALK_STEALTH_PISTOL, false }, + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_RUN_RELAXED, ACT_RUN_PISTOL_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_PISTOL_STIMULATED, false }, +#else + { ACT_RUN_RELAXED, ACT_RUN, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_STIMULATED, false }, +#endif + { ACT_RUN_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims + { ACT_RUN_STEALTH, ACT_RUN_STEALTH_PISTOL, false }, + + // Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_PISTOL, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_ANGRY_PISTOL, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims + { ACT_IDLE_AIM_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, + + { ACT_WALK_AIM_RELAXED, ACT_WALK, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_PISTOL, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims + { ACT_WALK_AIM_STEALTH, ACT_WALK_AIM_STEALTH_PISTOL, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_PISTOL, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims + { ACT_RUN_AIM_STEALTH, ACT_RUN_AIM_STEALTH_PISTOL, false },//always aims + //End readiness activities + + // Crouch activities + { ACT_CROUCHIDLE_STIMULATED, ACT_CROUCHIDLE_STIMULATED, false }, + { ACT_CROUCHIDLE_AIM_STIMULATED,ACT_RANGE_AIM_PISTOL_LOW, false },//always aims + { ACT_CROUCHIDLE_AGITATED, ACT_RANGE_AIM_PISTOL_LOW, false },//always aims + + // Readiness translations + { ACT_READINESS_RELAXED_TO_STIMULATED, ACT_READINESS_PISTOL_RELAXED_TO_STIMULATED, false }, + { ACT_READINESS_RELAXED_TO_STIMULATED_WALK, ACT_READINESS_PISTOL_RELAXED_TO_STIMULATED_WALK, false }, + { ACT_READINESS_AGITATED_TO_STIMULATED, ACT_READINESS_PISTOL_AGITATED_TO_STIMULATED, false }, + { ACT_READINESS_STIMULATED_TO_RELAXED, ACT_READINESS_PISTOL_STIMULATED_TO_RELAXED, false }, + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_REVOLVER_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_REVOLVER_MED, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_REVOLVER, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_REVOLVER, false }, + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_REVOLVER, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_REVOLVER, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_REVOLVER, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_REVOLVER, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_REVOLVER, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_REVOLVER, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_REVOLVER, false }, +#else + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_PISTOL, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_PISTOL, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_PISTOL, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_PISTOL, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_PISTOL, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_PISTOL, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE( CWeapon357 ); + +// Allows Weapon_BackupActivity() to access the 357's activity table. +acttable_t *Get357Acttable() +{ + return CWeapon357::m_acttable; +} + +int Get357ActtableCount() +{ + return ARRAYSIZE(CWeapon357::m_acttable); +} #endif //----------------------------------------------------------------------------- diff --git a/sp/src/game/server/hl2/weapon_alyxgun.cpp b/sp/src/game/server/hl2/weapon_alyxgun.cpp index 270a503cd1..5bf20eb6d9 100644 --- a/sp/src/game/server/hl2/weapon_alyxgun.cpp +++ b/sp/src/game/server/hl2/weapon_alyxgun.cpp @@ -37,20 +37,40 @@ acttable_t CWeaponAlyxGun::m_acttable[] = { ACT_RELOAD_LOW, ACT_RELOAD_PISTOL_LOW, true }, { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_PISTOL_LOW, true }, { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_PISTOL, true }, +#ifdef MAPBASE + // For non-Alyx NPCs + { ACT_WALK, ACT_WALK_PISTOL, false }, + { ACT_RUN, ACT_RUN_PISTOL, false }, +#endif // Readiness activities (not aiming) +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_IDLE_RELAXED, ACT_IDLE_PISTOL_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_PISTOL_STIMULATED, false }, +#else { ACT_IDLE_RELAXED, ACT_IDLE_PISTOL, false },//never aims { ACT_IDLE_STIMULATED, ACT_IDLE_STIMULATED, false }, +#endif { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims { ACT_IDLE_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_WALK_RELAXED, ACT_WALK_PISTOL_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_PISTOL_STIMULATED, false }, +#else { ACT_WALK_RELAXED, ACT_WALK, false },//never aims { ACT_WALK_STIMULATED, ACT_WALK_STIMULATED, false }, +#endif { ACT_WALK_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims { ACT_WALK_STEALTH, ACT_WALK_STEALTH_PISTOL, false }, - + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_RUN_RELAXED, ACT_RUN_PISTOL_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_PISTOL_STIMULATED, false }, +#else { ACT_RUN_RELAXED, ACT_RUN, false },//never aims { ACT_RUN_STIMULATED, ACT_RUN_STIMULATED, false }, +#endif { ACT_RUN_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims { ACT_RUN_STEALTH, ACT_RUN_STEALTH_PISTOL, false }, @@ -85,6 +105,21 @@ acttable_t CWeaponAlyxGun::m_acttable[] = // { ACT_ARM, ACT_ARM_PISTOL, true }, // { ACT_DISARM, ACT_DISARM_PISTOL, true }, + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_PISTOL, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_PISTOL, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_PISTOL, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_PISTOL, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_PISTOL, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_PISTOL, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_PISTOL, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_PISTOL, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponAlyxGun); diff --git a/sp/src/game/server/hl2/weapon_alyxgun.h b/sp/src/game/server/hl2/weapon_alyxgun.h index cc3e862cb2..4dd95532ac 100644 --- a/sp/src/game/server/hl2/weapon_alyxgun.h +++ b/sp/src/game/server/hl2/weapon_alyxgun.h @@ -13,6 +13,11 @@ #pragma once #endif +#ifdef MAPBASE +extern acttable_t *GetPistolActtable(); +extern int GetPistolActtableCount(); +#endif + class CWeaponAlyxGun : public CHLSelectFireMachineGun { DECLARE_DATADESC(); @@ -51,6 +56,11 @@ class CWeaponAlyxGun : public CHLSelectFireMachineGun SetTouch(NULL); } +#ifdef MAPBASE + virtual acttable_t *GetBackupActivityList() { return GetPistolActtable(); } + virtual int GetBackupActivityListCount() { return GetPistolActtableCount(); } +#endif + float m_flTooCloseTimer; DECLARE_ACTTABLE(); diff --git a/sp/src/game/server/hl2/weapon_annabelle.cpp b/sp/src/game/server/hl2/weapon_annabelle.cpp index 47ee9e3970..6b7ecdd9ce 100644 --- a/sp/src/game/server/hl2/weapon_annabelle.cpp +++ b/sp/src/game/server/hl2/weapon_annabelle.cpp @@ -20,6 +20,11 @@ extern ConVar sk_auto_reload_time; +#ifdef MAPBASE +extern acttable_t *GetShotgunActtable(); +extern int GetShotgunActtableCount(); +#endif + class CWeaponAnnabelle : public CBaseHLCombatWeapon { DECLARE_DATADESC(); @@ -41,6 +46,16 @@ class CWeaponAnnabelle : public CBaseHLCombatWeapon virtual const Vector& GetBulletSpread( void ) { static Vector cone = vec3_origin; + +#ifdef MAPBASE + if (GetOwner() && GetOwner()->OverridingWeaponProficiency()) + { + // If the owner's weapon proficiency is being overridden, return a more realistic spread + static Vector cone2 = VECTOR_CONE_6DEGREES; + return cone2; + } +#endif + return cone; } @@ -61,6 +76,11 @@ class CWeaponAnnabelle : public CBaseHLCombatWeapon void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); +#ifdef MAPBASE + virtual acttable_t *GetBackupActivityList() { return GetShotgunActtable(); } + virtual int GetBackupActivityListCount() { return GetShotgunActtableCount(); } +#endif + DECLARE_ACTTABLE(); CWeaponAnnabelle(void); @@ -82,6 +102,28 @@ END_DATADESC() acttable_t CWeaponAnnabelle::m_acttable[] = { +#if defined(EXPANDED_HL2_WEAPON_ACTIVITIES) && AR2_ACTIVITY_FIX == 1 + { ACT_IDLE, ACT_IDLE_AR2, false }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_AR2, true }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_AR2_LOW, false }, + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_ANNABELLE, true }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_ANNABELLE_LOW, true }, + { ACT_RELOAD, ACT_RELOAD_ANNABELLE, true }, + { ACT_WALK, ACT_WALK_AR2, true }, + { ACT_WALK_AIM, ACT_WALK_AIM_AR2, true }, + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, false }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, false }, + { ACT_RUN, ACT_RUN_AR2, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_AR2, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, false }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, false }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_ANNABELLE, true }, + { ACT_RELOAD_LOW, ACT_RELOAD_ANNABELLE_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_ANNABELLE, false }, + + { ACT_ARM, ACT_ARM_RIFLE, true }, + { ACT_DISARM, ACT_DISARM_RIFLE, true }, +#else #ifdef MAPBASE { ACT_IDLE, ACT_IDLE_SMG1, false }, #endif @@ -99,6 +141,22 @@ acttable_t CWeaponAnnabelle::m_acttable[] = { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_SHOTGUN, true }, { ACT_RELOAD_LOW, ACT_RELOAD_SMG1_LOW, false }, { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SMG1, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_AR2, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_AR2, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_AR2, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_AR2, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_SHOTGUN, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_AR2, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_AR2, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_AR2, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_SHOTGUN, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponAnnabelle); @@ -175,6 +233,13 @@ bool CWeaponAnnabelle::StartReload( void ) pOwner->m_flNextAttack = gpGlobals->curtime; m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); +#ifdef MAPBASE + if ( pOwner->IsPlayer() ) + { + static_cast(pOwner)->SetAnimation( PLAYER_RELOAD ); + } +#endif + m_bInReload = true; return true; } diff --git a/sp/src/game/server/hl2/weapon_ar1.cpp b/sp/src/game/server/hl2/weapon_ar1.cpp index 994cdabbb0..c6e492398c 100644 --- a/sp/src/game/server/hl2/weapon_ar1.cpp +++ b/sp/src/game/server/hl2/weapon_ar1.cpp @@ -37,6 +37,10 @@ float Damage[ MAX_SETTINGS ] = 20, }; +#ifdef MAPBASE +extern acttable_t *GetAR2Acttable(); +extern int GetAR2ActtableCount(); +#endif //========================================================= //========================================================= @@ -93,6 +97,12 @@ class CWeaponAR1 : public CHLMachineGun break; } } + +#ifdef MAPBSAE + virtual acttable_t *GetBackupActivityList() { return GetAR2Acttable(); } + virtual int GetBackupActivityListCount() { return GetAR2ActtableCount(); } +#endif + DECLARE_ACTTABLE(); }; @@ -105,6 +115,77 @@ PRECACHE_WEAPON_REGISTER(weapon_ar1); acttable_t CWeaponAR1::m_acttable[] = { { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_AR1, true }, + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + // Optional new NPC activities + // (these should fall back to AR2 animations when they don't exist on an NPC) + { ACT_RELOAD, ACT_RELOAD_AR1, true }, + { ACT_IDLE, ACT_IDLE_AR1, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_AR1, true }, + +// Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_AR1_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_AR1_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_AR1, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_AR1_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_AR1_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_AR1, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_AR1_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_AR1_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_AR1, false },//always aims + +// Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_AR1_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_AR1_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_AR1, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_AR1_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_AR1_STIMULATED, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_AR1, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_AR1_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_AR1_STIMULATED, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_AR1, false },//always aims +//End readiness activities + + { ACT_WALK, ACT_WALK_AR1, true }, + { ACT_WALK_AIM, ACT_WALK_AIM_AR1, true }, + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, true }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, true }, + { ACT_RUN, ACT_RUN_AR1, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_AR1, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, true }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_AR1, true }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_AR1_LOW, true }, + { ACT_COVER_LOW, ACT_COVER_AR1_LOW, false }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_AR1_LOW, false }, + { ACT_RELOAD_LOW, ACT_RELOAD_AR1_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_AR1, true }, + + { ACT_ARM, ACT_ARM_RIFLE, false }, + { ACT_DISARM, ACT_DISARM_RIFLE, false }, + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_AR1_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_AR1_MED, false }, +#endif + +#if EXPANDED_HL2DM_ACTIVITIES + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_AR1, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_AR1, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_AR1, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_AR1, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_AR1, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_AR1, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_AR1, false }, + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_AR1, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR1, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponAR1); diff --git a/sp/src/game/server/hl2/weapon_ar2.cpp b/sp/src/game/server/hl2/weapon_ar2.cpp index 89a16700c5..6e7d4cac27 100644 --- a/sp/src/game/server/hl2/weapon_ar2.cpp +++ b/sp/src/game/server/hl2/weapon_ar2.cpp @@ -38,10 +38,6 @@ ConVar sk_weapon_ar2_alt_fire_radius( "sk_weapon_ar2_alt_fire_radius", "10" ); ConVar sk_weapon_ar2_alt_fire_duration( "sk_weapon_ar2_alt_fire_duration", "2" ); ConVar sk_weapon_ar2_alt_fire_mass( "sk_weapon_ar2_alt_fire_mass", "150" ); -#ifdef MAPBASE -extern acttable_t *GetSMG1Acttable(); -#endif - //========================================================= //========================================================= @@ -100,14 +96,14 @@ acttable_t CWeaponAR2::m_acttable[] = { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, true }, { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, true }, { ACT_RUN, ACT_RUN_AR2, true }, - { ACT_RUN_AIM, ACT_RUN_AIM_RIFLE, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_AR2, true }, { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, true }, { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, true }, { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_AR2, false }, - { ACT_COVER_LOW, ACT_COVER_SMG1_LOW, false }, + { ACT_COVER_LOW, ACT_COVER_AR2_LOW, true }, { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_AR2_LOW, false }, { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_AR2_LOW, false }, - { ACT_RELOAD_LOW, ACT_RELOAD_SMG1_LOW, false }, + { ACT_RELOAD_LOW, ACT_RELOAD_AR2_LOW, false }, { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_AR2, true }, // { ACT_RANGE_ATTACK2, ACT_RANGE_ATTACK_AR2_GRENADE, true }, #else @@ -160,10 +156,53 @@ acttable_t CWeaponAR2::m_acttable[] = { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SMG1, true }, // { ACT_RANGE_ATTACK2, ACT_RANGE_ATTACK_AR2_GRENADE, true }, #endif + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_ARM, ACT_ARM_RIFLE, false }, + { ACT_DISARM, ACT_DISARM_RIFLE, false }, +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_AR2_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_AR2_MED, false }, + + { ACT_COVER_WALL_R, ACT_COVER_WALL_R_RIFLE, false }, + { ACT_COVER_WALL_L, ACT_COVER_WALL_L_RIFLE, false }, + { ACT_COVER_WALL_LOW_R, ACT_COVER_WALL_LOW_R_RIFLE, false }, + { ACT_COVER_WALL_LOW_L, ACT_COVER_WALL_LOW_L_RIFLE, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_AR2, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_AR2, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_AR2, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_AR2, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_AR2, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_AR2, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_AR2, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR2, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponAR2); +#ifdef MAPBASE +// Allows Weapon_BackupActivity() to access the AR2's activity table. +acttable_t *GetAR2Acttable() +{ + return CWeaponAR2::m_acttable; +} + +int GetAR2ActtableCount() +{ + return ARRAYSIZE(CWeaponAR2::m_acttable); +} +#endif + CWeaponAR2::CWeaponAR2( ) { m_fMinRange1 = 65; @@ -343,6 +382,9 @@ void CWeaponAR2::SecondaryAttack( void ) if( pPlayer ) { pPlayer->RumbleEffect(RUMBLE_AR2_ALT_FIRE, 0, RUMBLE_FLAG_RESTART ); +#ifdef MAPBASE + pPlayer->SetAnimation( PLAYER_ATTACK2 ); +#endif } SendWeaponAnim( ACT_VM_FIDGET ); diff --git a/sp/src/game/server/hl2/weapon_ar2.h b/sp/src/game/server/hl2/weapon_ar2.h index d5376a834c..5a855f2c8a 100644 --- a/sp/src/game/server/hl2/weapon_ar2.h +++ b/sp/src/game/server/hl2/weapon_ar2.h @@ -71,6 +71,9 @@ class CWeaponAR2 : public CHLMachineGun bool m_bShotDelayed; int m_nVentPose; +#ifdef MAPBASE // Make act table accessible outside class +public: +#endif DECLARE_ACTTABLE(); DECLARE_DATADESC(); }; diff --git a/sp/src/game/server/hl2/weapon_bugbait.cpp b/sp/src/game/server/hl2/weapon_bugbait.cpp index 612fe142c3..d9e6f94ca4 100644 --- a/sp/src/game/server/hl2/weapon_bugbait.cpp +++ b/sp/src/game/server/hl2/weapon_bugbait.cpp @@ -58,6 +58,9 @@ class CWeaponBugBait : public CBaseHLCombatWeapon bool ShouldDisplayHUDHint() { return true; } DECLARE_DATADESC(); +#ifdef MAPBASE + DECLARE_ACTTABLE(); +#endif protected: @@ -86,6 +89,26 @@ BEGIN_DATADESC( CWeaponBugBait ) END_DATADESC() +#ifdef MAPBASE +acttable_t CWeaponBugBait::m_acttable[] = +{ + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_GRENADE, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_GRENADE, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_GRENADE, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_GRENADE, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_GRENADE, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_GRENADE, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_GRENADE, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_GRENADE, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_GRENADE, false }, +#endif +}; + +IMPLEMENT_ACTTABLE( CWeaponBugBait ); +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -292,6 +315,10 @@ void CWeaponBugBait::ThrowGrenade( CBasePlayer *pPlayer ) } m_bRedraw = true; + +#ifdef MAPBASE + pPlayer->SetAnimation( PLAYER_ATTACK1 ); +#endif } //----------------------------------------------------------------------------- diff --git a/sp/src/game/server/hl2/weapon_citizenpackage.cpp b/sp/src/game/server/hl2/weapon_citizenpackage.cpp index cdb9378a12..95a0229633 100644 --- a/sp/src/game/server/hl2/weapon_citizenpackage.cpp +++ b/sp/src/game/server/hl2/weapon_citizenpackage.cpp @@ -23,6 +23,13 @@ acttable_t CWeaponCitizenPackage::m_acttable[] = { { ACT_IDLE, ACT_IDLE_PACKAGE, false }, { ACT_WALK, ACT_WALK_PACKAGE, false }, +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_RUN, ACT_RUN_PACKAGE, false }, + + { ACT_IDLE_ANGRY, ACT_IDLE_PACKAGE, false }, + { ACT_WALK_AIM, ACT_WALK_PACKAGE, false }, + { ACT_RUN_AIM, ACT_RUN_PACKAGE, false }, +#endif }; IMPLEMENT_ACTTABLE(CWeaponCitizenPackage); @@ -70,5 +77,12 @@ acttable_t CWeaponCitizenSuitcase::m_acttable[] = { { ACT_IDLE, ACT_IDLE_SUITCASE, false }, { ACT_WALK, ACT_WALK_SUITCASE, false }, +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_RUN, ACT_RUN_SUITCASE, false }, + + { ACT_IDLE_ANGRY, ACT_IDLE_SUITCASE, false }, + { ACT_WALK_AIM, ACT_WALK_SUITCASE, false }, + { ACT_RUN_AIM, ACT_RUN_SUITCASE, false }, +#endif }; IMPLEMENT_ACTTABLE(CWeaponCitizenSuitcase); diff --git a/sp/src/game/server/hl2/weapon_crossbow.cpp b/sp/src/game/server/hl2/weapon_crossbow.cpp index b86b4c05eb..8a0f306bfa 100644 --- a/sp/src/game/server/hl2/weapon_crossbow.cpp +++ b/sp/src/game/server/hl2/weapon_crossbow.cpp @@ -542,7 +542,7 @@ class CWeaponCrossbow : public CBaseHLCombatWeapon virtual bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL ); virtual bool Reload( void ); #ifdef MAPBASE - virtual void Reload_NPC( void ); + virtual void Reload_NPC( bool bPlaySound = true ); #endif virtual void ItemPostFrame( void ); virtual void ItemBusyFrame( void ); @@ -591,6 +591,7 @@ class CWeaponCrossbow : public CBaseHLCombatWeapon void CheckZoomToggle( void ); void FireBolt( void ); #ifdef MAPBASE + void SetBolt( int iSetting ); void FireNPCBolt( CAI_BaseNPC *pOwner, Vector &vecShootOrigin, Vector &vecShootDir ); #endif void ToggleZoom( void ); @@ -638,6 +639,57 @@ END_DATADESC() #ifdef MAPBASE acttable_t CWeaponCrossbow::m_acttable[] = { +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_CROSSBOW, true }, + { ACT_RELOAD, ACT_RELOAD_CROSSBOW, true }, + { ACT_IDLE, ACT_IDLE_CROSSBOW, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_CROSSBOW, true }, + +// Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_CROSSBOW_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_CROSSBOW_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_CROSSBOW, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_CROSSBOW_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_CROSSBOW_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_CROSSBOW, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_CROSSBOW_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_CROSSBOW_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_CROSSBOW, false },//always aims + +// Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_CROSSBOW_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_CROSSBOW_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_CROSSBOW, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_CROSSBOW_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_CROSSBOW_STIMULATED, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_CROSSBOW, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_CROSSBOW_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_CROSSBOW_STIMULATED, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_CROSSBOW, false },//always aims +//End readiness activities + + { ACT_WALK, ACT_WALK_CROSSBOW, true }, + { ACT_WALK_AIM, ACT_WALK_AIM_CROSSBOW, true }, + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, true }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, true }, + { ACT_RUN, ACT_RUN_CROSSBOW, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_CROSSBOW, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, true }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_CROSSBOW, true }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_CROSSBOW_LOW, true }, + { ACT_COVER_LOW, ACT_COVER_CROSSBOW_LOW, false }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_CROSSBOW_LOW, false }, + { ACT_RELOAD_LOW, ACT_RELOAD_CROSSBOW_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_CROSSBOW, true }, + + { ACT_ARM, ACT_ARM_RIFLE, false }, + { ACT_DISARM, ACT_DISARM_RIFLE, false }, +#else { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SMG1, true }, { ACT_RELOAD, ACT_RELOAD_SMG1, true }, { ACT_IDLE, ACT_IDLE_SMG1, true }, @@ -686,6 +738,27 @@ acttable_t CWeaponCrossbow::m_acttable[] = { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_SMG1_LOW, false }, { ACT_RELOAD_LOW, ACT_RELOAD_SMG1_LOW, false }, { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SMG1, true }, +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_CROSSBOW_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_CROSSBOW_MED, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_CROSSBOW, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_CROSSBOW, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_CROSSBOW, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_CROSSBOW, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_CROSSBOW, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_CROSSBOW, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_CROSSBOW, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_CROSSBOW, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_CROSSBOW, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponCrossbow); @@ -755,6 +828,10 @@ void CWeaponCrossbow::PrimaryAttack( void ) { m_iPrimaryAttacks++; gamestats->Event_WeaponFired( pPlayer, true, GetClassname() ); + +#ifdef MAPBASE + pPlayer->SetAnimation( PLAYER_ATTACK1 ); +#endif } } @@ -785,11 +862,11 @@ bool CWeaponCrossbow::Reload( void ) //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -void CWeaponCrossbow::Reload_NPC( void ) +void CWeaponCrossbow::Reload_NPC( bool bPlaySound ) { - BaseClass::Reload_NPC(); + BaseClass::Reload_NPC( bPlaySound ); - m_nSkin = 0; + SetBolt( 0 ); } #endif @@ -894,6 +971,10 @@ void CWeaponCrossbow::FireBolt( void ) m_iClip1--; +#ifdef MAPBASE + SetBolt( 1 ); +#endif + pOwner->ViewPunch( QAngle( -2, 0, 0 ) ); WeaponSound( SINGLE ); @@ -916,6 +997,18 @@ void CWeaponCrossbow::FireBolt( void ) } #ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Sets whether or not the bolt is visible +//----------------------------------------------------------------------------- +inline void CWeaponCrossbow::SetBolt( int iSetting ) +{ + int iBody = FindBodygroupByName( "bolt" ); + if (iBody != -1 || (GetOwner() && GetOwner()->IsPlayer())) // HACKHACK: Player models check the viewmodel instead of the worldmodel, so we have to do this manually + SetBodygroup( iBody, iSetting ); + else + m_nSkin = iSetting; +} + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -939,7 +1032,7 @@ void CWeaponCrossbow::FireNPCBolt( CAI_BaseNPC *pOwner, Vector &vecShootOrigin, m_iClip1--; - m_nSkin = 1; + SetBolt( 1 ); WeaponSound( SINGLE_NPC ); WeaponSound( SPECIAL2 ); @@ -961,11 +1054,18 @@ bool CWeaponCrossbow::Deploy( void ) { if ( m_iClip1 <= 0 ) { +#ifdef MAPBASE + SetBolt( 1 ); +#endif return DefaultDeploy( (char*)GetViewModel(), (char*)GetWorldModel(), ACT_CROSSBOW_DRAW_UNLOADED, (char*)GetAnimPrefix() ); } SetSkin( BOLT_SKIN_GLOW ); +#ifdef MAPBASE + SetBolt( 0 ); +#endif + return BaseClass::Deploy(); } @@ -1113,6 +1213,10 @@ void CWeaponCrossbow::SetChargerState( ChargerState_t state ) // Shoot some sparks and draw a beam between the two outer points DoLoadEffect(); +#ifdef MAPBASE + SetBolt( 0 ); +#endif + break; case CHARGER_STATE_START_CHARGE: diff --git a/sp/src/game/server/hl2/weapon_crowbar.cpp b/sp/src/game/server/hl2/weapon_crowbar.cpp index fdbc7943f6..7cc34b9cb3 100644 --- a/sp/src/game/server/hl2/weapon_crowbar.cpp +++ b/sp/src/game/server/hl2/weapon_crowbar.cpp @@ -42,6 +42,29 @@ acttable_t CWeaponCrowbar::m_acttable[] = { ACT_MELEE_ATTACK1, ACT_MELEE_ATTACK_SWING, true }, { ACT_IDLE, ACT_IDLE_ANGRY_MELEE, false }, { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_MELEE, false }, +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_RUN, ACT_RUN_MELEE, false }, + { ACT_WALK, ACT_WALK_MELEE, false }, + + { ACT_ARM, ACT_ARM_MELEE, false }, + { ACT_DISARM, ACT_DISARM_MELEE, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, true }, + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_MELEE, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_MELEE, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_MELEE, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_MELEE, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_MELEE, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_MELEE, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_MELEE, false }, + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_MELEE, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponCrowbar); diff --git a/sp/src/game/server/hl2/weapon_crowbar.h b/sp/src/game/server/hl2/weapon_crowbar.h index 1890ef6e05..26bb961fc4 100644 --- a/sp/src/game/server/hl2/weapon_crowbar.h +++ b/sp/src/game/server/hl2/weapon_crowbar.h @@ -46,6 +46,12 @@ class CWeaponCrowbar : public CBaseHLBludgeonWeapon // Animation event virtual void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); +#ifdef MAPBASE + // Don't use backup activities + acttable_t *GetBackupActivityList() { return NULL; } + int GetBackupActivityListCount() { return 0; } +#endif + private: // Animation event handlers void HandleAnimEventMeleeHit( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); diff --git a/sp/src/game/server/hl2/weapon_frag.cpp b/sp/src/game/server/hl2/weapon_frag.cpp index b0e188aa53..91eddcd3c5 100644 --- a/sp/src/game/server/hl2/weapon_frag.cpp +++ b/sp/src/game/server/hl2/weapon_frag.cpp @@ -82,6 +82,21 @@ END_DATADESC() acttable_t CWeaponFrag::m_acttable[] = { { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, true }, + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_GRENADE, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_GRENADE, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_GRENADE, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_GRENADE, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_GRENADE, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_GRENADE, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_GRENADE, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_GRENADE, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_GRENADE, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponFrag); @@ -404,6 +419,10 @@ void CWeaponFrag::ThrowGrenade( CBasePlayer *pPlayer ) WeaponSound( SINGLE ); +#ifdef MAPBASE + pPlayer->SetAnimation( PLAYER_ATTACK1 ); +#endif + m_iPrimaryAttacks++; gamestats->Event_WeaponFired( pPlayer, true, GetClassname() ); } @@ -428,6 +447,10 @@ void CWeaponFrag::LobGrenade( CBasePlayer *pPlayer ) WeaponSound( WPN_DOUBLE ); +#ifdef MAPBASE + pPlayer->SetAnimation( PLAYER_ATTACK2 ); +#endif + m_bRedraw = true; m_iPrimaryAttacks++; @@ -472,6 +495,10 @@ void CWeaponFrag::RollGrenade( CBasePlayer *pPlayer ) WeaponSound( SPECIAL1 ); +#ifdef MAPBASE + pPlayer->SetAnimation( PLAYER_ATTACK2 ); +#endif + m_bRedraw = true; m_iPrimaryAttacks++; diff --git a/sp/src/game/server/hl2/weapon_physcannon.cpp b/sp/src/game/server/hl2/weapon_physcannon.cpp index 87fddf4e9d..2e9854608d 100644 --- a/sp/src/game/server/hl2/weapon_physcannon.cpp +++ b/sp/src/game/server/hl2/weapon_physcannon.cpp @@ -1238,6 +1238,9 @@ class CWeaponPhysCannon : public CBaseHLCombatWeapon DECLARE_SERVERCLASS(); DECLARE_DATADESC(); +#ifdef MAPBASE + DECLARE_ACTTABLE(); +#endif CWeaponPhysCannon( void ); @@ -1455,6 +1458,30 @@ BEGIN_DATADESC( CWeaponPhysCannon ) END_DATADESC() +#ifdef MAPBASE +acttable_t CWeaponPhysCannon::m_acttable[] = +{ + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_PHYSGUN, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_PHYSGUN, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_PHYSGUN, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_PHYSGUN, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_PHYSGUN, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_PHYSGUN, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_PHYSGUN, false }, + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_PHYSGUN, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_PHYSGUN, false }, +#endif + + { ACT_ARM, ACT_ARM_RIFLE, false }, + { ACT_DISARM, ACT_DISARM_RIFLE, false }, +}; + +IMPLEMENT_ACTTABLE( CWeaponPhysCannon ); +#endif + enum { @@ -1738,6 +1765,9 @@ void CWeaponPhysCannon::DryFire( void ) if ( pOwner ) { pOwner->RumbleEffect( RUMBLE_PISTOL, 0, RUMBLE_FLAG_RESTART ); +#ifdef MAPBASE // TODO: Is this animation too dramatic? + pOwner->SetAnimation( PLAYER_ATTACK1 ); +#endif } } @@ -1794,6 +1824,11 @@ void CWeaponPhysCannon::PuntNonVPhysics( CBaseEntity *pEntity, const Vector &for PrimaryFireEffect(); SendWeaponAnim( ACT_VM_SECONDARYATTACK ); +#ifdef MAPBASE + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + if (pPlayer) + pPlayer->SetAnimation( PLAYER_ATTACK1 ); +#endif m_nChangeState = ELEMENT_STATE_CLOSED; m_flElementDebounce = gpGlobals->curtime + 0.5f; @@ -1944,6 +1979,10 @@ void CWeaponPhysCannon::PuntVPhysics( CBaseEntity *pEntity, const Vector &vecFor PrimaryFireEffect(); SendWeaponAnim( ACT_VM_SECONDARYATTACK ); +#ifdef MAPBASE + pOwner->SetAnimation( PLAYER_ATTACK1 ); +#endif + m_nChangeState = ELEMENT_STATE_CLOSED; m_flElementDebounce = gpGlobals->curtime + 0.5f; m_flCheckSuppressTime = gpGlobals->curtime + 0.25f; @@ -2062,6 +2101,10 @@ void CWeaponPhysCannon::PuntRagdoll( CBaseEntity *pEntity, const Vector &vecForw PrimaryFireEffect(); SendWeaponAnim( ACT_VM_SECONDARYATTACK ); +#ifdef MAPBASE + pOwner->SetAnimation( PLAYER_ATTACK1 ); +#endif + m_nChangeState = ELEMENT_STATE_CLOSED; m_flElementDebounce = gpGlobals->curtime + 0.5f; m_flCheckSuppressTime = gpGlobals->curtime + 0.25f; @@ -2167,6 +2210,9 @@ void CWeaponPhysCannon::PrimaryAttack( void ) PrimaryFireEffect(); SendWeaponAnim( ACT_VM_SECONDARYATTACK ); +#ifdef MAPBASE + pOwner->SetAnimation( PLAYER_ATTACK1 ); +#endif return; } diff --git a/sp/src/game/server/hl2/weapon_pistol.cpp b/sp/src/game/server/hl2/weapon_pistol.cpp index 80bb509091..9819f7481a 100644 --- a/sp/src/game/server/hl2/weapon_pistol.cpp +++ b/sp/src/game/server/hl2/weapon_pistol.cpp @@ -108,6 +108,12 @@ class CWeaponPistol : public CBaseHLCombatWeapon return 0.5f; } +#ifdef MAPBASE + // Pistols are their own backup activities + virtual acttable_t *GetBackupActivityList() { return NULL; } + virtual int GetBackupActivityListCount() { return 0; } +#endif + DECLARE_ACTTABLE(); private: @@ -149,11 +155,141 @@ acttable_t CWeaponPistol::m_acttable[] = { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_PISTOL, false }, { ACT_WALK, ACT_WALK_PISTOL, false }, { ACT_RUN, ACT_RUN_PISTOL, false }, + +#ifdef MAPBASE + // + // Activities ported from weapon_alyxgun below + // + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + // Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_PISTOL_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_PISTOL_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims + { ACT_IDLE_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, + + { ACT_WALK_RELAXED, ACT_WALK_PISTOL_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_PISTOL_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims + { ACT_WALK_STEALTH, ACT_WALK_STEALTH_PISTOL, false }, + + { ACT_RUN_RELAXED, ACT_RUN_PISTOL_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_PISTOL_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims + { ACT_RUN_STEALTH, ACT_RUN_STEALTH_PISTOL, false }, + + // Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_PISTOL_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_PISTOL_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims + { ACT_IDLE_AIM_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, + + { ACT_WALK_AIM_RELAXED, ACT_WALK_PISTOL_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_PISTOL, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims + { ACT_WALK_AIM_STEALTH, ACT_WALK_AIM_STEALTH_PISTOL, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_PISTOL_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_PISTOL, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims + { ACT_RUN_AIM_STEALTH, ACT_RUN_AIM_STEALTH_PISTOL, false },//always aims + //End readiness activities +#else + // Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_PISTOL, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims + { ACT_IDLE_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, + + { ACT_WALK_RELAXED, ACT_WALK, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims + { ACT_WALK_STEALTH, ACT_WALK_STEALTH_PISTOL, false }, + + { ACT_RUN_RELAXED, ACT_RUN, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims + { ACT_RUN_STEALTH, ACT_RUN_STEALTH_PISTOL, false }, + + // Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_PISTOL, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_ANGRY_PISTOL, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims + { ACT_IDLE_AIM_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, + + { ACT_WALK_AIM_RELAXED, ACT_WALK, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_PISTOL, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims + { ACT_WALK_AIM_STEALTH, ACT_WALK_AIM_STEALTH_PISTOL, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_PISTOL, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims + { ACT_RUN_AIM_STEALTH, ACT_RUN_AIM_STEALTH_PISTOL, false },//always aims + //End readiness activities +#endif + + // Crouch activities + { ACT_CROUCHIDLE_STIMULATED, ACT_CROUCHIDLE_STIMULATED, false }, + { ACT_CROUCHIDLE_AIM_STIMULATED,ACT_RANGE_AIM_PISTOL_LOW, false },//always aims + { ACT_CROUCHIDLE_AGITATED, ACT_RANGE_AIM_PISTOL_LOW, false },//always aims + + // Readiness translations + { ACT_READINESS_RELAXED_TO_STIMULATED, ACT_READINESS_PISTOL_RELAXED_TO_STIMULATED, false }, + { ACT_READINESS_RELAXED_TO_STIMULATED_WALK, ACT_READINESS_PISTOL_RELAXED_TO_STIMULATED_WALK, false }, + { ACT_READINESS_AGITATED_TO_STIMULATED, ACT_READINESS_PISTOL_AGITATED_TO_STIMULATED, false }, + { ACT_READINESS_STIMULATED_TO_RELAXED, ACT_READINESS_PISTOL_STIMULATED_TO_RELAXED, false }, +#endif + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_PISTOL, true }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_PISTOL, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_PISTOL, true }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_PISTOL, true }, +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_PISTOL_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_PISTOL_MED, false }, + + { ACT_COVER_WALL_R, ACT_COVER_WALL_R_PISTOL, false }, + { ACT_COVER_WALL_L, ACT_COVER_WALL_L_PISTOL, false }, + { ACT_COVER_WALL_LOW_R, ACT_COVER_WALL_LOW_R_PISTOL, false }, + { ACT_COVER_WALL_LOW_L, ACT_COVER_WALL_LOW_L_PISTOL, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_PISTOL, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_PISTOL, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_PISTOL, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_PISTOL, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_PISTOL, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_PISTOL, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_PISTOL, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_PISTOL, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE( CWeaponPistol ); +#ifdef MAPBASE +// Allows Weapon_BackupActivity() to access the pistol's activity table. +acttable_t *GetPistolActtable() +{ + return CWeaponPistol::m_acttable; +} + +int GetPistolActtableCount() +{ + return ARRAYSIZE(CWeaponPistol::m_acttable); +} +#endif + //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- diff --git a/sp/src/game/server/hl2/weapon_rpg.cpp b/sp/src/game/server/hl2/weapon_rpg.cpp index 3ae41e288d..645ca8e603 100644 --- a/sp/src/game/server/hl2/weapon_rpg.cpp +++ b/sp/src/game/server/hl2/weapon_rpg.cpp @@ -1399,6 +1399,11 @@ PRECACHE_WEAPON_REGISTER(weapon_rpg); acttable_t CWeaponRPG::m_acttable[] = { { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_RPG, true }, +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_RPG_LOW, false }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_RPG_LOW, false }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_RPG, false }, +#endif { ACT_IDLE_RELAXED, ACT_IDLE_RPG_RELAXED, true }, { ACT_IDLE_STIMULATED, ACT_IDLE_ANGRY_RPG, true }, @@ -1411,6 +1416,31 @@ acttable_t CWeaponRPG::m_acttable[] = { ACT_RUN, ACT_RUN_RPG, true }, { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RPG, true }, { ACT_COVER_LOW, ACT_COVER_LOW_RPG, true }, + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_ARM, ACT_ARM_RPG, false }, + { ACT_DISARM, ACT_DISARM_RPG, false }, +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_RPG_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_RPG_MED, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_RPG, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_RPG, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_RPG, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_RPG, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_RPG, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_RPG, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_RPG, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_RPG, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_RPG, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponRPG); @@ -1559,6 +1589,54 @@ void CWeaponRPG::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatChara } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponRPG::Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ) +{ + if ( m_hMissile != NULL ) + return; + + Vector muzzlePoint, vecShootDir; + QAngle angShootDir; + GetAttachment( LookupAttachment( "muzzle" ), muzzlePoint, angShootDir ); + AngleVectors( angShootDir, &vecShootDir ); + + // look for a better launch location + Vector altLaunchPoint; + if (GetAttachment( "missile", altLaunchPoint )) + { + // check to see if it's relativly free + trace_t tr; + AI_TraceHull( altLaunchPoint, altLaunchPoint + vecShootDir * (10.0f*12.0f), Vector( -24, -24, -24 ), Vector( 24, 24, 24 ), MASK_NPCSOLID, NULL, &tr ); + + if( tr.fraction == 1.0) + { + muzzlePoint = altLaunchPoint; + } + } + + m_hMissile = CMissile::Create( muzzlePoint, angShootDir, pOperator->edict() ); + m_hMissile->m_hOwner = this; + + // NPCs always get a grace period + m_hMissile->SetGracePeriod( 0.5 ); + + pOperator->DoMuzzleFlash(); + + WeaponSound( SINGLE_NPC ); + + // Make sure our laserdot is off + m_bGuiding = false; + + if ( m_hLaserDot ) + { + m_hLaserDot->TurnOff(); + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -1634,6 +1712,10 @@ void CWeaponRPG::PrimaryAttack( void ) SendWeaponAnim( ACT_VM_PRIMARYATTACK ); WeaponSound( SINGLE ); + +#ifdef MAPBASE + pOwner->SetAnimation( PLAYER_ATTACK1 ); +#endif pOwner->RumbleEffect( RUMBLE_SHOTGUN_SINGLE, 0, RUMBLE_FLAG_RESTART ); diff --git a/sp/src/game/server/hl2/weapon_rpg.h b/sp/src/game/server/hl2/weapon_rpg.h index 3c9dae5f61..a536d1c22f 100644 --- a/sp/src/game/server/hl2/weapon_rpg.h +++ b/sp/src/game/server/hl2/weapon_rpg.h @@ -216,6 +216,9 @@ class CWeaponRPG : public CBaseHLCombatWeapon int WeaponRangeAttack1Condition( float flDot, float flDist ); void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); +#ifdef MAPBASE + void Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ); +#endif void StartGuiding( void ); void StopGuiding( void ); void ToggleGuiding( void ); diff --git a/sp/src/game/server/hl2/weapon_shotgun.cpp b/sp/src/game/server/hl2/weapon_shotgun.cpp index fb9ec21561..5348e08a96 100644 --- a/sp/src/game/server/hl2/weapon_shotgun.cpp +++ b/sp/src/game/server/hl2/weapon_shotgun.cpp @@ -108,6 +108,57 @@ END_DATADESC() acttable_t CWeaponShotgun::m_acttable[] = { +#if EXPANDED_HL2_WEAPON_ACTIVITIES + // Note that ACT_IDLE_SHOTGUN_AGITATED seems to be a stand-in for ACT_IDLE_SHOTGUN on citizens, + // but that isn't acceptable for NPCs which don't use readiness activities. + { ACT_IDLE, ACT_IDLE_SHOTGUN, true }, + + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SHOTGUN, true }, + { ACT_RELOAD, ACT_RELOAD_SHOTGUN, false }, + { ACT_WALK, ACT_WALK_SHOTGUN, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_SHOTGUN, true }, + +// Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_SHOTGUN_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_SHOTGUN_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_SHOTGUN, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_SHOTGUN_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_SHOTGUN_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_SHOTGUN, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_SHOTGUN_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_SHOTGUN_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_SHOTGUN, false },//always aims + +// Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_SHOTGUN_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_SHOTGUN_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_SHOTGUN, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_SHOTGUN_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_SHOTGUN_STIMULATED, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_SHOTGUN, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_SHOTGUN_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_SHOTGUN_STIMULATED, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_SHOTGUN, false },//always aims +//End readiness activities + + { ACT_WALK_AIM, ACT_WALK_AIM_SHOTGUN, true }, + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, true }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, true }, + { ACT_RUN, ACT_RUN_SHOTGUN, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_SHOTGUN, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, true }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_SHOTGUN, true }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_SHOTGUN_LOW, true }, + { ACT_RELOAD_LOW, ACT_RELOAD_SHOTGUN_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SHOTGUN, false }, + { ACT_COVER_LOW, ACT_COVER_SHOTGUN_LOW, false }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_SHOTGUN_LOW, false }, +#else { ACT_IDLE, ACT_IDLE_SMG1, true }, // FIXME: hook to shotgun unique { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SHOTGUN, true }, @@ -153,10 +204,49 @@ acttable_t CWeaponShotgun::m_acttable[] = { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_SHOTGUN_LOW, true }, { ACT_RELOAD_LOW, ACT_RELOAD_SHOTGUN_LOW, false }, { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SHOTGUN, false }, +#endif + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_ARM, ACT_ARM_SHOTGUN, true }, + { ACT_DISARM, ACT_DISARM_SHOTGUN, true }, +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_SHOTGUN_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_SHOTGUN_MED, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_SHOTGUN, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_SHOTGUN, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_SHOTGUN, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_SHOTGUN, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_SHOTGUN, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_SHOTGUN, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_SHOTGUN, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_SHOTGUN, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_SHOTGUN, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponShotgun); +#ifdef MAPBASE +// Allows Weapon_BackupActivity() to access the shotgun's activity table. +acttable_t *GetShotgunActtable() +{ + return CWeaponShotgun::m_acttable; +} + +int GetShotgunActtableCount() +{ + return ARRAYSIZE(CWeaponShotgun::m_acttable); +} +#endif + void CWeaponShotgun::Precache( void ) { CBaseCombatWeapon::Precache(); @@ -316,6 +406,13 @@ bool CWeaponShotgun::StartReload( void ) pOwner->m_flNextAttack = gpGlobals->curtime; m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); +#ifdef MAPBASE + if ( pOwner->IsPlayer() ) + { + static_cast(pOwner)->SetAnimation( PLAYER_RELOAD ); + } +#endif + m_bInReload = true; return true; } @@ -469,7 +566,11 @@ void CWeaponShotgun::PrimaryAttack( void ) pPlayer->SetAnimation( PLAYER_ATTACK1 ); // Don't fire again until fire animation has completed +#ifdef MAPBASE + m_flNextPrimaryAttack = gpGlobals->curtime + GetViewModelSequenceDuration(); +#else m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); +#endif m_iClip1 -= 1; Vector vecSrc = pPlayer->Weapon_ShootPosition( ); @@ -524,10 +625,18 @@ void CWeaponShotgun::SecondaryAttack( void ) SendWeaponAnim( ACT_VM_SECONDARYATTACK ); // player "shoot" animation +#ifdef MAPBASE + pPlayer->SetAnimation( PLAYER_ATTACK2 ); +#else pPlayer->SetAnimation( PLAYER_ATTACK1 ); +#endif // Don't fire again until fire animation has completed +#ifdef MAPBASE + m_flNextPrimaryAttack = gpGlobals->curtime + GetViewModelSequenceDuration(); +#else m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); +#endif m_iClip1 -= 2; // Shotgun uses same clip for primary and secondary attacks Vector vecSrc = pPlayer->Weapon_ShootPosition(); diff --git a/sp/src/game/server/hl2/weapon_smg1.cpp b/sp/src/game/server/hl2/weapon_smg1.cpp index 97b0e89eac..b11fbedc38 100644 --- a/sp/src/game/server/hl2/weapon_smg1.cpp +++ b/sp/src/game/server/hl2/weapon_smg1.cpp @@ -134,6 +134,36 @@ acttable_t CWeaponSMG1::m_acttable[] = { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_SMG1_LOW, false }, { ACT_RELOAD_LOW, ACT_RELOAD_SMG1_LOW, false }, { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SMG1, true }, + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_ARM, ACT_ARM_RIFLE, false }, + { ACT_DISARM, ACT_DISARM_RIFLE, false }, +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_SMG1_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_SMG1_MED, false }, + + { ACT_COVER_WALL_R, ACT_COVER_WALL_R_RIFLE, false }, + { ACT_COVER_WALL_L, ACT_COVER_WALL_L_RIFLE, false }, + { ACT_COVER_WALL_LOW_R, ACT_COVER_WALL_LOW_R_RIFLE, false }, + { ACT_COVER_WALL_LOW_L, ACT_COVER_WALL_LOW_L_RIFLE, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_SMG1, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_SMG1, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_SMG1, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_SMG1, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG1, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_SMG1, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_SMG1, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_SMG1, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG1, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponSMG1); @@ -439,7 +469,11 @@ void CWeaponSMG1::SecondaryAttack( void ) CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 1000, 0.2, GetOwner(), SOUNDENT_CHANNEL_WEAPON ); // player "shoot" animation +#ifdef MAPBASE + pPlayer->SetAnimation( PLAYER_ATTACK2 ); +#else pPlayer->SetAnimation( PLAYER_ATTACK1 ); +#endif // Decrease ammo pPlayer->RemoveAmmo( 1, m_iSecondaryAmmoType ); diff --git a/sp/src/game/server/hl2/weapon_smg2.cpp b/sp/src/game/server/hl2/weapon_smg2.cpp index 498faddd0e..9c5037b959 100644 --- a/sp/src/game/server/hl2/weapon_smg2.cpp +++ b/sp/src/game/server/hl2/weapon_smg2.cpp @@ -46,6 +46,77 @@ PRECACHE_WEAPON_REGISTER(weapon_smg2); acttable_t CWeaponSMG2::m_acttable[] = { { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SMG2, true }, + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + // Optional new NPC activities + // (these should fall back to SMG animations when they don't exist on an NPC) + { ACT_RELOAD, ACT_RELOAD_SMG2, true }, + { ACT_IDLE, ACT_IDLE_SMG2, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_SMG2, true }, + +// Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_SMG2_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_SMG2_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_SMG2, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_SMG2_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_SMG2_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_SMG2, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_SMG2_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_SMG2_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_SMG2, false },//always aims + +// Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_SMG2_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_SMG2_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_SMG2, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_SMG2_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_SMG2_STIMULATED, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_SMG2, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_SMG2_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_SMG2_STIMULATED, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_SMG2, false },//always aims +//End readiness activities + + { ACT_WALK, ACT_WALK_SMG2, true }, + { ACT_WALK_AIM, ACT_WALK_AIM_SMG2, true }, + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, true }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, true }, + { ACT_RUN, ACT_RUN_SMG2, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_SMG2, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, true }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_SMG2, true }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_SMG2_LOW, true }, + { ACT_COVER_LOW, ACT_COVER_SMG2_LOW, false }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_SMG2_LOW, false }, + { ACT_RELOAD_LOW, ACT_RELOAD_SMG2_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SMG2, true }, + + { ACT_ARM, ACT_ARM_RIFLE, false }, + { ACT_DISARM, ACT_DISARM_RIFLE, false }, + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_SMG2_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_SMG2_MED, false }, +#endif + +#if EXPANDED_HL2DM_ACTIVITIES + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_SMG2, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_SMG2, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_SMG2, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_SMG2, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG2, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_SMG2, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_SMG2, false }, + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_SMG2, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG2, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponSMG2); diff --git a/sp/src/game/server/hl2/weapon_sniperrifle.cpp b/sp/src/game/server/hl2/weapon_sniperrifle.cpp index 901a73aea5..10777f89ad 100644 --- a/sp/src/game/server/hl2/weapon_sniperrifle.cpp +++ b/sp/src/game/server/hl2/weapon_sniperrifle.cpp @@ -46,6 +46,10 @@ static int g_nZoomFOV[] = 5 }; +#ifdef MAPBASE +extern acttable_t *GetAR2Acttable(); +extern int GetAR2ActtableCount(); +#endif class CWeaponSniperRifle : public CBaseHLCombatWeapon { @@ -72,6 +76,11 @@ class CWeaponSniperRifle : public CBaseHLCombatWeapon void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); +#ifdef MAPBSAE + virtual acttable_t *GetBackupActivityList() { return GetAR2Acttable(); } + virtual int GetBackupActivityListCount() { return GetAR2ActtableCount(); } +#endif + DECLARE_ACTTABLE(); protected: @@ -99,6 +108,77 @@ END_DATADESC() acttable_t CWeaponSniperRifle::m_acttable[] = { { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SNIPER_RIFLE, true } + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + // Optional new NPC activities + // (these should fall back to AR2 animations when they don't exist on an NPC) + { ACT_RELOAD, ACT_RELOAD_SNIPER_RIFLE, true }, + { ACT_IDLE, ACT_IDLE_SNIPER_RIFLE, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_SNIPER_RIFLE, true }, + +// Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_SNIPER_RIFLE_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_SNIPER_RIFLE_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_SNIPER_RIFLE, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_SNIPER_RIFLE_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_SNIPER_RIFLE_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_SNIPER_RIFLE, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_SNIPER_RIFLE_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_SNIPER_RIFLE_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_SNIPER_RIFLE, false },//always aims + +// Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_SNIPER_RIFLE_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_SNIPER_RIFLE_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_SNIPER_RIFLE, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_SNIPER_RIFLE_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_SNIPER_RIFLE_STIMULATED, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_SNIPER_RIFLE, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_SNIPER_RIFLE_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_SNIPER_RIFLE_STIMULATED, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_SNIPER_RIFLE, false },//always aims +//End readiness activities + + { ACT_WALK, ACT_WALK_SNIPER_RIFLE, true }, + { ACT_WALK_AIM, ACT_WALK_AIM_SNIPER_RIFLE, true }, + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, true }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, true }, + { ACT_RUN, ACT_RUN_SNIPER_RIFLE, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_SNIPER_RIFLE, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, true }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_SNIPER_RIFLE, true }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_SNIPER_RIFLE_LOW, true }, + { ACT_COVER_LOW, ACT_COVER_SNIPER_RIFLE_LOW, false }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_SNIPER_RIFLE_LOW, false }, + { ACT_RELOAD_LOW, ACT_RELOAD_SNIPER_RIFLE_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SNIPER_RIFLE, true }, + + { ACT_ARM, ACT_ARM_RIFLE, false }, + { ACT_DISARM, ACT_DISARM_RIFLE, false }, + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_SNIPER_RIFLE_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_SNIPER_RIFLE_MED, false }, +#endif + +#if EXPANDED_HL2DM_ACTIVITIES + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_SNIPER_RIFLE, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_SNIPER_RIFLE, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_SNIPER_RIFLE, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_SNIPER_RIFLE, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_SNIPER_RIFLE, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_SNIPER_RIFLE, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_SNIPER_RIFLE, false }, + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_SNIPER_RIFLE, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_SNIPER_RIFLE, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponSniperRifle); diff --git a/sp/src/game/server/hltvdirector.h b/sp/src/game/server/hltvdirector.h index 653bf1dd47..1b390e834d 100644 --- a/sp/src/game/server/hltvdirector.h +++ b/sp/src/game/server/hltvdirector.h @@ -67,7 +67,7 @@ class CHLTVDirector : public CGameEventListener, public CBaseGameSystemPerFrame, virtual void Shutdown(); virtual void FrameUpdatePostEntityThink(); virtual void LevelInitPostEntity(); - virtual char *GetFixedCameraEntityName( void ) { return "point_viewcontrol"; } + virtual const char *GetFixedCameraEntityName( void ) { return "point_viewcontrol"; } bool SetCameraMan( int iPlayerIndex ); int GetCameraMan() { return m_iCameraManIndex; } diff --git a/sp/src/game/server/items.h b/sp/src/game/server/items.h index 2089fa1fb3..6209c15ee0 100644 --- a/sp/src/game/server/items.h +++ b/sp/src/game/server/items.h @@ -89,9 +89,12 @@ class CItem : public CBaseAnimating, public CDefaultPlayerPickupVPhysics #endif #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. - virtual bool IsCombatItem() { return true; } + // This appeared to have no prior use in Source SDK 2013. + // It may have been originally intended for TF2 or some other game-specific item class. + virtual bool IsCombatItem() const { 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 ); diff --git a/sp/src/game/server/mapbase/GlobalStrings.h b/sp/src/game/server/mapbase/GlobalStrings.h index 336ff622cd..13c4775a6f 100644 --- a/sp/src/game/server/mapbase/GlobalStrings.h +++ b/sp/src/game/server/mapbase/GlobalStrings.h @@ -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; } diff --git a/sp/src/game/server/mapbase/ai_grenade.cpp b/sp/src/game/server/mapbase/ai_grenade.cpp index e14b47a3fe..a9ff5302e4 100644 --- a/sp/src/game/server/mapbase/ai_grenade.cpp +++ b/sp/src/game/server/mapbase/ai_grenade.cpp @@ -11,3 +11,5 @@ int COMBINE_AE_BEGIN_ALTFIRE; int COMBINE_AE_ALTFIRE; + +ConVar ai_grenade_always_drop( "ai_grenade_always_drop", "0", FCVAR_NONE, "Causes non-Combine grenade user NPCs to be allowed to drop grenades, alt-fire items, etc. *IF* the keyvalue has not already been set in Hammer. This is useful for debugging purposes or if a mod which was developed before this feature was introduced wants its NPCs to drop grenades and beyond without recompiling all of the maps." ); diff --git a/sp/src/game/server/mapbase/ai_grenade.h b/sp/src/game/server/mapbase/ai_grenade.h index 7f1ea8c13a..543969c23e 100644 --- a/sp/src/game/server/mapbase/ai_grenade.h +++ b/sp/src/game/server/mapbase/ai_grenade.h @@ -19,6 +19,10 @@ #include "basegrenade_shared.h" #include "ai_squad.h" #include "GlobalStrings.h" +#include "gameweaponmanager.h" +#include "hl2_gamerules.h" +#include "weapon_physcannon.h" +#include "globalstate.h" #define COMBINE_AE_GREN_TOSS ( 7 ) @@ -36,7 +40,9 @@ DEFINE_FIELD( m_flNextAltFireTime, FIELD_TIME ), \ DEFINE_FIELD( m_vecAltFireTarget, FIELD_VECTOR ), \ DEFINE_FIELD( m_vecTossVelocity, FIELD_VECTOR ), \ + DEFINE_FIELD( m_iLastAnimEventHandled, FIELD_INTEGER ), \ DEFINE_INPUTFUNC( FIELD_STRING, "ThrowGrenadeAtTarget", InputThrowGrenadeAtTarget ), \ + DEFINE_INPUTFUNC( FIELD_STRING, "ThrowGrenadeGestureAtTarget", InputThrowGrenadeGestureAtTarget ), \ DEFINE_INPUTFUNC( FIELD_INTEGER, "SetGrenades", InputSetGrenades ), \ DEFINE_INPUTFUNC( FIELD_INTEGER, "AddGrenades", InputAddGrenades ), \ DEFINE_OUTPUT(m_OnThrowGrenade, "OnThrowGrenade"), \ @@ -61,12 +67,22 @@ extern int COMBINE_AE_BEGIN_ALTFIRE; extern int COMBINE_AE_ALTFIRE; +extern ConVar ai_grenade_always_drop; + enum eGrenadeCapabilities { GRENCAP_GRENADE = (1 << 0), GRENCAP_ALTFIRE = (1 << 1), }; +// What grenade/item types NPCs are capable of dropping +enum eGrenadeDropCapabilities +{ + GRENDROPCAP_GRENADE = (1 << 0), + GRENDROPCAP_ALTFIRE = (1 << 1), + GRENDROPCAP_INTERRUPTED = (1 << 2), // Drops grenades when interrupted mid-animation +}; + //----------------------------------------------------------------------------- // Other classes can use this and access some CAI_GrenadeUser functions. //----------------------------------------------------------------------------- @@ -102,17 +118,20 @@ class CAI_GrenadeUser : public BASE_NPC, public CAI_GrenadeUserSink m_OnOutOfGrenades.Set( pLastGrenade, pLastGrenade, this ); } - virtual bool IsAltFireCapable() { return false; } + // Use secondary ammo as a way of checking if this is a weapon which can be alt-fired (e.g. AR2 or SMG) + virtual bool IsAltFireCapable() { return (this->GetActiveWeapon() && this->GetActiveWeapon()->UsesSecondaryAmmo()); } virtual bool IsGrenadeCapable() { return true; } inline bool HasGrenades() { return m_iNumGrenades > 0; } void InputSetGrenades( inputdata_t &inputdata ) { AddGrenades( inputdata.value.Int() - m_iNumGrenades ); } void InputAddGrenades( inputdata_t &inputdata ) { AddGrenades( inputdata.value.Int() ); } void InputThrowGrenadeAtTarget( inputdata_t &inputdata ); + void InputThrowGrenadeGestureAtTarget( inputdata_t &inputdata ); virtual void DelayGrenadeCheck( float delay ) { m_flNextGrenadeCheck = gpGlobals->curtime + delay; } void HandleAnimEvent( animevent_t *pEvent ); + void SetActivity( Activity NewActivity ); // Soldiers use "lefthand", cops use "LHand", and citizens use "anim_attachment_LH" virtual const char* GetGrenadeAttachment() { return "anim_attachment_LH"; } @@ -131,6 +150,12 @@ class CAI_GrenadeUser : public BASE_NPC, public CAI_GrenadeUserSink // For OnThrowGrenade + point_entity_replace, see grenade_frag.cpp bool UsingOnThrowGrenade() { return m_OnThrowGrenade.NumberOfElements() > 0; } + // For dropping grenades and beyond + void DropGrenadeItemsOnDeath( const CTakeDamageInfo &info, CBasePlayer *pPlayer ); + virtual bool ShouldDropGrenades() { return HasGrenades(); } + virtual bool ShouldDropInterruptedGrenades() { return true; } + virtual bool ShouldDropAltFire() { return HasGrenades(); } + protected: void StartTask_FaceAltFireTarget( const Task_t *pTask ); @@ -151,6 +176,9 @@ class CAI_GrenadeUser : public BASE_NPC, public CAI_GrenadeUserSink Vector m_vecAltFireTarget; Vector m_vecTossVelocity; + // CNPC_Combine port for determining if we tossed a grenade + int m_iLastAnimEventHandled; + COutputEHANDLE m_OnThrowGrenade; COutputEHANDLE m_OnOutOfGrenades; }; @@ -166,6 +194,8 @@ void CAI_GrenadeUser::HandleAnimEvent( animevent_t *pEvent ) if (this->GetActiveWeapon()) this->GetActiveWeapon()->WeaponSound( SPECIAL1 ); + m_iLastAnimEventHandled = pEvent->event; + //SpeakIfAllowed( TLK_CMB_THROWGRENADE, "altfire:1" ); return; } @@ -185,6 +215,8 @@ void CAI_GrenadeUser::HandleAnimEvent( animevent_t *pEvent ) AddGrenades(-1); + m_iLastAnimEventHandled = pEvent->event; + return; } @@ -221,12 +253,25 @@ void CAI_GrenadeUser::HandleAnimEvent( animevent_t *pEvent ) // wait six seconds before even looking again to see if a grenade can be thrown. m_flNextGrenadeCheck = gpGlobals->curtime + 6; + + m_iLastAnimEventHandled = pEvent->event; + return; } BaseClass::HandleAnimEvent( pEvent ); } +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +void CAI_GrenadeUser::SetActivity( Activity NewActivity ) +{ + BaseClass::SetActivity( NewActivity ); + + m_iLastAnimEventHandled = -1; +} + //----------------------------------------------------------------------------- // Purpose: Force the combine soldier to throw a grenade at the target // If I'm a combine elite, fire my combine ball at the target instead. @@ -252,6 +297,63 @@ void CAI_GrenadeUser::InputThrowGrenadeAtTarget( inputdata_t &inputdat this->ClearSchedule( "Told to throw grenade via input" ); } +//----------------------------------------------------------------------------- +// Purpose: Force the combine soldier to throw a grenade at the target using the gesture animation. +// If I'm a combine elite, fire my combine ball at the target instead. +// Input : &inputdata - +//----------------------------------------------------------------------------- +template +void CAI_GrenadeUser::InputThrowGrenadeGestureAtTarget( inputdata_t &inputdata ) +{ + // Ignore if we're inside a scripted sequence + //if ( this->GetState() == NPC_STATE_SCRIPT && this->m_hCine ) + // return; + + CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.String(), this, inputdata.pActivator, inputdata.pCaller ); + if ( !pEntity ) + { + DevMsg("%s (%s) received ThrowGrenadeGestureAtTarget input, but couldn't find target entity '%s'\n", this->GetClassname(), this->GetDebugName(), inputdata.value.String() ); + return; + } + + m_hForcedGrenadeTarget = pEntity; + m_flNextGrenadeCheck = 0; + + Vector vecTarget = m_hForcedGrenadeTarget->WorldSpaceCenter(); + +#if SHARED_COMBINE_ACTIVITIES + if (IsAltFireCapable()) + { + if (this->FVisible( m_hForcedGrenadeTarget )) + { + m_vecAltFireTarget = vecTarget; + m_hForcedGrenadeTarget = NULL; + + int iLayer = this->AddGesture( ACT_GESTURE_COMBINE_AR2_ALTFIRE ); + if (iLayer != -1) + { + this->GetShotRegulator()->FireNoEarlierThan( gpGlobals->curtime + this->GetLayerDuration( iLayer ) ); + } + } + } + else + { + // If we can, throw a grenade at the target. + // Ignore grenade count / distance / etc + if (CheckCanThrowGrenade( vecTarget )) + { + int iLayer = this->AddGesture( ACT_GESTURE_COMBINE_THROW_GRENADE ); + if (iLayer != -1) + { + this->GetShotRegulator()->FireNoEarlierThan( gpGlobals->curtime + this->GetLayerDuration( iLayer ) ); + } + } + } +#else + Warning("Gesture grenades/alt-fire not supported\n"); +#endif +} + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- template @@ -534,6 +636,105 @@ void CAI_GrenadeUser::ClearAttackConditions() } } +//----------------------------------------------------------------------------- +// Purpose: Drops grenades and alt-fire items on death. Based on code from npc_combines.cpp and npc_combine.cpp +//----------------------------------------------------------------------------- +template +void CAI_GrenadeUser::DropGrenadeItemsOnDeath( const CTakeDamageInfo &info, CBasePlayer *pPlayer ) +{ + // Elites drop alt-fire ammo, so long as they weren't killed by dissolving. + if( IsAltFireCapable() && ShouldDropAltFire() ) + { + CBaseEntity *pItem; + if (this->GetActiveWeapon() && FClassnameIs( this->GetActiveWeapon(), "weapon_smg1" )) + pItem = this->DropItem( "item_ammo_smg1_grenade", this->WorldSpaceCenter()+RandomVector(-4,4), RandomAngle(0,360) ); + else + pItem = this->DropItem( "item_ammo_ar2_altfire", this->WorldSpaceCenter() + RandomVector( -4, 4 ), RandomAngle( 0, 360 ) ); + + if ( pItem ) + { + IPhysicsObject *pObj = pItem->VPhysicsGetObject(); + + if ( pObj ) + { + Vector vel = RandomVector( -64.0f, 64.0f ); + AngularImpulse angImp = RandomAngularImpulse( -300.0f, 300.0f ); + + vel[2] = 0.0f; + pObj->AddVelocity( &vel, &angImp ); + } + + if( info.GetDamageType() & DMG_DISSOLVE ) + { + CBaseAnimating *pAnimating = dynamic_cast(pItem); + + if( pAnimating ) + { + pAnimating->Dissolve( NULL, gpGlobals->curtime, false, ENTITY_DISSOLVE_NORMAL ); + } + } + else + { + WeaponManager_AddManaged( pItem ); + } + } + } + + if ( IsGrenadeCapable() ) + { + if ( ShouldDropGrenades() ) + { + CHalfLife2 *pHL2GameRules = static_cast(g_pGameRules); + + // Attempt to drop a grenade + if ( pHL2GameRules->NPC_ShouldDropGrenade( pPlayer ) ) + { + this->DropItem( "weapon_frag", this->WorldSpaceCenter()+RandomVector(-4,4), RandomAngle(0,360) ); + pHL2GameRules->NPC_DroppedGrenade(); + } + } + + // if I was killed before I could finish throwing my grenade, drop + // a grenade item that the player can retrieve. + if (this->GetActivity() == ACT_RANGE_ATTACK2 && ShouldDropInterruptedGrenades()) + { + if( m_iLastAnimEventHandled != COMBINE_AE_GREN_TOSS ) + { + // Drop the grenade as an item. + Vector vecStart; + this->GetAttachment( GetGrenadeAttachment(), vecStart ); + + CBaseEntity *pItem = this->DropItem( "weapon_frag", vecStart, RandomAngle(0,360) ); + + if ( pItem ) + { + IPhysicsObject *pObj = pItem->VPhysicsGetObject(); + + if ( pObj ) + { + Vector vel; + vel.x = random->RandomFloat( -100.0f, 100.0f ); + vel.y = random->RandomFloat( -100.0f, 100.0f ); + vel.z = random->RandomFloat( 800.0f, 1200.0f ); + AngularImpulse angImp = RandomAngularImpulse( -300.0f, 300.0f ); + + vel[2] = 0.0f; + pObj->AddVelocity( &vel, &angImp ); + } + + // In the Citadel we need to dissolve this + if ( PlayerHasMegaPhysCannon() && GlobalEntity_GetCounter("super_phys_gun") != 1 ) + { + CBaseCombatWeapon *pWeapon = static_cast(pItem); + + pWeapon->Dissolve( NULL, gpGlobals->curtime, false, ENTITY_DISSOLVE_NORMAL ); + } + } + } + } + } +} + //----------------------------------------------------------------------------- // Purpose: Task helpers //----------------------------------------------------------------------------- diff --git a/sp/src/game/server/mapbase/ai_monitor.cpp b/sp/src/game/server/mapbase/ai_monitor.cpp index 754ff56d9b..fa11a20398 100644 --- a/sp/src/game/server/mapbase/ai_monitor.cpp +++ b/sp/src/game/server/mapbase/ai_monitor.cpp @@ -672,6 +672,33 @@ int CAI_Monitor::TranslateScheduleString(const char *schedName) return 0; } +template +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 +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 ); diff --git a/sp/src/game/server/mapbase/func_fake_worldportal.cpp b/sp/src/game/server/mapbase/func_fake_worldportal.cpp index 3184d2fa3c..6430d5df2f 100644 --- a/sp/src/game/server/mapbase/func_fake_worldportal.cpp +++ b/sp/src/game/server/mapbase/func_fake_worldportal.cpp @@ -1,6 +1,6 @@ //========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// // -// Purpose: Recreates Portal 2 linked_portal_door functionality using SDK code only. +// Purpose: Recreates Portal 2 linked_portal_door visual functionality using SDK code only. // (basically a combination of point_camera and func_reflective_glass) // //===========================================================================// @@ -54,6 +54,7 @@ class CFuncFakeWorldPortal : public CFuncBrush void InputSetSkyMode( inputdata_t &inputdata ) { m_iSkyMode = inputdata.value.Int(); } void InputSetRenderTarget( inputdata_t &inputdata ) { m_iszRenderTarget = inputdata.value.StringID(); } void InputSetFogController( inputdata_t &inputdata ) { m_hFogController = inputdata.value.Entity(); if (m_hFogController) { m_iszFogController = m_hFogController->GetEntityName(); } } + void InputSetScale( inputdata_t &inputdata ) { m_flScale = inputdata.value.Float(); } private: @@ -83,6 +84,7 @@ BEGIN_DATADESC( CFuncFakeWorldPortal ) DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSkyMode", InputSetSkyMode ), DEFINE_INPUTFUNC( FIELD_STRING, "SetRenderTarget", InputSetRenderTarget ), DEFINE_INPUTFUNC( FIELD_EHANDLE, "SetFogController", InputSetFogController ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetScale", InputSetScale ), END_DATADESC() diff --git a/sp/src/game/server/mapbase/logic_externaldata.cpp b/sp/src/game/server/mapbase/logic_externaldata.cpp index b28189f74a..588bb6c7c2 100644 --- a/sp/src/game/server/mapbase/logic_externaldata.cpp +++ b/sp/src/game/server/mapbase/logic_externaldata.cpp @@ -96,7 +96,7 @@ BEGIN_ENT_SCRIPTDESC( CLogicExternalData, CBaseEntity, "An entity which loads ke DEFINE_SCRIPTFUNC_NAMED( ScriptGetKeyValueBlock, "GetKeyValueBlock", "Gets the current external data block expressed in CScriptKeyValues." ) DEFINE_SCRIPTFUNC_NAMED( ScriptSetKeyValues, "SetKeyValues", "Sets the external data from a CScriptKeyValues object." ) - DEFINE_SCRIPTFUNC_NAMED( ScriptSetKeyValueBlock, "SetKeyValues", "Sets the current external data block from a CScriptKeyValues object." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetKeyValueBlock, "SetKeyValueBlock", "Sets the current external data block from a CScriptKeyValues object." ) DEFINE_SCRIPTFUNC( LoadFile, "Loads external data from the external file." ) DEFINE_SCRIPTFUNC( SaveFile, "Saves the external data to the external 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)); diff --git a/sp/src/game/server/mapbase/logic_substring.cpp b/sp/src/game/server/mapbase/logic_substring.cpp new file mode 100644 index 0000000000..7c7d153b90 --- /dev/null +++ b/sp/src/game/server/mapbase/logic_substring.cpp @@ -0,0 +1,110 @@ +//====================== By Holly Liberatore / MoofEMP ======================// +// +// Purpose: Takes a string parameter and returns a substring defined by keyvalues +// +//===========================================================================// + +#include "cbase.h" + +#define SF_SUBSTRING_START_DISABLED (1 << 0) + +class CLogicSubstring : public CLogicalEntity +{ +public: + DECLARE_CLASS( CLogicSubstring, CLogicalEntity ); + DECLARE_DATADESC(); + + CLogicSubstring( void ) { } + + void InputDisable( inputdata_t &inputData ); + void InputEnable( inputdata_t &inputData ); + void InputInValue( inputdata_t &inputData ); + void InputSetLength( inputdata_t &inputData ); + void InputSetStartPos( inputdata_t &inputData ); + + void Spawn(void); + +private: + int m_nLength; + int m_nStartPos; + + bool m_bEnabled; + + COutputString m_OutValue; +}; + +LINK_ENTITY_TO_CLASS( logic_substring, CLogicSubstring ); + +BEGIN_DATADESC( CLogicSubstring ) + + DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ), + + DEFINE_KEYFIELD(m_nLength, FIELD_INTEGER, "length" ), + DEFINE_KEYFIELD(m_nStartPos, FIELD_INTEGER, "startPos" ), + + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_STRING, "InValue", InputInValue ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetLength", InputSetLength ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetStartPos", InputSetStartPos ), + + DEFINE_OUTPUT( m_OutValue, "OutValue" ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: Disable or enable the entity (disabling prevents any input functions from running) +//----------------------------------------------------------------------------- +void CLogicSubstring::InputDisable( inputdata_t &inputData ) { m_bEnabled = false; } +void CLogicSubstring::InputEnable ( inputdata_t &inputData ) { m_bEnabled = true ; } + +//----------------------------------------------------------------------------- +// Purpose: Trim substring from input +// Output: Substring +//----------------------------------------------------------------------------- +void CLogicSubstring::InputInValue( inputdata_t &inputData ) +{ + if( !m_bEnabled ) return; + + int inputLength = Q_strlen(inputData.value.String()); + int startPosCheck = m_nStartPos < 0 ? inputLength + m_nStartPos : m_nStartPos; + if( startPosCheck < 0 ) + { + startPosCheck = 0; + } + int lengthCheck = (m_nLength < 0 || m_nLength > inputLength - startPosCheck ? inputLength - startPosCheck : m_nLength) + 1; + if( lengthCheck < 1 || startPosCheck > inputLength ) + { + m_OutValue.Set( MAKE_STRING(""), inputData.pActivator, this ); + return; + } + char* strOutValue = (char*)malloc( lengthCheck ); + Q_strncpy( strOutValue, inputData.value.String() + startPosCheck, lengthCheck ); + m_OutValue.Set( AllocPooledString(strOutValue), inputData.pActivator, this ); + free(strOutValue); +} + +//----------------------------------------------------------------------------- +// Purpose: Setter methods for keyvalues +//----------------------------------------------------------------------------- +void CLogicSubstring::InputSetLength( inputdata_t &inputData ) +{ + if( !m_bEnabled ) return; + + m_nLength = inputData.value.Int(); +} + +void CLogicSubstring::InputSetStartPos( inputdata_t &inputData ) +{ + if( !m_bEnabled ) return; + + m_nStartPos = inputData.value.Int(); +} + +//----------------------------------------------------------------------------- +// Purpose: Respond to spawnflags when entity spawns +//----------------------------------------------------------------------------- +void CLogicSubstring::Spawn( void ) +{ + m_bEnabled = !HasSpawnFlags( SF_SUBSTRING_START_DISABLED ); +} diff --git a/sp/src/game/server/movie_display.cpp b/sp/src/game/server/movie_display.cpp index 5c31fb233a..b5a4476c57 100644 --- a/sp/src/game/server/movie_display.cpp +++ b/sp/src/game/server/movie_display.cpp @@ -21,6 +21,8 @@ class CMovieDisplay : public CBaseEntity DECLARE_DATADESC(); DECLARE_SERVERCLASS(); + CMovieDisplay() { m_bMuted = true; } + virtual ~CMovieDisplay(); virtual bool KeyValue( const char *szKeyName, const char *szValue ); @@ -53,6 +55,7 @@ class CMovieDisplay : public CBaseEntity 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 diff --git a/sp/src/game/server/nav_merge.cpp b/sp/src/game/server/nav_merge.cpp index d62764ab59..1d5330ca5c 100644 --- a/sp/src/game/server/nav_merge.cpp +++ b/sp/src/game/server/nav_merge.cpp @@ -303,7 +303,7 @@ void CNavMesh::CommandNavMergeMesh( const CCommand &args ) //-------------------------------------------------------------------------------------------------------- int NavMeshMergeAutocomplete( char const *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] ) { - char *commandName = "nav_merge_mesh"; + const char *commandName = "nav_merge_mesh"; int numMatches = 0; partial += Q_strlen( commandName ) + 1; int partialLength = Q_strlen( partial ); diff --git a/sp/src/game/server/physics_main.cpp b/sp/src/game/server/physics_main.cpp index b0b2d867a1..eb6ceee6b5 100644 --- a/sp/src/game/server/physics_main.cpp +++ b/sp/src/game/server/physics_main.cpp @@ -7,12 +7,12 @@ #include "cbase.h" -#ifdef _WIN32 +#if POSIX || _MSC_VER >= 1900 +#include +#elif defined(_WIN32) #include "typeinfo.h" // BUGBUG: typeinfo stomps some of the warning settings (in yvals.h) #pragma warning(disable:4244) -#elif POSIX -#include #else #error "need typeinfo defined" #endif @@ -942,8 +942,8 @@ void CBaseEntity::PhysicsDispatchThink( BASEPTR thinkFunc ) if ( thinkLimit ) { // calculate running time of the AI in milliseconds - float time = ( engine->Time() - startTime ) * 1000.0f; - if ( time > thinkLimit ) + float flTime = ( engine->Time() - startTime ) * 1000.0f; + if ( flTime > thinkLimit ) { #if defined( _XBOX ) && !defined( _RETAIL ) if ( vprof_think_limit.GetBool() ) @@ -956,14 +956,14 @@ void CBaseEntity::PhysicsDispatchThink( BASEPTR thinkFunc ) CAI_BaseNPC *pNPC = MyNPCPointer(); if (pNPC && pNPC->GetCurSchedule()) { - pNPC->ReportOverThinkLimit( time ); + pNPC->ReportOverThinkLimit( flTime ); } else { #ifdef _WIN32 - Msg( "%s(%s) thinking for %.02f ms!!!\n", GetClassname(), typeid(this).raw_name(), time ); + Msg( "%s(%s) thinking for %.02f ms!!!\n", GetClassname(), typeid(this).raw_name(), flTime ); #elif POSIX - Msg( "%s(%s) thinking for %.02f ms!!!\n", GetClassname(), typeid(this).name(), time ); + Msg( "%s(%s) thinking for %.02f ms!!!\n", GetClassname(), typeid(this).name(), flTime ); #else #error "typeinfo" #endif diff --git a/sp/src/game/server/physics_prop_ragdoll.cpp b/sp/src/game/server/physics_prop_ragdoll.cpp index f417175fcd..93efddc79a 100644 --- a/sp/src/game/server/physics_prop_ragdoll.cpp +++ b/sp/src/game/server/physics_prop_ragdoll.cpp @@ -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 }; diff --git a/sp/src/game/server/player.cpp b/sp/src/game/server/player.cpp index 0f26c2522e..00aefbb813 100644 --- a/sp/src/game/server/player.cpp +++ b/sp/src/game/server/player.cpp @@ -59,6 +59,10 @@ #include "env_zoom.h" #include "rumble_shared.h" #include "gamestats.h" +#ifdef MAPBASE // From Alien Swarm SDK +#include "env_tonemap_controller.h" +#include "fogvolume.h" +#endif #include "npcevent.h" #include "datacache/imdlcache.h" #include "hintsystem.h" @@ -459,8 +463,13 @@ BEGIN_DATADESC( CBasePlayer ) // Inputs DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ), DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetHUDVisibility", InputSetHUDVisibility ), - DEFINE_INPUTFUNC( FIELD_STRING, "SetFogController", InputSetFogController ), +#ifdef MAPBASE // From Alien Swarm SDK (kind of) + DEFINE_INPUTFUNC( FIELD_INPUT, "SetFogController", InputSetFogController ), DEFINE_INPUTFUNC( FIELD_INPUT, "SetPostProcessController", InputSetPostProcessController ), + DEFINE_INPUTFUNC( FIELD_INPUT, "SetColorCorrectionController", InputSetColorCorrectionController ), +#else + DEFINE_INPUTFUNC( FIELD_STRING, "SetFogController", InputSetFogController ), +#endif DEFINE_INPUTFUNC( FIELD_STRING, "HandleMapEvent", InputHandleMapEvent ), #ifdef MAPBASE DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetSuppressAttacks", InputSetSuppressAttacks ), @@ -474,9 +483,10 @@ BEGIN_DATADESC( CBasePlayer ) DEFINE_FIELD( m_nNumCrateHudHints, FIELD_INTEGER ), +#ifdef MAPBASE // From Alien Swarm SDK DEFINE_FIELD( m_hPostProcessCtrl, FIELD_EHANDLE ), - - + DEFINE_FIELD( m_hColorCorrectionCtrl, FIELD_EHANDLE ), +#endif // DEFINE_FIELD( m_nBodyPitchPoseParam, FIELD_INTEGER ), // DEFINE_ARRAY( m_StepSoundCache, StepSoundCache_t, 2 ), @@ -541,6 +551,8 @@ BEGIN_ENT_SCRIPTDESC( CBasePlayer, CBaseCombatCharacter, "The player entity." ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetEyeRight, "GetEyeRight", "Gets the player's right eye vector." ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetEyeUp, "GetEyeUp", "Gets the player's up eye vector." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetViewModel, "GetViewModel", "Returns the viewmodel of the specified index." ) + // // Hooks // @@ -622,6 +634,10 @@ void CBasePlayer::DestroyViewModels( void ) } #ifdef MAPBASE +extern char g_szDefaultHandsModel[MAX_PATH]; +extern int g_iDefaultHandsSkin; +extern int g_iDefaultHandsBody; + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -638,6 +654,11 @@ void CBasePlayer::CreateHandModel(int index, int iOtherVm) vm->SetAbsOrigin(GetAbsOrigin()); vm->SetOwner(this); vm->SetIndex(index); + + vm->SetModel( g_szDefaultHandsModel ); + vm->m_nSkin = g_iDefaultHandsSkin; + vm->m_nBody = g_iDefaultHandsBody; + DispatchSpawn(vm); vm->FollowEntity(GetViewModel(iOtherVm), true); m_hViewModel.Set(index, vm); @@ -3831,7 +3852,7 @@ void CBasePlayer::PlayerRunCommand(CUserCmd *ucmd, IMoveHelper *moveHelper) // Movement hook for VScript if (m_ScriptScope.IsInitialized() && g_Hook_PlayerRunCommand.CanRunInScope(m_ScriptScope)) { - HSCRIPT hCmd = g_pScriptVM->RegisterInstance( ucmd ); + HSCRIPT hCmd = g_pScriptVM->RegisterInstance( reinterpret_cast(ucmd) ); // command ScriptVariant_t args[] = { hCmd }; @@ -4668,6 +4689,55 @@ void CBasePlayer::ForceOrigin( const Vector &vecOrigin ) m_vForcedOrigin = vecOrigin; } +#ifdef MAPBASE // From Alien Swarm SDK +//-------------------------------------------------------------------------------------------------------- +void CBasePlayer::OnTonemapTriggerStartTouch( CTonemapTrigger *pTonemapTrigger ) +{ + m_hTriggerTonemapList.FindAndRemove( pTonemapTrigger ); + m_hTriggerTonemapList.AddToTail( pTonemapTrigger ); +} + + +//-------------------------------------------------------------------------------------------------------- +void CBasePlayer::OnTonemapTriggerEndTouch( CTonemapTrigger *pTonemapTrigger ) +{ + m_hTriggerTonemapList.FindAndRemove( pTonemapTrigger ); +} + + +//-------------------------------------------------------------------------------------------------------- +void CBasePlayer::UpdateTonemapController( void ) +{ + // For now, Mapbase uses Tony Sergi's Source 2007 tonemap fixes. + // Alien Swarm SDK tonemap controller code copies the parameters instead. + + CEnvTonemapController *pController = NULL; + + if (m_hTriggerTonemapList.Count() > 0) + { + pController = static_cast(m_hTriggerTonemapList.Tail()->GetTonemapController()); + } + else if (TheTonemapSystem()->GetMasterTonemapController()) + { + pController = static_cast(TheTonemapSystem()->GetMasterTonemapController()); + } + + if (pController) + { + //m_hTonemapController = TheTonemapSystem()->GetMasterTonemapController(); + + if (pController->m_bUseCustomAutoExposureMax) + m_Local.m_TonemapParams.m_flAutoExposureMax = pController->m_flCustomAutoExposureMax; + + if (pController->m_bUseCustomAutoExposureMin) + m_Local.m_TonemapParams.m_flAutoExposureMin = pController->m_flCustomAutoExposureMin; + + if (pController->m_bUseCustomBloomScale) + m_Local.m_TonemapParams.m_flBloomScale = pController->m_flCustomBloomScale; + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -4675,6 +4745,11 @@ void CBasePlayer::PostThink() { m_vecSmoothedVelocity = m_vecSmoothedVelocity * SMOOTHING_FACTOR + GetAbsVelocity() * ( 1 - SMOOTHING_FACTOR ); +#ifdef MAPBASE // From Alien Swarm SDK + UpdateTonemapController(); + UpdateFXVolume(); +#endif + if ( !g_fGameOver && !m_iPlayerLocked ) { if ( IsAlive() ) @@ -5151,7 +5226,9 @@ void CBasePlayer::Spawn( void ) // Initialize the fog and postprocess controllers. InitFogController(); +#ifdef MAPBASE // From Alien Swarm SDK InitPostProcessController(); +#endif m_DmgTake = 0; m_DmgSave = 0; @@ -5352,6 +5429,10 @@ void CBasePlayer::Precache( void ) m_iTrain = TRAIN_NEW; #endif +#ifdef MAPBASE + PrecacheModel( g_szDefaultHandsModel ); +#endif + m_iClientBattery = -1; m_iUpdateTime = 5; // won't update for 1/2 a second @@ -7069,6 +7150,19 @@ HSCRIPT CBasePlayer::VScriptGetExpresser() return hScript; } + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT CBasePlayer::ScriptGetViewModel( int viewmodelindex ) +{ + if (viewmodelindex < 0 || viewmodelindex >= MAX_VIEWMODELS) + { + Warning( "GetViewModel: Invalid index '%i'\n", viewmodelindex ); + return NULL; + } + + return ToHScript( GetViewModel( viewmodelindex ) ); +} #endif //----------------------------------------------------------------------------- @@ -7698,6 +7792,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 +7804,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. + // 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()) + { + // Unused Weighting + case 2: + bShouldSwitch = g_pGameRules->FShouldSwitchWeapon( this, pWeapon ); + break; - if ( !PlayerHasMegaPhysCannon() ) - { - Weapon_Switch( pWeapon ); + // 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 +7837,6 @@ void CBasePlayer::Weapon_Equip( CBaseCombatWeapon *pWeapon ) { Weapon_Switch( pWeapon ); } -#endif } #ifdef MAPBASE @@ -7747,39 +7845,24 @@ void CBasePlayer::Weapon_Equip( CBaseCombatWeapon *pWeapon ) //----------------------------------------------------------------------------- Activity CBasePlayer::Weapon_TranslateActivity( Activity baseAct, bool *pRequired ) { -#ifdef HL2_DLL - // HAAAAAAAAAAAAAACKS! - if (GetActiveWeapon()) + Activity weaponTranslation = BaseClass::Weapon_TranslateActivity( baseAct, pRequired ); + + if ( GetActiveWeapon() && GetActiveWeapon()->IsEffectActive(EF_NODRAW) && baseAct != ACT_ARM ) { - int translated = baseAct; - int iActOffset = (baseAct - ACT_HL2MP_IDLE); - - string_t iszClassname = GetActiveWeapon()->m_iClassname; - if (iszClassname == gm_isz_class_Pistol || iszClassname == gm_isz_class_357) - translated = (ACT_HL2MP_IDLE_PISTOL + iActOffset); - else if (iszClassname == gm_isz_class_SMG1) - translated = (ACT_HL2MP_IDLE_SMG1 + iActOffset); - else if (iszClassname == gm_isz_class_AR2) - translated = (ACT_HL2MP_IDLE_AR2 + iActOffset); - else if (iszClassname == gm_isz_class_Shotgun) - translated = (ACT_HL2MP_IDLE_SHOTGUN + iActOffset); - else if (iszClassname == gm_isz_class_RPG) - translated = (ACT_HL2MP_IDLE_RPG + iActOffset); - else if (iszClassname == gm_isz_class_Grenade) - translated = (ACT_HL2MP_IDLE_GRENADE + iActOffset); - else if (iszClassname == gm_isz_class_Physcannon) - translated = (ACT_HL2MP_IDLE_PHYSGUN + iActOffset); - else if (iszClassname == gm_isz_class_Crossbow) - translated = (ACT_HL2MP_IDLE_CROSSBOW + iActOffset); - else if (iszClassname == gm_isz_class_Crowbar || iszClassname == gm_isz_class_Stunstick) - translated = (ACT_HL2MP_IDLE_MELEE + iActOffset); - - if (translated != baseAct) - return (Activity)translated; + // Our weapon is holstered. Use the base activity. + return baseAct; + } + if ( GetModelPtr() && (!GetModelPtr()->HaveSequenceForActivity(weaponTranslation) || baseAct == weaponTranslation) ) + { + // This is used so players can fall back to backup activities in the same way NPCs in Mapbase can + Activity backupActivity = Weapon_BackupActivity(baseAct, pRequired); + if ( baseAct != backupActivity && GetModelPtr()->HaveSequenceForActivity(backupActivity) ) + return backupActivity; + + return baseAct; } -#endif - return BaseClass::Weapon_TranslateActivity( baseAct, pRequired ); + return weaponTranslation; } #endif @@ -8672,6 +8755,7 @@ void SendProxy_ShiftPlayerSpawnflags( const SendProp *pProp, const void *pStruct SendPropInt ( SENDINFO( m_spawnflags ), 3, SPROP_UNSIGNED, SendProxy_ShiftPlayerSpawnflags ), SendPropBool ( SENDINFO( m_bDrawPlayerModelExternally ) ), + SendPropBool ( SENDINFO( m_bInTriggerFall ) ), #endif END_SEND_TABLE() @@ -8711,8 +8795,11 @@ void SendProxy_ShiftPlayerSpawnflags( const SendProp *pProp, const void *pStruct SendPropArray ( SendPropEHandle( SENDINFO_ARRAY( m_hViewModel ) ), m_hViewModel ), SendPropString (SENDINFO(m_szLastPlaceName) ), +#ifdef MAPBASE // From Alien Swarm SDK // Postprocess data - SendPropEHandle( SENDINFO( m_hPostProcessCtrl ) ), + SendPropEHandle ( SENDINFO(m_hPostProcessCtrl) ), + SendPropEHandle ( SENDINFO(m_hColorCorrectionCtrl) ), +#endif #if defined USES_ECON_ITEMS SendPropUtlVector( SENDINFO_UTLVECTOR( m_hMyWearables ), MAX_WEARABLES_SENT_FROM_SERVER, SendPropEHandle( NULL, 0 ) ), @@ -9428,7 +9515,19 @@ void CBasePlayer::InputSetSuppressAttacks( inputdata_t &inputdata ) void CBasePlayer::InputSetFogController( inputdata_t &inputdata ) { // Find the fog controller with the given name. +#ifdef MAPBASE // From Alien Swarm SDK + CFogController *pFogController = NULL; + if ( inputdata.value.FieldType() == FIELD_EHANDLE ) + { + pFogController = dynamic_cast( inputdata.value.Entity().Get() ); + } + else + { + pFogController = dynamic_cast( gEntList.FindEntityByName( NULL, inputdata.value.String() ) ); + } +#else CFogController *pFogController = dynamic_cast( gEntList.FindEntityByName( NULL, inputdata.value.String() ) ); +#endif if ( pFogController ) { m_Local.m_PlayerFog.m_hCtrl.Set( pFogController ); @@ -9444,6 +9543,7 @@ void CBasePlayer::InitFogController( void ) m_Local.m_PlayerFog.m_hCtrl = FogSystem()->GetMasterFogController(); } +#ifdef MAPBASE // From Alien Swarm SDK //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- @@ -9456,25 +9556,57 @@ void CBasePlayer::InitPostProcessController( void ) //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- -void CBasePlayer::InputSetPostProcessController( inputdata_t& inputdata ) +void CBasePlayer::InitColorCorrectionController( void ) { - // Find the postprocess controller with the given name. - CPostProcessController* pController = NULL; - if (inputdata.value.FieldType() == FIELD_EHANDLE) + m_hColorCorrectionCtrl = ColorCorrectionSystem()->GetMasterColorCorrection(); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CBasePlayer::InputSetPostProcessController( inputdata_t &inputdata ) +{ + // Find the fog controller with the given name. + CPostProcessController *pController = NULL; + if ( inputdata.value.FieldType() == FIELD_EHANDLE ) { - pController = dynamic_cast(inputdata.value.Entity().Get()); + pController = dynamic_cast( inputdata.value.Entity().Get() ); } else { - pController = dynamic_cast(gEntList.FindEntityByName( NULL, inputdata.value.String() )); + pController = dynamic_cast( gEntList.FindEntityByName( NULL, inputdata.value.String() ) ); } - if (pController) + if ( pController ) { m_hPostProcessCtrl.Set( pController ); } } +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CBasePlayer::InputSetColorCorrectionController( inputdata_t &inputdata ) +{ + // Find the fog controller with the given name. + CColorCorrection *pController = NULL; + if ( inputdata.value.FieldType() == FIELD_EHANDLE ) + { + pController = dynamic_cast( inputdata.value.Entity().Get() ); + } + else + { + pController = dynamic_cast( gEntList.FindEntityByName( NULL, inputdata.value.String() ) ); + } + + if ( pController ) + { + m_hColorCorrectionCtrl.Set( pController ); + } + +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : *pEntity - @@ -10145,4 +10277,71 @@ uint64 CBasePlayer::GetSteamIDAsUInt64( void ) return steamIDForPlayer.ConvertToUint64(); return 0; } -#endif // NO_STEAM \ No newline at end of file +#endif // NO_STEAM + +#ifdef MAPBASE // From Alien Swarm SDK +//-------------------------------------------------------------------------------------------------------- +void CBasePlayer::UpdateFXVolume( void ) +{ + CFogController *pFogController = NULL; + CPostProcessController *pPostProcessController = NULL; + CColorCorrection* pColorCorrectionEnt = NULL; + + Vector eyePos; + CBaseEntity *pViewEntity = GetViewEntity(); + if ( pViewEntity ) + { + eyePos = pViewEntity->GetAbsOrigin(); + } + else + { + eyePos = EyePosition(); + } + + CFogVolume *pFogVolume = CFogVolume::FindFogVolumeForPosition( eyePos ); + if ( pFogVolume ) + { + pFogController = pFogVolume->GetFogController(); + pPostProcessController = pFogVolume->GetPostProcessController(); + pColorCorrectionEnt = pFogVolume->GetColorCorrectionController(); + + if ( !pFogController ) + { + pFogController = FogSystem()->GetMasterFogController(); + } + + if ( !pPostProcessController ) + { + pPostProcessController = PostProcessSystem()->GetMasterPostProcessController(); + } + + if ( !pColorCorrectionEnt ) + { + pColorCorrectionEnt = ColorCorrectionSystem()->GetMasterColorCorrection(); + } + } + else if ( TheFogVolumes.Count() > 0 ) + { + // If we're not in a fog volume, clear our fog volume, if the map has any. + // This will get us back to using the master fog controller. + pFogController = FogSystem()->GetMasterFogController(); + pPostProcessController = PostProcessSystem()->GetMasterPostProcessController(); + pColorCorrectionEnt = ColorCorrectionSystem()->GetMasterColorCorrection(); + } + + if ( pFogController && m_Local.m_PlayerFog.m_hCtrl.Get() != pFogController ) + { + m_Local.m_PlayerFog.m_hCtrl.Set( pFogController ); + } + + if ( pPostProcessController ) + { + m_hPostProcessCtrl.Set( pPostProcessController ); + } + + if ( pColorCorrectionEnt ) + { + m_hColorCorrectionCtrl.Set( pColorCorrectionEnt ); + } +} +#endif diff --git a/sp/src/game/server/player.h b/sp/src/game/server/player.h index 03709ade75..0bfba8fbfa 100644 --- a/sp/src/game/server/player.h +++ b/sp/src/game/server/player.h @@ -88,6 +88,10 @@ class CNavArea; class CHintSystem; class CAI_Expresser; +#ifdef MAPBASE // From Alien Swarm SDK +class CTonemapTrigger; +#endif + #if defined USES_ECON_ITEMS class CEconWearable; #endif // USES_ECON_ITEMS @@ -408,6 +412,8 @@ class CBasePlayer : public CBaseCombatCharacter const Vector& ScriptGetEyeForward() { static Vector vecForward; EyeVectors( &vecForward, NULL, NULL ); return vecForward; } const Vector& ScriptGetEyeRight() { static Vector vecRight; EyeVectors( NULL, &vecRight, NULL ); return vecRight; } const Vector& ScriptGetEyeUp() { static Vector vecUp; EyeVectors( NULL, NULL, &vecUp ); return vecUp; } + + HSCRIPT ScriptGetViewModel( int viewmodelindex ); #endif // View model prediction setup @@ -658,6 +664,8 @@ class CBasePlayer : public CBaseCombatCharacter #ifdef MAPBASE bool ShouldUseVisibilityCache( CBaseEntity *pEntity ); + + void UpdateFXVolume( void ); // From Alien Swarm SDK #endif public: @@ -884,9 +892,18 @@ class CBasePlayer : public CBaseCombatCharacter void InitFogController( void ); void InputSetFogController( inputdata_t &inputdata ); +#ifdef MAPBASE // From Alien Swarm SDK + void OnTonemapTriggerStartTouch( CTonemapTrigger *pTonemapTrigger ); + void OnTonemapTriggerEndTouch( CTonemapTrigger *pTonemapTrigger ); + CUtlVector< CHandle< CTonemapTrigger > > m_hTriggerTonemapList; + CNetworkHandle( CPostProcessController, m_hPostProcessCtrl ); // active postprocessing controller + CNetworkHandle( CColorCorrection, m_hColorCorrectionCtrl ); // active FXVolume color correction void InitPostProcessController( void ); - void InputSetPostProcessController( inputdata_t& inputdata ); + void InputSetPostProcessController( inputdata_t &inputdata ); + void InitColorCorrectionController( void ); + void InputSetColorCorrectionController( inputdata_t &inputdata ); +#endif // Used by env_soundscape_triggerable to manage when the player is touching multiple // soundscape triggers simultaneously. @@ -943,7 +960,7 @@ class CBasePlayer : public CBaseCombatCharacter #endif #ifdef MAPBASE - bool m_bInTriggerFall; + CNetworkVar( bool, m_bInTriggerFall ); #endif private: @@ -1031,6 +1048,13 @@ class CBasePlayer : public CBaseCombatCharacter float m_fReplayEnd; // time to stop replay mode int m_iReplayEntity; // follow this entity in replay +#ifdef MAPBASE // From Alien Swarm SDK + // For now, Mapbase uses Tony Sergi's Source 2007 tonemap fixes. + // Alien Swarm SDK tonemap controller code copies the parameters instead. + virtual void UpdateTonemapController( void ); + //CNetworkHandle( CBaseEntity, m_hTonemapController ); +#endif + private: void HandleFuncTrain(); @@ -1147,7 +1171,8 @@ class CBasePlayer : public CBaseCombatCharacter int m_nNumCrateHudHints; #ifdef MAPBASE - CNetworkVar( bool, m_bDrawPlayerModelExternally ); + bool GetDrawPlayerModelExternally( void ) { return m_bDrawPlayerModelExternally; } + void SetDrawPlayerModelExternally( bool bToggle ) { m_bDrawPlayerModelExternally.Set( bToggle ); } #endif private: @@ -1188,6 +1213,10 @@ class CBasePlayer : public CBaseCombatCharacter // Player name char m_szNetname[MAX_PLAYER_NAME_LENGTH]; +#ifdef MAPBASE + CNetworkVar( bool, m_bDrawPlayerModelExternally ); +#endif + protected: // HACK FOR TF2 Prediction friend class CTFGameMovementRecon; diff --git a/sp/src/game/server/playerlocaldata.h b/sp/src/game/server/playerlocaldata.h index 587dbd1a4c..f9ceef70e6 100644 --- a/sp/src/game/server/playerlocaldata.h +++ b/sp/src/game/server/playerlocaldata.h @@ -15,7 +15,10 @@ #include "playernet_vars.h" #include "networkvar.h" #include "fogcontroller.h" +#ifdef MAPBASE // From Alien Swarm SDK #include "postprocesscontroller.h" +#include "colorcorrection.h" +#endif //----------------------------------------------------------------------------- // Purpose: Player specific data ( sent only to local player, too ) diff --git a/sp/src/game/server/props.cpp b/sp/src/game/server/props.cpp index 9263628b44..56428e73ca 100644 --- a/sp/src/game/server/props.cpp +++ b/sp/src/game/server/props.cpp @@ -44,6 +44,7 @@ #ifdef MAPBASE #include "mapbase/GlobalStrings.h" #include "collisionutils.h" +#include "vstdlib/IKeyValuesSystem.h" // From Alien Swarm SDK #endif // memdbgon must be the last include file in a .cpp file!!! @@ -387,8 +388,15 @@ int CBaseProp::ParsePropData( void ) return PARSE_FAILED_NO_DATA; } +#ifdef MAPBASE // From Alien Swarm SDK + static int keyPropData = KeyValuesSystem()->GetSymbolForString( "prop_data" ); + + // Do we have a props section? + KeyValues *pkvPropData = modelKeyValues->FindKey( keyPropData ); +#else // Do we have a props section? KeyValues *pkvPropData = modelKeyValues->FindKey("prop_data"); +#endif if ( !pkvPropData ) { modelKeyValues->deleteThis(); @@ -1200,6 +1208,17 @@ int CBreakableProp::OnTakeDamage( const CTakeDamageInfo &inputInfo ) { m_hLastAttacker.Set( info.GetAttacker() ); } +#ifdef MAPBASE // From Alien Swarm SDK + else if ( info.GetAttacker() ) + { + CBaseEntity *attacker = info.GetAttacker(); + CBaseEntity *attackerOwner = attacker->GetOwnerEntity(); + if ( attackerOwner && attackerOwner->MyCombatCharacterPointer() ) + { + m_hLastAttacker.Set( attackerOwner ); + } + } +#endif float flPropDamage = GetBreakableDamage( info, assert_cast(this) ); info.SetDamage( flPropDamage ); @@ -1999,9 +2018,16 @@ BEGIN_DATADESC( CDynamicProp ) DEFINE_KEYFIELD( m_bDisableBoneFollowers, FIELD_BOOLEAN, "DisableBoneFollowers" ), DEFINE_FIELD( m_bUseHitboxesForRenderBox, FIELD_BOOLEAN ), DEFINE_FIELD( m_nPendingSequence, FIELD_SHORT ), +#ifdef MAPBASE // From Alien Swarm SDK + DEFINE_KEYFIELD( m_bUpdateAttachedChildren, FIELD_BOOLEAN, "updatechildren" ), + DEFINE_KEYFIELD( m_bHoldAnimation, FIELD_BOOLEAN, "HoldAnimation" ), +#endif // Inputs DEFINE_INPUTFUNC( FIELD_STRING, "SetAnimation", InputSetAnimation ), +#ifdef MAPBASE // From Alien Swarm SDK + DEFINE_INPUTFUNC( FIELD_STRING, "SetAnimationNoReset", InputSetAnimationNoReset ), +#endif DEFINE_INPUTFUNC( FIELD_STRING, "SetDefaultAnimation", InputSetDefaultAnimation ), DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ), DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ), @@ -2226,9 +2252,8 @@ void CDynamicProp::CreateBoneFollowers() pBone = pBone->GetNextKey(); } } - - modelKeyValues->deleteThis(); } + modelKeyValues->deleteThis(); // if we got here, we don't have a bone follower section, but if we have a ragdoll // go ahead and create default bone followers for it @@ -2258,7 +2283,11 @@ bool CDynamicProp::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& } } } +#ifdef MAPBASE // From Alien Swarm SDK + return BaseClass::TestCollision( ray, mask, trace ); +#else return false; +#endif } @@ -2366,10 +2395,23 @@ void CDynamicProp::AnimThink( void ) } else { +#ifdef MAPBASE // From Alien Swarm SDK + if ( m_iszDefaultAnim != NULL_STRING && m_bHoldAnimation == false ) + { + PropSetAnim( STRING( m_iszDefaultAnim ) ); + } + + // We need to wait for an animation change to come in + if ( m_bHoldAnimation ) + { + SetNextThink( gpGlobals->curtime + 0.1f ); + } +#else if (m_iszDefaultAnim != NULL_STRING) { PropSetAnim( STRING( m_iszDefaultAnim ) ); } +#endif } } } @@ -2381,6 +2423,17 @@ void CDynamicProp::AnimThink( void ) StudioFrameAdvance(); DispatchAnimEvents(this); m_BoneFollowerManager.UpdateBoneFollowers(this); + +#ifdef MAPBASE // From Alien Swarm SDK + // Update any SetParentAttached children + if ( m_bUpdateAttachedChildren ) + { + for ( CBaseEntity *pChild = FirstMoveChild(); pChild; pChild = pChild->NextMovePeer() ) + { + pChild->PhysicsTouchTriggers(); + } + } +#endif } @@ -2419,6 +2472,19 @@ void CDynamicProp::InputSetAnimation( inputdata_t &inputdata ) PropSetAnim( inputdata.value.String() ); } +#ifdef MAPBASE // From Alien Swarm SDK +//------------------------------------------------------------------------------ +// Purpose: Set the animation unless the prop is already set to this particular animation +//------------------------------------------------------------------------------ +void CDynamicProp::InputSetAnimationNoReset( inputdata_t &inputdata ) +{ + if ( GetSequence() != LookupSequence( inputdata.value.String() ) ) + { + PropSetAnim( inputdata.value.String() ); + } +} +#endif + //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ @@ -4099,6 +4165,11 @@ BEGIN_DATADESC(CBasePropDoor) DEFINE_KEYFIELD(m_SoundClose, FIELD_SOUNDNAME, "soundcloseoverride"), DEFINE_KEYFIELD(m_ls.sLockedSound, FIELD_SOUNDNAME, "soundlockedoverride"), DEFINE_KEYFIELD(m_ls.sUnlockedSound, FIELD_SOUNDNAME, "soundunlockedoverride"), +#ifdef MAPBASE + DEFINE_KEYFIELD(m_flNPCOpenDistance, FIELD_FLOAT, "opendistoverride"), + DEFINE_KEYFIELD(m_eNPCOpenFrontActivity, FIELD_INTEGER, "openfrontactivityoverride"), + DEFINE_KEYFIELD(m_eNPCOpenBackActivity, FIELD_INTEGER, "openbackactivityoverride"), +#endif DEFINE_KEYFIELD(m_SlaveName, FIELD_STRING, "slavename" ), DEFINE_FIELD(m_bLocked, FIELD_BOOLEAN), //DEFINE_KEYFIELD(m_flBlockDamage, FIELD_FLOAT, "dmg"), @@ -4143,6 +4214,11 @@ END_SEND_TABLE() CBasePropDoor::CBasePropDoor( void ) { m_hMaster = NULL; +#ifdef MAPBASE + m_flNPCOpenDistance = -1; + m_eNPCOpenFrontActivity = ACT_INVALID; + m_eNPCOpenBackActivity = ACT_INVALID; +#endif } //----------------------------------------------------------------------------- @@ -4340,6 +4416,32 @@ void CBasePropDoor::CalcDoorSounds() { strSoundLocked = AllocPooledString( pkvHardwareData->GetString( "locked" ) ); strSoundUnlocked = AllocPooledString( pkvHardwareData->GetString( "unlocked" ) ); + +#ifdef MAPBASE + if (m_eNPCOpenFrontActivity == ACT_INVALID) + { + const char *pszActivity = pkvHardwareData->GetString( "activity_front" ); + if (pszActivity[0] != '\0') + { + m_eNPCOpenFrontActivity = (Activity)CAI_BaseNPC::GetActivityID( pszActivity ); + if (m_eNPCOpenFrontActivity == ACT_INVALID) + m_eNPCOpenFrontActivity = ActivityList_RegisterPrivateActivity( pszActivity ); + } + } + if (m_eNPCOpenBackActivity == ACT_INVALID) + { + const char *pszActivity = pkvHardwareData->GetString( "activity_back" ); + if (pszActivity[0] != '\0') + { + m_eNPCOpenBackActivity = (Activity)CAI_BaseNPC::GetActivityID( pszActivity ); + if (m_eNPCOpenBackActivity == ACT_INVALID) + m_eNPCOpenBackActivity = ActivityList_RegisterPrivateActivity( pszActivity ); + } + } + + if (m_flNPCOpenDistance == -1) + m_flNPCOpenDistance = pkvHardwareData->GetFloat( "npc_distance", 32.0 ); +#endif } // If any sounds were missing, try the "defaults" block. @@ -5940,6 +6042,11 @@ void CPropDoorRotating::DoorResume( void ) AngularMove( m_angGoal, m_flSpeed ); } +#ifdef MAPBASE +ConVar ai_door_enable_acts( "ai_door_enable_acts", "1", FCVAR_NONE, "Enables the new door-opening activities." ); +ConVar ai_door_open_dist_override( "ai_door_open_dist_override", "-1", FCVAR_NONE, "Overrides the distance from a door a NPC has to navigate to in order to open a door." ); +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : vecMoveDir - @@ -5961,20 +6068,37 @@ void CPropDoorRotating::GetNPCOpenData(CAI_BaseNPC *pNPC, opendata_t &opendata) Vector vecNPCOrigin = pNPC->GetAbsOrigin(); +#ifdef MAPBASE + float flPosOffset = ai_door_open_dist_override.GetFloat() >= 0.0f ? ai_door_open_dist_override.GetFloat() : GetNPCOpenDistance(); +#else + float flPosOffset = 64; +#endif + if (pNPC->GetAbsOrigin().Dot(vecForward) > GetAbsOrigin().Dot(vecForward)) { // In front of the door relative to the door's forward vector. - opendata.vecStandPos += vecForward * 64; + opendata.vecStandPos += vecForward * flPosOffset; opendata.vecFaceDir = -vecForward; +#ifdef MAPBASE + opendata.eActivity = !ai_door_enable_acts.GetBool() ? ACT_INVALID : GetNPCOpenFrontActivity(); +#endif } else { // Behind the door relative to the door's forward vector. - opendata.vecStandPos -= vecForward * 64; + opendata.vecStandPos -= vecForward * flPosOffset; opendata.vecFaceDir = vecForward; +#ifdef MAPBASE + opendata.eActivity = !ai_door_enable_acts.GetBool() ? ACT_INVALID : GetNPCOpenBackActivity(); +#endif } +#ifdef MAPBASE + if (opendata.eActivity == ACT_INVALID) + opendata.eActivity = ACT_OPEN_DOOR; +#else opendata.eActivity = ACT_OPEN_DOOR; +#endif } @@ -6006,6 +6130,14 @@ int CPropDoorRotating::DrawDebugTextOverlays(void) EntityText( text_offset, tempstr, 0); text_offset++; +#ifdef MAPBASE // From Alien Swarm SDK + if ( IsDoorLocked() ) + { + EntityText( text_offset, "LOCKED", 0); + text_offset++; + } +#endif + if ( IsDoorOpen() ) { Q_strncpy(tempstr, "DOOR STATE: OPEN", sizeof(tempstr)); diff --git a/sp/src/game/server/props.h b/sp/src/game/server/props.h index 121dd5c315..ebd87c4da1 100644 --- a/sp/src/game/server/props.h +++ b/sp/src/game/server/props.h @@ -329,6 +329,9 @@ class CDynamicProp : public CBreakableProp, public IPositionWatcher // Input handlers void InputSetAnimation( inputdata_t &inputdata ); +#ifdef MAPBASE // From Alien Swarm SDK + void InputSetAnimationNoReset( inputdata_t &inputdata ); +#endif void InputSetDefaultAnimation( inputdata_t &inputdata ); void InputTurnOn( inputdata_t &inputdata ); void InputTurnOff( inputdata_t &inputdata ); @@ -345,6 +348,9 @@ class CDynamicProp : public CBreakableProp, public IPositionWatcher int m_iTransitionDirection; // Random animations +#ifdef MAPBASE // From Alien Swarm SDK + bool m_bHoldAnimation; +#endif bool m_bRandomAnimator; float m_flNextRandAnim; float m_flMinRandAnimTime; @@ -353,6 +359,9 @@ class CDynamicProp : public CBreakableProp, public IPositionWatcher bool m_bStartDisabled; bool m_bDisableBoneFollowers; +#ifdef MAPBASE // From Alien Swarm SDK + bool m_bUpdateAttachedChildren; // For props with children on attachment points, update their child touches as we animate +#endif CNetworkVar( bool, m_bUseHitboxesForRenderBox ); diff --git a/sp/src/game/server/rope.cpp b/sp/src/game/server/rope.cpp index 516015fee1..23f44c3976 100644 --- a/sp/src/game/server/rope.cpp +++ b/sp/src/game/server/rope.cpp @@ -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. diff --git a/sp/src/game/server/rope.h b/sp/src/game/server/rope.h index 740378001c..44fc7102d5 100644 --- a/sp/src/game/server/rope.h +++ b/sp/src/game/server/rope.h @@ -102,6 +102,11 @@ class CRopeKeyframe : public CBaseEntity, public IPositionWatcher void InputSetScrollSpeed( inputdata_t &inputdata ); void InputSetForce( inputdata_t &inputdata ); void InputBreak( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetSlack( inputdata_t &inputdata ); + void InputSetWidth( inputdata_t &inputdata ); + void InputSetSubdivision( inputdata_t &inputdata ); +#endif public: diff --git a/sp/src/game/server/sceneentity.cpp b/sp/src/game/server/sceneentity.cpp index 0437c28086..ab70e1974f 100644 --- a/sp/src/game/server/sceneentity.cpp +++ b/sp/src/game/server/sceneentity.cpp @@ -32,6 +32,7 @@ #include "scenefilecache/ISceneFileCache.h" #include "SceneCache.h" #include "scripted.h" +#include "basemultiplayerplayer.h" #include "env_debughistory.h" #include "team.h" #include "triggers.h" @@ -159,6 +160,7 @@ class CSceneManager : public CBaseEntity #ifdef MAPBASE bool IsRunningScriptedSceneWithFlexAndNotPaused( CBaseFlex *pActor, bool bIgnoreInstancedScenes, const char *pszNotThisScene = NULL ); + bool IsTalkingInAScriptedScene( CBaseFlex *pActor, bool bIgnoreInstancedScenes = false ); CUtlVector< CHandle< CSceneEntity > > *GetActiveSceneList(); #endif @@ -485,6 +487,9 @@ class CSceneEntity : public CPointEntity, public IChoreoEventCallback bool HasUnplayedSpeech( void ); bool HasFlexAnimation( void ); +#ifdef MAPBASE + bool IsPlayingSpeech( void ); +#endif void SetCurrentTime( float t, bool forceClientSync ); @@ -1157,6 +1162,31 @@ bool CSceneEntity::HasFlexAnimation( void ) return false; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CSceneEntity::IsPlayingSpeech( void ) +{ + if ( m_pScene ) + { + float flTime = m_pScene->GetTime(); + for ( int i = 0; i < m_pScene->GetNumEvents(); i++ ) + { + CChoreoEvent *e = m_pScene->GetEvent( i ); + if ( e->GetType() == CChoreoEvent::SPEAK ) + { + if ( /*flTime >= e->GetStartTime() &&*/ flTime <= e->GetEndTime() ) + return true; + } + } + } + + return false; +} +#endif + //----------------------------------------------------------------------------- // Purpose: @@ -2348,6 +2378,40 @@ void CSceneEntity::InputInterjectResponse( inputdata_t &inputdata ) } else { +#ifdef NEW_RESPONSE_SYSTEM + CUtlString modifiers("scene:"); + modifiers += STRING( GetEntityName() ); + + while (candidates.Count() > 0) + { + // Pick a random slot in the candidates array. + int slot = RandomInt( 0, candidates.Count() - 1 ); + + CAI_BaseActor *npc = candidates[ slot ]; + + // Try to find the response for this slot. + AI_Response response; + CAI_Concept concept(inputdata.value.String()); + concept.SetSpeaker(npc); + AI_CriteriaSet set; + npc->GatherCriteria(&set, concept, modifiers.Get()); + bool result = npc->FindResponse( response, concept, &set); + if ( result ) + { + float duration = npc->GetResponseDuration( &response ); + + if ( ( duration > 0.0f ) && npc->PermitResponse( duration ) ) + { + // If we could look it up, dispatch it and bail. + npc->SpeakDispatchResponse( concept, &response, &set); + return; + } + } + + // Remove this entry and look for another one. + candidates.FastRemove(slot); + } +#else CUtlVector< NPCInterjection > validResponses; char modifiers[ 512 ]; @@ -2399,6 +2463,7 @@ void CSceneEntity::InputInterjectResponse( inputdata_t &inputdata ) } } } +#endif } } @@ -2576,6 +2641,30 @@ bool CSceneEntity::CheckActors() return false; } } +#ifdef MAPBASE + else if ( pTestActor->IsPlayer() ) + { + // Blixibon - Player speech handling + CBasePlayer *pPlayer = static_cast(pTestActor); + bool bShouldWait = false; + if ( pPlayer->GetExpresser() && pPlayer->GetExpresser()->IsSpeaking() ) + { + bShouldWait = true; + } + else if ( IsTalkingInAScriptedScene( pPlayer ) ) + { + bShouldWait = true; + } + + if ( bShouldWait ) + { + // One of the actors for this scene is talking already. + // Try again next think. + m_bWaitingForActor = true; + return false; + } + } +#endif } else if ( m_BusyActor == SCENE_BUSYACTOR_INTERRUPT || m_BusyActor == SCENE_BUSYACTOR_INTERRUPT_CANCEL ) { @@ -3019,6 +3108,16 @@ void CSceneEntity::QueueResumePlayback( void ) CAI_BaseActor *pBaseActor = dynamic_cast(pActor); if ( pBaseActor ) { +#ifdef NEW_RESPONSE_SYSTEM + AI_Response response; + CAI_Concept concept(STRING(m_iszResumeSceneFile)); + bool result = pBaseActor->FindResponse( response, concept, NULL ); + if ( result ) + { + const char* szResponse = response.GetResponsePtr(); + bStartedScene = InstancedScriptedScene( NULL, szResponse, &m_hWaitingForThisResumeScene, 0, false ) != 0; + } +#else AI_Response *result = pBaseActor->SpeakFindResponse( STRING(m_iszResumeSceneFile), NULL ); if ( result ) { @@ -3026,6 +3125,7 @@ void CSceneEntity::QueueResumePlayback( void ) result->GetResponse( response, sizeof( response ) ); bStartedScene = InstancedScriptedScene( NULL, response, &m_hWaitingForThisResumeScene, 0, false ) != 0; } +#endif } } } @@ -3688,7 +3788,7 @@ CChoreoScene *CSceneEntity::LoadScene( const char *filename, IChoreoEventCallbac Q_FixSlashes( loadfile ); // binary compiled vcd - void *pBuffer; + void *pBuffer = NULL; #ifdef MAPBASE // // Raw scene file support @@ -3713,12 +3813,13 @@ CChoreoScene *CSceneEntity::LoadScene( const char *filename, IChoreoEventCallbac { g_TokenProcessor.SetBuffer((char*)pBuffer); pScene = ChoreoLoadScene( loadfile, NULL, &g_TokenProcessor, LocalScene_Printf ); + g_TokenProcessor.SetBuffer(NULL); } // Okay, it's definitely missing. else { MissingSceneWarning( loadfile ); - return NULL; + pScene = NULL; } if (pScene) @@ -4236,6 +4337,7 @@ CBaseEntity *CSceneEntity::FindNamedEntity( const char *name, CBaseEntity *pActo #ifdef MAPBASE const char *GetFirstSoundInScene(const char *pszScene) { + const char *soundName = NULL; SceneCachedData_t sceneData; if ( scenefilecache->GetSceneCachedData( pszScene, &sceneData ) ) { @@ -4245,16 +4347,17 @@ const char *GetFirstSoundInScene(const char *pszScene) short stringId = scenefilecache->GetSceneCachedSound( sceneData.sceneId, 0 ); // Trust that it's been precached - return scenefilecache->GetSceneString( stringId ); + soundName = scenefilecache->GetSceneString( stringId ); } } else { void *pBuffer = NULL; - if (filesystem->ReadFileEx( pszScene, "MOD", &pBuffer, false, true )) + if (filesystem->ReadFileEx( pszScene, "MOD", &pBuffer, true )) { g_TokenProcessor.SetBuffer((char*)pBuffer); CChoreoScene *pScene = ChoreoLoadScene( pszScene, NULL, &g_TokenProcessor, LocalScene_Printf ); + g_TokenProcessor.SetBuffer(NULL); if (pScene) { for (int i = 0; i < pScene->GetNumEvents(); i++) @@ -4262,13 +4365,17 @@ const char *GetFirstSoundInScene(const char *pszScene) CChoreoEvent *pEvent = pScene->GetEvent(i); if (pEvent->GetType() == CChoreoEvent::SPEAK) - return pEvent->GetParameters(); + { + soundName = pEvent->GetParameters(); + break; + } } } } + FreeSceneFileMemory( pBuffer ); } - return NULL; + return soundName; } const char *GetFirstSoundInScene(CChoreoScene *scene) @@ -4436,6 +4543,8 @@ bool CSceneEntity::ScriptLoadSceneFromString(const char* pszFilename, const char PrecacheScene(pScene); } + g_TokenProcessor.SetBuffer(NULL); + if (pScene != NULL) { // release prior scene if present @@ -4789,6 +4898,33 @@ void CSceneEntity::OnSceneFinished( bool canceled, bool fireoutput ) m_OnCompletion.FireOutput( this, this, 0 ); } +#ifdef NEW_RESPONSE_SYSTEM + { + CBaseFlex *pFlex = FindNamedActor( 0 ) ; + if ( pFlex ) + { +#ifdef MAPBASE + CBasePlayer *pAsPlayer = ToBasePlayer(pFlex); +#else + CBaseMultiplayerPlayer *pAsPlayer = dynamic_cast(pFlex); +#endif + if (pAsPlayer) + { + CAI_Expresser *pExpresser = pAsPlayer->GetExpresser(); +#ifdef MAPBASE + if (pExpresser) +#endif + pExpresser->OnSpeechFinished(); + } + else if ( CAI_BaseActor *pActor = dynamic_cast( pFlex ) ) + { + CAI_Expresser *pExpresser = pActor->GetExpresser(); + pExpresser->OnSpeechFinished(); + } + } + } +#endif + // Put face back in neutral pose ClearSceneEvents( m_pScene, canceled ); @@ -5166,6 +5302,34 @@ float GetSceneDuration( char const *pszScene ) return (float)msecs * 0.001f; } +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pszScene - +// Output : float +//----------------------------------------------------------------------------- +float GetSceneSpeechDuration( char const* pszScene ) +{ + float flSecs = 0.0f; + + CChoreoScene* pScene = CSceneEntity::LoadScene( pszScene, nullptr ); + if (pScene) + { + for (int i = pScene->GetNumEvents() - 1; i >= 0; i--) + { + CChoreoEvent* pEvent = pScene->GetEvent( i ); + + if (pEvent->GetType() == CChoreoEvent::SPEAK) + { + flSecs = pEvent->GetStartTime() + pEvent->GetDuration(); + break; + } + } + delete pScene; + } + + return flSecs; +} + //----------------------------------------------------------------------------- // Purpose: // Input : *pszScene - @@ -5182,12 +5346,12 @@ int GetSceneSpeechCount( char const *pszScene ) else { void *pBuffer = NULL; - if (filesystem->ReadFileEx( pszScene, "MOD", &pBuffer, false, true )) + int iNumSounds = 0; + if (filesystem->ReadFileEx( pszScene, "MOD", &pBuffer, true )) { - int iNumSounds = 0; - g_TokenProcessor.SetBuffer((char*)pBuffer); CChoreoScene *pScene = ChoreoLoadScene( pszScene, NULL, &g_TokenProcessor, LocalScene_Printf ); + g_TokenProcessor.SetBuffer(NULL); if (pScene) { for (int i = 0; i < pScene->GetNumEvents(); i++) @@ -5198,9 +5362,11 @@ int GetSceneSpeechCount( char const *pszScene ) iNumSounds++; } } - - return iNumSounds; } + + FreeSceneFileMemory( pBuffer ); + + return iNumSounds; } #endif return 0; @@ -5257,7 +5423,7 @@ void PrecacheInstancedScene( char const *pszScene ) // Attempt to precache manually void *pBuffer = NULL; - if (filesystem->ReadFileEx( loadfile, "MOD", &pBuffer, false, true )) + if (filesystem->ReadFileEx( loadfile, "MOD", &pBuffer, true )) { g_TokenProcessor.SetBuffer((char*)pBuffer); CChoreoScene *pScene = ChoreoLoadScene( loadfile, NULL, &g_TokenProcessor, LocalScene_Printf ); @@ -5265,7 +5431,9 @@ void PrecacheInstancedScene( char const *pszScene ) { PrecacheChoreoScene(pScene); } + g_TokenProcessor.SetBuffer(NULL); } + FreeSceneFileMemory( pBuffer ); #else // Scenes are sloppy and don't always exist. // A scene that is not in the pre-built cache image, but on disk, is a true error. @@ -5937,6 +6105,31 @@ bool CSceneManager::IsRunningScriptedSceneWithFlexAndNotPaused( CBaseFlex *pActo } +bool CSceneManager::IsTalkingInAScriptedScene( CBaseFlex *pActor, bool bIgnoreInstancedScenes ) +{ + int c = m_ActiveScenes.Count(); + for ( int i = 0; i < c; i++ ) + { + CSceneEntity *pScene = m_ActiveScenes[ i ].Get(); + if ( !pScene || + !pScene->IsPlayingBack() || + pScene->IsPaused() || + ( bIgnoreInstancedScenes && dynamic_cast(pScene) != NULL ) + ) + { + continue; + } + + if ( pScene->InvolvesActor( pActor ) ) + { + if ( pScene->IsPlayingSpeech() ) + return true; + } + } + return false; +} + + CUtlVector< CHandle< CSceneEntity > > *CSceneManager::GetActiveSceneList() { return &m_ActiveScenes; @@ -6035,6 +6228,11 @@ bool IsRunningScriptedSceneWithFlexAndNotPaused( CBaseFlex *pActor, bool bIgnore return GetSceneManager()->IsRunningScriptedSceneWithFlexAndNotPaused( pActor, bIgnoreInstancedScenes, pszNotThisScene ); } +bool IsTalkingInAScriptedScene( CBaseFlex *pActor, bool bIgnoreInstancedScenes ) +{ + return GetSceneManager()->IsTalkingInAScriptedScene( pActor, bIgnoreInstancedScenes ); +} + CUtlVector< CHandle< CSceneEntity > > *GetActiveSceneList() { return GetSceneManager()->GetActiveSceneList(); diff --git a/sp/src/game/server/sceneentity.h b/sp/src/game/server/sceneentity.h index 6e005a60bf..ea286fe95f 100644 --- a/sp/src/game/server/sceneentity.h +++ b/sp/src/game/server/sceneentity.h @@ -13,7 +13,9 @@ // List of the last 5 lines of speech from NPCs for bug reports #define SPEECH_LIST_MAX_SOUNDS 5 +#ifndef NEW_RESPONSE_SYSTEM class AI_Response; +#endif struct recentNPCSpeech_t { @@ -41,9 +43,11 @@ bool IsRunningScriptedSceneWithSpeech( CBaseFlex *pActor, bool bIgnoreInstancedS bool IsRunningScriptedSceneWithSpeechAndNotPaused( CBaseFlex *pActor, bool bIgnoreInstancedScenes = false ); #ifdef MAPBASE bool IsRunningScriptedSceneWithFlexAndNotPaused( CBaseFlex *pActor, bool bIgnoreInstancedScenes = false, const char *pszNotThisScene = NULL ); +bool IsTalkingInAScriptedScene( CBaseFlex *pActor, bool bIgnoreInstancedScenes = false ); CUtlVector< CHandle< CSceneEntity > > *GetActiveSceneList(); #endif float GetSceneDuration( char const *pszScene ); +float GetSceneSpeechDuration( char const* pszScene ); int GetSceneSpeechCount( char const *pszScene ); bool IsInInterruptableScenes( CBaseFlex *pActor ); diff --git a/sp/src/game/server/scripted.cpp b/sp/src/game/server/scripted.cpp index 19fc67842e..37b4584484 100644 --- a/sp/src/game/server/scripted.cpp +++ b/sp/src/game/server/scripted.cpp @@ -136,6 +136,8 @@ BEGIN_DATADESC( CAI_ScriptedSequence ) DEFINE_OUTPUT(m_OnScriptEvent[6], "OnScriptEvent07"), DEFINE_OUTPUT(m_OnScriptEvent[7], "OnScriptEvent08"), #ifdef MAPBASE + DEFINE_OUTPUT(m_OnEntrySequence, "OnEntrySequence"), + DEFINE_OUTPUT(m_OnActionSequence, "OnActionSequence"), DEFINE_OUTPUT(m_OnPreIdleSequence, "OnPreIdleSequence"), DEFINE_OUTPUT(m_OnFoundNPC, "OnFoundNPC"), #endif @@ -864,6 +866,16 @@ void CAI_ScriptedSequence::OnBeginSequence( CBaseEntity *pActor ) m_OnBeginSequence.FireOutput( pActor, this ); } +void CAI_ScriptedSequence::OnEntrySequence( CBaseEntity *pActor ) +{ + m_OnEntrySequence.FireOutput( pActor, this ); +} + +void CAI_ScriptedSequence::OnActionSequence( CBaseEntity *pActor ) +{ + m_OnActionSequence.FireOutput( pActor, this ); +} + void CAI_ScriptedSequence::OnPreIdleSequence( CBaseEntity *pActor ) { m_OnPreIdleSequence.FireOutput( pActor, this ); diff --git a/sp/src/game/server/scripted.h b/sp/src/game/server/scripted.h index 1b7398a4b7..7b1d825441 100644 --- a/sp/src/game/server/scripted.h +++ b/sp/src/game/server/scripted.h @@ -97,6 +97,8 @@ class CAI_ScriptedSequence : public CBaseEntity void FireScriptEvent( int nEvent ); #ifdef MAPBASE void OnBeginSequence( CBaseEntity *pActor ); + void OnEntrySequence( CBaseEntity *pActor ); + void OnActionSequence( CBaseEntity *pActor ); void OnPreIdleSequence( CBaseEntity *pActor ); #else void OnBeginSequence( void ); @@ -220,6 +222,8 @@ class CAI_ScriptedSequence : public CBaseEntity COutputEvent m_OnCancelFailedSequence; // Fired when a scene is cancelled before it's ever run COutputEvent m_OnScriptEvent[MAX_SCRIPT_EVENTS]; #ifdef MAPBASE + COutputEvent m_OnEntrySequence; + COutputEvent m_OnActionSequence; COutputEvent m_OnPreIdleSequence; COutputEvent m_OnFoundNPC; #endif diff --git a/sp/src/game/server/server_base.vpc b/sp/src/game/server/server_base.vpc index df35da1e6d..64558734d2 100644 --- a/sp/src/game/server/server_base.vpc +++ b/sp/src/game/server/server_base.vpc @@ -126,8 +126,10 @@ $Project $File "ai_concommands.cpp" $File "ai_condition.cpp" $File "ai_condition.h" - $File "AI_Criteria.cpp" + $File "AI_Criteria.cpp" [!$NEW_RESPONSE_SYSTEM] $File "AI_Criteria.h" + $File "$SRCDIR\game\shared\ai_criteria_new.cpp" [$NEW_RESPONSE_SYSTEM] + $File "$SRCDIR\game\shared\ai_criteria_new.h" [$NEW_RESPONSE_SYSTEM] $File "ai_debug.h" $File "$SRCDIR\game\shared\ai_debug_shared.h" $File "ai_default.cpp" @@ -182,8 +184,10 @@ $Project $File "ai_planesolver.h" $File "ai_playerally.cpp" $File "ai_playerally.h" - $File "AI_ResponseSystem.cpp" + $File "AI_ResponseSystem.cpp" [!$NEW_RESPONSE_SYSTEM] $File "AI_ResponseSystem.h" + $File "$SRCDIR\game\shared\ai_responsesystem_new.cpp" [$NEW_RESPONSE_SYSTEM] + $File "$SRCDIR\game\shared\ai_responsesystem_new.h" [$NEW_RESPONSE_SYSTEM] $File "ai_route.cpp" $File "ai_route.h" $File "ai_routedist.h" @@ -197,8 +201,10 @@ $Project $File "ai_senses.h" $File "ai_sentence.cpp" $File "ai_sentence.h" - $File "ai_speech.cpp" + $File "ai_speech.cpp" [!$NEW_RESPONSE_SYSTEM] $File "ai_speech.h" + $File "ai_speech_new.cpp" [$NEW_RESPONSE_SYSTEM] + $File "ai_speech_new.h" [$NEW_RESPONSE_SYSTEM] $File "ai_speechfilter.cpp" $File "ai_speechfilter.h" $File "ai_squad.cpp" @@ -428,7 +434,6 @@ $Project $File "init_factory.h" $File "intermission.cpp" $File "$SRCDIR\public\interpolatortypes.h" - $File "$SRCDIR\game\shared\interval.h" $File "$SRCDIR\public\iregistry.h" $File "$SRCDIR\game\shared\iscenetokenprocessor.h" $File "iservervehicle.h" @@ -686,7 +691,6 @@ $Project "h_export.cpp" \ "init_factory.cpp" \ "$SRCDIR\public\interpolatortypes.cpp" \ - "$SRCDIR\game\shared\interval.cpp" \ "$SRCDIR\public\keyframe\keyframe.cpp" \ "$SRCDIR\common\language.cpp" \ "$SRCDIR\public\map_utils.cpp" \ @@ -1000,6 +1004,7 @@ $Project $File "$SRCDIR\public\winlite.h" $File "$SRCDIR\public\worldsize.h" $File "$SRCDIR\public\zip_uncompressed.h" + $File "$SRCDIR\public\tier1\interval.h" $File "$SRCDIR\game\shared\mp_shareddefs.h" $File "$SRCDIR\game\shared\econ\ihasowner.h" //Haptics diff --git a/sp/src/game/server/server_mapbase.vpc b/sp/src/game/server/server_mapbase.vpc index 6b2ec779cd..b04706d48d 100644 --- a/sp/src/game/server/server_mapbase.vpc +++ b/sp/src/game/server/server_mapbase.vpc @@ -10,6 +10,7 @@ $Configuration { $PreprocessorDefinitions "$BASE;ASW_PROJECTED_TEXTURES;DYNAMIC_RTT_SHADOWS;GLOWS_ENABLE" $PreprocessorDefinitions "$BASE;MAPBASE_VSCRIPT" [$MAPBASE_VSCRIPT] + $PreprocessorDefinitions "$BASE;NEW_RESPONSE_SYSTEM" [$NEW_RESPONSE_SYSTEM] } } @@ -29,6 +30,11 @@ $Project $File "env_dof_controller.h" $File "logic_playmovie.cpp" $File "movie_display.cpp" + $File "fogvolume.cpp" + $File "fogvolume.h" + $File "ai_expresserfollowup.cpp" [$NEW_RESPONSE_SYSTEM] + $File "ai_speechqueue.cpp" [$NEW_RESPONSE_SYSTEM] + $File "ai_speechqueue.h" [$NEW_RESPONSE_SYSTEM] $Folder "Mapbase" { @@ -40,6 +46,8 @@ $Project $File "$SRCDIR\game\shared\mapbase\MapEdit.h" $File "$SRCDIR\game\shared\mapbase\matchers.cpp" $File "$SRCDIR\game\shared\mapbase\matchers.h" + $File "$SRCDIR\game\shared\mapbase\singleplayer_animstate.cpp" + $File "$SRCDIR\game\shared\mapbase\singleplayer_animstate.h" $File "$SRCDIR\game\shared\mapbase\vscript_funcs_shared.cpp" [$MAPBASE_VSCRIPT] $File "$SRCDIR\game\shared\mapbase\vscript_funcs_shared.h" [$MAPBASE_VSCRIPT] $File "$SRCDIR\game\shared\mapbase\vscript_singletons.cpp" [$MAPBASE_VSCRIPT] @@ -65,6 +73,7 @@ $Project $File "mapbase\GlobalStrings.h" $File "mapbase\logic_externaldata.cpp" $File "mapbase\logic_skill.cpp" + $File "mapbase\logic_substring.cpp" $File "mapbase\point_advanced_finder.cpp" $File "mapbase\point_copy_size.cpp" $File "mapbase\point_damageinfo.cpp" @@ -107,5 +116,6 @@ $Project $Folder "Link Libraries" { $Lib "vscript" [$MAPBASE_VSCRIPT] + $Lib "responserules" [$NEW_RESPONSE_SYSTEM] } } diff --git a/sp/src/game/server/triggers.cpp b/sp/src/game/server/triggers.cpp index 03e23cb7ba..2361f8facd 100644 --- a/sp/src/game/server/triggers.cpp +++ b/sp/src/game/server/triggers.cpp @@ -1279,7 +1279,7 @@ void CTriggerLook::Touch(CBaseEntity *pOther) VectorNormalize(vTargetDir); float fDotPr = DotProduct(vLookDir,vTargetDir); - if (fDotPr > m_flFieldOfView && (!m_bUseLOS || pOther->FVisible(pOther))) + if (fDotPr > m_flFieldOfView && (!m_bUseLOS || pOther->FVisible(m_hLookTargets[i]))) { hLookingAtEntity = m_hLookTargets[i]; break; @@ -3100,6 +3100,7 @@ BEGIN_DATADESC( CTriggerCamera ) #ifdef MAPBASE DEFINE_KEYFIELD( m_fov, FIELD_FLOAT, "fov" ), DEFINE_KEYFIELD( m_fovSpeed, FIELD_FLOAT, "fov_rate" ), + DEFINE_KEYFIELD( m_flTrackSpeed, FIELD_FLOAT, "trackspeed" ), DEFINE_KEYFIELD( m_bDontSetPlayerView, FIELD_BOOLEAN, "DontSetPlayerView" ), #endif @@ -3110,6 +3111,10 @@ BEGIN_DATADESC( CTriggerCamera ) #ifdef MAPBASE DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFOV", InputSetFOV ), DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFOVRate", InputSetFOVRate ), + + //DEFINE_INPUTFUNC( FIELD_STRING, "SetTarget", InputSetTarget ), // Defined by base class + DEFINE_INPUTFUNC( FIELD_STRING, "SetTargetAttachment", InputSetTargetAttachment ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetTrackSpeed", InputSetTrackSpeed ), #endif // Function Pointers @@ -3138,6 +3143,7 @@ CTriggerCamera::CTriggerCamera() { m_fov = 90; m_fovSpeed = 1; + m_flTrackSpeed = 40.0f; } //------------------------------------------------------------------------------ @@ -3259,6 +3265,61 @@ void CTriggerCamera::InputSetFOVRate( inputdata_t &inputdata ) { m_fovSpeed = inputdata.value.Float(); } + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CTriggerCamera::InputSetTarget( inputdata_t &inputdata ) +{ + BaseClass::InputSetTarget( inputdata ); + + if ( FStrEq(STRING(m_target), "!player") ) + { + AddSpawnFlags( SF_CAMERA_PLAYER_TARGET ); + m_hTarget = m_hPlayer; + } + else + { + RemoveSpawnFlags( SF_CAMERA_PLAYER_TARGET ); + m_hTarget = GetNextTarget(); + } +} + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CTriggerCamera::InputSetTargetAttachment( inputdata_t &inputdata ) +{ + m_iszTargetAttachment = inputdata.value.StringID(); + m_iAttachmentIndex = 0; + + if (m_hTarget) + { + if ( m_iszTargetAttachment != NULL_STRING ) + { + if ( !m_hTarget->GetBaseAnimating() ) + { + Warning("%s tried to target an attachment (%s) on target %s, which has no model.\n", GetClassname(), STRING(m_iszTargetAttachment), STRING(m_hTarget->GetEntityName()) ); + } + else + { + m_iAttachmentIndex = m_hTarget->GetBaseAnimating()->LookupAttachment( STRING(m_iszTargetAttachment) ); + if ( m_iAttachmentIndex <= 0 ) + { + Warning("%s could not find attachment %s on target %s.\n", GetClassname(), STRING(m_iszTargetAttachment), STRING(m_hTarget->GetEntityName()) ); + } + } + } + } +} + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CTriggerCamera::InputSetTrackSpeed( inputdata_t &inputdata ) +{ + m_flTrackSpeed = inputdata.value.Float(); +} #endif //----------------------------------------------------------------------------- @@ -3436,12 +3497,14 @@ void CTriggerCamera::Enable( void ) #ifdef MAPBASE if (!m_bDontSetPlayerView) #endif - pPlayer->SetViewEntity( this ); - - // Hide the player's viewmodel - if ( pPlayer->GetActiveWeapon() ) { - pPlayer->GetActiveWeapon()->AddEffects( EF_NODRAW ); + pPlayer->SetViewEntity( this ); + + // Hide the player's viewmodel + if ( pPlayer->GetActiveWeapon() ) + { + pPlayer->GetActiveWeapon()->AddEffects( EF_NODRAW ); + } } // Only track if we have a target @@ -3487,11 +3550,16 @@ void CTriggerCamera::Disable( void ) pBasePlayer->RemoveSolidFlags( FSOLID_NOT_SOLID ); } + if ( HasSpawnFlags( SF_CAMERA_PLAYER_TAKECONTROL ) ) + { + pBasePlayer->EnableControl( TRUE ); + } + if (!m_bDontSetPlayerView) + { pBasePlayer->SetViewEntity( NULL ); - - pBasePlayer->EnableControl(TRUE); - pBasePlayer->m_Local.m_bDrawViewmodel = true; + pBasePlayer->m_Local.m_bDrawViewmodel = true; + } } if ( HasSpawnFlags( SF_CAMERA_PLAYER_SETFOV ) ) @@ -3626,7 +3694,11 @@ void CTriggerCamera::FollowTarget( ) dy = dy - 360; QAngle vecAngVel; +#ifdef MAPBASE + vecAngVel.Init( dx * m_flTrackSpeed * gpGlobals->frametime, dy * m_flTrackSpeed * gpGlobals->frametime, GetLocalAngularVelocity().z ); +#else vecAngVel.Init( dx * 40 * gpGlobals->frametime, dy * 40 * gpGlobals->frametime, GetLocalAngularVelocity().z ); +#endif SetLocalAngularVelocity(vecAngVel); } diff --git a/sp/src/game/server/triggers.h b/sp/src/game/server/triggers.h index 4aacad13b9..47bf82be39 100644 --- a/sp/src/game/server/triggers.h +++ b/sp/src/game/server/triggers.h @@ -300,6 +300,10 @@ class CTriggerCamera : public CBaseEntity #ifdef MAPBASE void InputSetFOV( inputdata_t &inputdata ); void InputSetFOVRate( inputdata_t &inputdata ); + + void InputSetTarget( inputdata_t &inputdata ); + void InputSetTargetAttachment( inputdata_t &inputdata ); + void InputSetTrackSpeed( inputdata_t &inputdata ); #endif private: @@ -323,6 +327,7 @@ class CTriggerCamera : public CBaseEntity #ifdef MAPBASE float m_fov; float m_fovSpeed; + float m_flTrackSpeed; bool m_bDontSetPlayerView; #endif diff --git a/sp/src/game/server/vscript_server.cpp b/sp/src/game/server/vscript_server.cpp index 6927b0f106..559c57e3d0 100644 --- a/sp/src/game/server/vscript_server.cpp +++ b/sp/src/game/server/vscript_server.cpp @@ -37,10 +37,16 @@ extern ScriptClassDesc_t * GetScriptDesc( CBaseEntity * ); #endif // VMPROFILE +#ifdef MAPBASE_VSCRIPT +static ScriptHook_t g_Hook_OnEntityCreated; +static ScriptHook_t g_Hook_OnEntitySpawned; +static ScriptHook_t g_Hook_OnEntityDeleted; +#endif + //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- -class CScriptEntityIterator +class CScriptEntityIterator : public IEntityListener { public: #ifdef MAPBASE_VSCRIPT @@ -115,6 +121,66 @@ class CScriptEntityIterator { return ToHScript( gEntList.FindEntityClassNearestFacing( origin, facing, threshold, const_cast(classname) ) ); } + + HSCRIPT FindByClassnameNearest2D( const char *szName, const Vector &vecSrc, float flRadius ) + { + return ToHScript( gEntList.FindEntityByClassnameNearest2D( szName, vecSrc, flRadius ) ); + } + + // + // Custom Procedurals + // + void AddCustomProcedural( const char *pszName, HSCRIPT hFunc, bool bCanReturnMultiple ) + { + gEntList.AddCustomProcedural( pszName, hFunc, bCanReturnMultiple ); + } + + void RemoveCustomProcedural( const char *pszName ) + { + gEntList.RemoveCustomProcedural( pszName ); + } + + void EnableEntityListening() + { + // Start getting entity updates! + gEntList.AddListenerEntity( this ); + } + + void DisableEntityListening() + { + // Stop getting entity updates! + gEntList.RemoveListenerEntity( this ); + } + + void OnEntityCreated( CBaseEntity *pEntity ) + { + if ( g_pScriptVM ) + { + // entity + ScriptVariant_t args[] = { ScriptVariant_t( pEntity->GetScriptInstance() ) }; + g_Hook_OnEntityCreated.Call( NULL, NULL, args ); + } + }; + + void OnEntitySpawned( CBaseEntity *pEntity ) + { + if ( g_pScriptVM ) + { + // entity + ScriptVariant_t args[] = { ScriptVariant_t( pEntity->GetScriptInstance() ) }; + g_Hook_OnEntitySpawned.Call( NULL, NULL, args ); + } + }; + + void OnEntityDeleted( CBaseEntity *pEntity ) + { + if ( g_pScriptVM ) + { + // entity + ScriptVariant_t args[] = { ScriptVariant_t( pEntity->GetScriptInstance() ) }; + g_Hook_OnEntityDeleted.Call( NULL, NULL, args ); + } + }; #endif private: } g_ScriptEntityIterator; @@ -138,6 +204,25 @@ BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptEntityIterator, "CEntities", SCRIPT_SINGLETO #ifdef MAPBASE_VSCRIPT DEFINE_SCRIPTFUNC( FindByClassnameWithinBox, "Find entities by class name within an AABB. Pass 'null' to start an iteration, or reference to a previously found entity to continue a search" ) DEFINE_SCRIPTFUNC( FindByClassNearestFacing, "Find the nearest entity along the facing direction from the given origin within the angular threshold with the given classname." ) + DEFINE_SCRIPTFUNC( FindByClassnameNearest2D, "Find entities by class name nearest to a point in 2D space." ) + + DEFINE_SCRIPTFUNC( AddCustomProcedural, "Adds a custom '!' target name. The first parameter is the name of the procedural (which should NOT include the '!'), the second parameter is a function which should support 5 arguments (name, startEntity, searchingEntity, activator, caller), and the third parameter is whether or not this procedural can return multiple entities. Note that these are NOT saved and must be redeclared on restore!" ) + DEFINE_SCRIPTFUNC( RemoveCustomProcedural, "Removes a custom '!' target name previously defined with AddCustomProcedural." ) + + DEFINE_SCRIPTFUNC( EnableEntityListening, "Enables the 'OnEntity' hooks. This function must be called before using them." ) + DEFINE_SCRIPTFUNC( DisableEntityListening, "Disables the 'OnEntity' hooks." ) + + BEGIN_SCRIPTHOOK( g_Hook_OnEntityCreated, "OnEntityCreated", FIELD_VOID, "Called when an entity is created. Requires EnableEntityListening() to be fired beforehand." ) + DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( g_Hook_OnEntitySpawned, "OnEntitySpawned", FIELD_VOID, "Called when an entity spawns. Requires EnableEntityListening() to be fired beforehand." ) + DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( g_Hook_OnEntityDeleted, "OnEntityDeleted", FIELD_VOID, "Called when an entity is deleted. Requires EnableEntityListening() to be fired beforehand." ) + DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT ) + END_SCRIPTHOOK() #endif END_SCRIPTDESC(); diff --git a/sp/src/game/server/vscript_server.nut b/sp/src/game/server/vscript_server.nut index deeacf5dce..0a2d75d79a 100644 --- a/sp/src/game/server/vscript_server.nut +++ b/sp/src/game/server/vscript_server.nut @@ -63,10 +63,26 @@ function EntFireByHandle( target, action, value = null, delay = 0.0, activator = function DispatchParticleEffect( particleName, origin, angles, entity = null ) { - DoDispatchParticleEffect( particleName, origin, angles, entity ); + return DoDispatchParticleEffect( particleName, origin, angles, entity ); } -__Documentation.RegisterHelp( "CConvars::GetClientConvarValue", "CConvars::GetClientConvarValue(string, int)", "Returns the convar value for the entindex as a string. Only works with client convars with the FCVAR_USERINFO flag." ); +function ImpulseScale( flTargetMass, flDesiredSpeed ) +{ + return flTargetMass * flDesiredSpeed; +} +__Documentation.RegisterHelp( "ImpulseScale", "float ImpulseScale(float, float)", "Returns an impulse scale required to push an object." ); + +local PrecacheModel = PrecacheModel +function PrecacheModel( a, b = true ) +{ + return PrecacheModel( a, b ) +} + +local PrecacheOther = PrecacheOther +function PrecacheOther( a, b = "" ) +{ + PrecacheOther( a, b ) +} function __ReplaceClosures( script, scope ) { diff --git a/sp/src/game/shared/GameEventListener.h b/sp/src/game/shared/GameEventListener.h index 42d2ebaa31..9378257d79 100644 --- a/sp/src/game/shared/GameEventListener.h +++ b/sp/src/game/shared/GameEventListener.h @@ -25,7 +25,7 @@ class CGameEventListener : public IGameEventListener2 { } - ~CGameEventListener() + virtual ~CGameEventListener() { StopListeningForAllEvents(); } diff --git a/sp/src/game/shared/SoundEmitterSystem.cpp b/sp/src/game/shared/SoundEmitterSystem.cpp index e1072b78b2..6b10c6fead 100644 --- a/sp/src/game/shared/SoundEmitterSystem.cpp +++ b/sp/src/game/shared/SoundEmitterSystem.cpp @@ -132,12 +132,18 @@ void Hack_FixEscapeChars( char *str ) #ifdef MAPBASE static const ConVar *pHostTimescale; +static ConVar host_pitchscale( "host_pitchscale", "-1", FCVAR_REPLICATED, "If greater than 0, controls the pitch scale of sounds instead of host_timescale." ); static float GetSoundPitchScale() { static ConVarRef sv_cheats( "sv_cheats" ); if (sv_cheats.GetBool()) - return pHostTimescale->GetFloat(); + { + if (host_pitchscale.GetFloat() > 0.0f) + return host_pitchscale.GetFloat(); + else + return pHostTimescale->GetFloat(); + } return 1.0f; } @@ -1417,6 +1423,52 @@ int SENTENCEG_Lookup(const char *sample) } #endif +#if defined(MAPBASE) && defined(GAME_DLL) +//----------------------------------------------------------------------------- +// Purpose: Wrapper to emit a sentence and also a close caption token for the sentence as appropriate. +// Input : filter - +// iEntIndex - +// iChannel - +// iSentenceIndex - +// flVolume - +// iSoundlevel - +// iFlags - +// iPitch - +// bUpdatePositions - +// soundtime - +//----------------------------------------------------------------------------- +void CBaseEntity::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*/, int iSpecialDSP /*= 0*/, int iSpeakerIndex /*= 0*/ ) +{ + CUtlVector< Vector > soundOrigins; + + bool bSwallowed = CEnvMicrophone::OnSentencePlayed( + iEntIndex, + iSentenceIndex, + iSoundlevel, + flVolume, + iFlags, + iPitch, + pOrigin, + soundtime, + soundOrigins ); + if ( bSwallowed ) + return; + + CBaseEntity *pEntity = UTIL_EntityByIndex( iEntIndex ); + if ( pEntity ) + { + pEntity->ModifySentenceParams( iSentenceIndex, iChannel, flVolume, iSoundlevel, iFlags, iPitch, + &pOrigin, &pDirection, bUpdatePositions, soundtime, iSpecialDSP, iSpeakerIndex ); + } + + enginesound->EmitSentenceByIndex( filter, iEntIndex, iChannel, iSentenceIndex, + flVolume, iSoundlevel, iFlags, iPitch * GetSoundPitchScale(), iSpecialDSP, pOrigin, pDirection, &soundOrigins, bUpdatePositions, soundtime, iSpeakerIndex ); +} +#endif + void UTIL_EmitAmbientSound( int entindex, const Vector &vecOrigin, const char *samp, float vol, soundlevel_t soundlevel, int fFlags, int pitch, float soundtime /*= 0.0f*/, float *duration /*=NULL*/ ) { #ifdef STAGING_ONLY diff --git a/sp/src/game/shared/activitylist.cpp b/sp/src/game/shared/activitylist.cpp index 9e986cb7ad..b8aea0bf06 100644 --- a/sp/src/game/shared/activitylist.cpp +++ b/sp/src/game/shared/activitylist.cpp @@ -2296,15 +2296,22 @@ void ActivityList_RegisterSharedActivities( void ) REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_AR2 ); REGISTER_SHARED_ACTIVITY( ACT_RELOAD_AR2 ); - //REGISTER_SHARED_ACTIVITY( ACT_RELOAD_AR2_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_AR2_LOW ); REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RELOAD_AR2 ); + + REGISTER_SHARED_ACTIVITY( ACT_COVER_AR2_LOW ); #endif -#ifdef SHARED_COMBINE_ACTIVITIES +#if SHARED_COMBINE_ACTIVITIES REGISTER_SHARED_ACTIVITY( ACT_COMBINE_THROW_GRENADE ); REGISTER_SHARED_ACTIVITY( ACT_COMBINE_AR2_ALTFIRE ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_COMBINE_THROW_GRENADE ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_COMBINE_AR2_ALTFIRE ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_SPECIAL_ATTACK1 ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_SPECIAL_ATTACK2 ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_SIGNAL_ADVANCE ); REGISTER_SHARED_ACTIVITY( ACT_GESTURE_SIGNAL_FORWARD ); REGISTER_SHARED_ACTIVITY( ACT_GESTURE_SIGNAL_GROUP ); @@ -2314,9 +2321,322 @@ void ActivityList_RegisterSharedActivities( void ) REGISTER_SHARED_ACTIVITY( ACT_GESTURE_SIGNAL_TAKECOVER ); #endif -#ifdef COMPANION_HOLSTER_WORKAROUND +#if EXPANDED_HL2_WEAPON_ACTIVITIES + REGISTER_SHARED_ACTIVITY( ACT_IDLE_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_ANGRY_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_REVOLVER_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_REVOLVER_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_REVOLVER_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_REVOLVER_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RANGE_ATTACK_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RELOAD_REVOLVER ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_CROSSBOW ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_ANGRY_CROSSBOW ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_CROSSBOW ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_CROSSBOW ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_CROSSBOW ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_CROSSBOW ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_CROSSBOW ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_CROSSBOW ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_CROSSBOW_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_CROSSBOW_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_CROSSBOW_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_CROSSBOW_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RANGE_ATTACK_CROSSBOW ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RELOAD_CROSSBOW ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_CROSSBOW_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_CROSSBOW_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_CROSSBOW_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_CROSSBOW_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_CROSSBOW_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_CROSSBOW_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AIM_CROSSBOW_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_CROSSBOW_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_CROSSBOW_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_PISTOL_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_PISTOL_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_PISTOL_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_PISTOL_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_PISTOL_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_PISTOL_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AIM_PISTOL_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_PISTOL_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_PISTOL_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_WALK_CROUCH_PISTOL ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_CROUCH_AIM_PISTOL ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_CROUCH_PISTOL ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_CROUCH_AIM_PISTOL ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_SHOTGUN ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_SHOTGUN ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_SHOTGUN ); + + REGISTER_SHARED_ACTIVITY( ACT_COVER_SHOTGUN_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_SHOTGUN_LOW ); + + REGISTER_SHARED_ACTIVITY( ACT_WALK_SHOTGUN_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_SHOTGUN_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_SHOTGUN_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_SHOTGUN_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AIM_SHOTGUN_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_SHOTGUN_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_SHOTGUN_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_RPG_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_RPG_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RANGE_ATTACK_RPG ); + + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_ANNABELLE ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_ANNABELLE_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RANGE_ATTACK_ANNABELLE ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_ANNABELLE ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_ANNABELLE_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RELOAD_ANNABELLE ); + + REGISTER_SHARED_ACTIVITY( ACT_WALK_MELEE ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_MELEE ); + + REGISTER_SHARED_ACTIVITY( ACT_RUN_PACKAGE ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_SUITCASE ); + REGISTER_SHARED_ACTIVITY( ACT_ARM_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_ARM_SHOTGUN ); + REGISTER_SHARED_ACTIVITY( ACT_ARM_RPG ); + REGISTER_SHARED_ACTIVITY( ACT_ARM_MELEE ); REGISTER_SHARED_ACTIVITY( ACT_DISARM_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_DISARM_SHOTGUN ); + REGISTER_SHARED_ACTIVITY( ACT_DISARM_RPG ); + REGISTER_SHARED_ACTIVITY( ACT_DISARM_MELEE ); +#endif + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_ANGRY_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_AR1 ); + //REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_AR1_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_AR1_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_AR1_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_AR1_LOW ); + //REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RANGE_ATTACK_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RELOAD_AR1 ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AR1_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AR1_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AR1_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AR1_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AR1_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AR1_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AIM_AR1_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_AR1_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_AR1_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_ANGRY_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_SMG2 ); + //REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_SMG2_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_SMG2_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_SMG2_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_SMG2_LOW ); + //REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RANGE_ATTACK_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RELOAD_SMG2 ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_SMG2_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_SMG2_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_SMG2_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_SMG2_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_SMG2_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_SMG2_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AIM_SMG2_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_SMG2_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_SMG2_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_ANGRY_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_SNIPER_RIFLE ); + //REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_SNIPER_RIFLE_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_SNIPER_RIFLE_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_SNIPER_RIFLE_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_SNIPER_RIFLE_LOW ); + //REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RANGE_ATTACK_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RELOAD_SNIPER_RIFLE ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_SNIPER_RIFLE_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_SNIPER_RIFLE_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_SNIPER_RIFLE_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_SNIPER_RIFLE_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_SNIPER_RIFLE_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_SNIPER_RIFLE_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AIM_SNIPER_RIFLE_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_SNIPER_RIFLE_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_SNIPER_RIFLE_STIMULATED ); +#endif + +#if EXPANDED_NAVIGATION_ACTIVITIES + REGISTER_SHARED_ACTIVITY( ACT_CLIMB_ALL ); + REGISTER_SHARED_ACTIVITY( ACT_CLIMB_IDLE ); + + REGISTER_SHARED_ACTIVITY( ACT_CLIMB_MOUNT_TOP ); + REGISTER_SHARED_ACTIVITY( ACT_CLIMB_MOUNT_BOTTOM ); + REGISTER_SHARED_ACTIVITY( ACT_CLIMB_DISMOUNT_BOTTOM ); +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK1_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK2_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_MED ); + + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_AR2_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_SMG1_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_SHOTGUN_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_PISTOL_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_RPG_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_REVOLVER_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_CROSSBOW_MED ); + + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_AR2_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_SMG1_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_SHOTGUN_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_PISTOL_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_RPG_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_REVOLVER_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_CROSSBOW_MED ); + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_AR1_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_AR1_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_SMG2_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_SMG2_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_SNIPER_RIFLE_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_SNIPER_RIFLE_MED ); +#endif + + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_R ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_L ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_LOW_R ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_LOW_L ); + + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_R_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_L_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_LOW_R_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_LOW_L_RIFLE ); + + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_R_PISTOL ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_L_PISTOL ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_LOW_R_PISTOL ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_LOW_L_PISTOL ); +#endif + +#if EXPANDED_HL2DM_ACTIVITIES + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_PISTOL ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_SHOTGUN ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_SMG1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_AR2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_PHYSGUN ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_GRENADE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_RPG ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_CROSSBOW ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_MELEE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_SLAM ); + + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_PISTOL ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SHOTGUN ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_PHYSGUN ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_GRENADE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_RPG ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_CROSSBOW ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_MELEE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SLAM ); + + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_RUN_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_CROUCH_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_CROUCH_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELOAD_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_REVOLVER ); + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_RUN_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_CROUCH_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_CROUCH_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELOAD_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_AR1 ); + + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_RUN_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_CROUCH_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_CROUCH_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELOAD_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_SMG2 ); + + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_RUN_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_CROUCH_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_CROUCH_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELOAD_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_SNIPER_RIFLE ); +#endif + + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_USE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_RUN_USE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_USE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_CROUCH_USE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_CROUCH_USE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_USE ); + + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_USE_HEAVY ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_RUN_USE_HEAVY ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_USE_HEAVY ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_CROUCH_USE_HEAVY ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_CROUCH_USE_HEAVY ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_USE_HEAVY ); #endif AssertMsg( g_HighestActivity == LAST_SHARED_ACTIVITY - 1, "Not all activities from ai_activity.h registered in activitylist.cpp" ); diff --git a/sp/src/game/shared/ai_activity.h b/sp/src/game/shared/ai_activity.h index 44b8f62ddf..2373f2f93e 100644 --- a/sp/src/game/shared/ai_activity.h +++ b/sp/src/game/shared/ai_activity.h @@ -13,42 +13,52 @@ #ifdef MAPBASE -// Mapbase adds a few shared activities. // -// These used to be placed in between existing activities, as outside of the code activities are based off of strings. -// This seemed like a bad idea, but no problems arose at the time. -// I later discovered that apparently some things in MP use the direct integers instead of the enum names. -// I retroactively put all custom activities at the bottom of the enum instead. -// Their placements in activitylist.cpp and ai_activity.cpp have not been changed. +// Mapbase adds many new shared activities, primarily for NPCs. +// +// These are at the bottom of the enum to prevent disruptions in the order of existing activities. +// // AR2 ACTIVITY FIX -// You know all of those AR2 activities that citizens and combine soldiers have? -// Yeah, those are unused. It appears Valve forgot to implement them. -// -// What could be 20-40 different animations on two different characters are not even defined in code. -// I didn't even realize they were unused until I saw ACT_RELOAD_AR2, so I don't blame them for never realizing this. -// They work surprisingly well for probably never being tested in-game. +// Citizens and Combine soldiers have several activities for the SMG1 and AR2 which differ from each other. +// Across both NPCs, there are around 20-40 different AR2 animations. Most of these animations are similar to the +// SMG1 animations, except their hand positions are adjusted to use the AR2 instead. // -// 1 = Add activities directly -// 2 = Add activities as custom activities (todo) +// Unfortunately, the vast majority of the AR2 animations in those models are not declared as real activities in +// code. The AR2 instead falls back to its SMG1 animation counterparts. +// This is thought to be an oversight which dates back to late in Half-Life 2's development. // -// 2 should only be preferable if adding them like this breaks something. +// This preprocessor declares those activities and implements them on the AR2. In-game, they work surprisingly well +// despite presumably never being tested in-game during HL2's development. #define AR2_ACTIVITY_FIX 1 -// COMPANION HOLSTER WORKAROUND -// I introduced a separate holster/unholster animation to male_shared -// and female_shared and I realized it might conflict with Alyx's animation. -// -// I came up with a solution--ACT_ARM_RIFLE and its disarm counterpart--to solve it. -// I didn't think about the fact I could've named them the same as Alyx's so her animations would overwrite it... -// ...so this has been deactivated. -//#define COMPANION_HOLSTER_WORKAROUND 1 - // SHARED COMBINE ACTIVITIES -// This turns ACT_COMBINE_AR2_ALTFIRE and ACT_COMBINE_THROW_GRENADE into shared activities. -// This is necessary so other NPCs to use them without having to rely on a bunch of custom activities. +// This turns ACT_COMBINE_AR2_ALTFIRE, ACT_COMBINE_THROW_GRENADE, and their new gesture counterparts into shared activities. +// This is necessary for other NPCs to use them without having to rely on private custom activities declared through the AI definition system. #define SHARED_COMBINE_ACTIVITIES 1 +// EXPANDED HL2 WEAPON ACTIVITIES +// This enables a bunch of new activities for Half-Life 2 weapons, including new 357 animations and readiness activities for pistols. +#define EXPANDED_HL2_WEAPON_ACTIVITIES 1 + +// EXPANDED HL2 UNUSED WEAPON ACTIVITIES +// This enables a bunch of new activities for unused Half-Life 2 weapons, particularly those which exist in the SDK, but are deactivated by default. +// This essentially just means mods which restore those weapons have the option of using custom activities for them. +// Mapbase's backup activity system would allow them to fall back to other weapons if the relevant activities do not exist. +#define EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES 0 + +// EXPANDED NAVIGATION ACTIVITIES +// This enables some new navigation-related activities. +#define EXPANDED_NAVIGATION_ACTIVITIES 1 + +// EXPANDED HL2 COVER ACTIVITIES +// This enables some new cover-related activities. +#define EXPANDED_HL2_COVER_ACTIVITIES 1 + +// EXPANDED HL2DM ACTIVITIES +// This enables some new activities for the HL2:DM set. +#define EXPANDED_HL2DM_ACTIVITIES 1 + #endif #define ACTIVITY_NOT_AVAILABLE -1 @@ -2169,16 +2179,23 @@ typedef enum ACT_RUN_AIM_AR2, ACT_RELOAD_AR2, - //ACT_RELOAD_AR2_LOW, + ACT_RELOAD_AR2_LOW, ACT_GESTURE_RELOAD_AR2, + + ACT_COVER_AR2_LOW, #endif -#ifdef SHARED_COMBINE_ACTIVITIES +#if SHARED_COMBINE_ACTIVITIES ACT_COMBINE_THROW_GRENADE, ACT_COMBINE_AR2_ALTFIRE, - // New gesture-based signals as activities for people who want to use them + // Gesture versions for existing Combine signal and grenade activities + ACT_GESTURE_COMBINE_THROW_GRENADE, + ACT_GESTURE_COMBINE_AR2_ALTFIRE, + ACT_GESTURE_SPECIAL_ATTACK1, + ACT_GESTURE_SPECIAL_ATTACK2, + ACT_GESTURE_SIGNAL_ADVANCE, ACT_GESTURE_SIGNAL_FORWARD, ACT_GESTURE_SIGNAL_GROUP, @@ -2188,9 +2205,338 @@ typedef enum ACT_GESTURE_SIGNAL_TAKECOVER, #endif -#ifdef COMPANION_HOLSTER_WORKAROUND +#if EXPANDED_HL2_WEAPON_ACTIVITIES + // Revolver (357) + ACT_IDLE_REVOLVER, + ACT_IDLE_ANGRY_REVOLVER, + ACT_WALK_REVOLVER, + ACT_RUN_REVOLVER, + ACT_WALK_AIM_REVOLVER, + ACT_RUN_AIM_REVOLVER, + ACT_RANGE_ATTACK_REVOLVER, + ACT_RELOAD_REVOLVER, + ACT_RANGE_ATTACK_REVOLVER_LOW, + ACT_RELOAD_REVOLVER_LOW, + ACT_COVER_REVOLVER_LOW, + ACT_RANGE_AIM_REVOLVER_LOW, + ACT_GESTURE_RANGE_ATTACK_REVOLVER, + ACT_GESTURE_RELOAD_REVOLVER, + + // Crossbow + ACT_IDLE_CROSSBOW, + ACT_IDLE_ANGRY_CROSSBOW, + ACT_WALK_CROSSBOW, + ACT_RUN_CROSSBOW, + ACT_WALK_AIM_CROSSBOW, + ACT_RUN_AIM_CROSSBOW, + ACT_RANGE_ATTACK_CROSSBOW, + ACT_RELOAD_CROSSBOW, + ACT_RANGE_ATTACK_CROSSBOW_LOW, + ACT_RELOAD_CROSSBOW_LOW, + ACT_COVER_CROSSBOW_LOW, + ACT_RANGE_AIM_CROSSBOW_LOW, + ACT_GESTURE_RANGE_ATTACK_CROSSBOW, + ACT_GESTURE_RELOAD_CROSSBOW, + + ACT_IDLE_CROSSBOW_RELAXED, + ACT_IDLE_CROSSBOW_STIMULATED, + ACT_WALK_CROSSBOW_RELAXED, + ACT_RUN_CROSSBOW_RELAXED, + ACT_WALK_CROSSBOW_STIMULATED, + ACT_RUN_CROSSBOW_STIMULATED, + + ACT_IDLE_AIM_CROSSBOW_STIMULATED, + ACT_WALK_AIM_CROSSBOW_STIMULATED, + ACT_RUN_AIM_CROSSBOW_STIMULATED, + + // Pistol + ACT_IDLE_PISTOL_RELAXED, + ACT_IDLE_PISTOL_STIMULATED, + ACT_WALK_PISTOL_RELAXED, + ACT_WALK_PISTOL_STIMULATED, + ACT_RUN_PISTOL_RELAXED, + ACT_RUN_PISTOL_STIMULATED, + + ACT_IDLE_AIM_PISTOL_STIMULATED, + ACT_WALK_AIM_PISTOL_STIMULATED, + ACT_RUN_AIM_PISTOL_STIMULATED, + + ACT_WALK_CROUCH_PISTOL, + ACT_WALK_CROUCH_AIM_PISTOL, + ACT_RUN_CROUCH_PISTOL, + ACT_RUN_CROUCH_AIM_PISTOL, + + // Shotgun + ACT_IDLE_SHOTGUN, + ACT_WALK_SHOTGUN, + ACT_RUN_SHOTGUN, + + ACT_COVER_SHOTGUN_LOW, + ACT_RANGE_AIM_SHOTGUN_LOW, + + ACT_WALK_SHOTGUN_RELAXED, + ACT_WALK_SHOTGUN_STIMULATED, + ACT_RUN_SHOTGUN_RELAXED, + ACT_RUN_SHOTGUN_STIMULATED, + + ACT_IDLE_AIM_SHOTGUN_STIMULATED, + ACT_WALK_AIM_SHOTGUN_STIMULATED, + ACT_RUN_AIM_SHOTGUN_STIMULATED, + + // RPG + ACT_RANGE_AIM_RPG_LOW, + ACT_RANGE_ATTACK_RPG_LOW, + ACT_GESTURE_RANGE_ATTACK_RPG, + + // Annabelle + ACT_RANGE_ATTACK_ANNABELLE, + ACT_RANGE_ATTACK_ANNABELLE_LOW, + ACT_GESTURE_RANGE_ATTACK_ANNABELLE, + ACT_RELOAD_ANNABELLE, + ACT_RELOAD_ANNABELLE_LOW, + ACT_GESTURE_RELOAD_ANNABELLE, + + // Melee + ACT_WALK_MELEE, + ACT_RUN_MELEE, + + // Citizen accessories + ACT_RUN_PACKAGE, + ACT_RUN_SUITCASE, + + // Holster/Unholster ACT_ARM_RIFLE, + ACT_ARM_SHOTGUN, + ACT_ARM_RPG, + ACT_ARM_MELEE, ACT_DISARM_RIFLE, + ACT_DISARM_SHOTGUN, + ACT_DISARM_RPG, + ACT_DISARM_MELEE, +#endif + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + // AR1 + ACT_IDLE_AR1, + ACT_IDLE_ANGRY_AR1, + ACT_WALK_AR1, + ACT_RUN_AR1, + ACT_WALK_AIM_AR1, + ACT_RUN_AIM_AR1, + //ACT_RANGE_ATTACK_AR1, + ACT_RELOAD_AR1, + ACT_RANGE_ATTACK_AR1_LOW, + ACT_RELOAD_AR1_LOW, + ACT_COVER_AR1_LOW, + ACT_RANGE_AIM_AR1_LOW, + //ACT_GESTURE_RANGE_ATTACK_AR1, + ACT_GESTURE_RELOAD_AR1, + + ACT_IDLE_AR1_RELAXED, + ACT_IDLE_AR1_STIMULATED, + ACT_WALK_AR1_RELAXED, + ACT_RUN_AR1_RELAXED, + ACT_WALK_AR1_STIMULATED, + ACT_RUN_AR1_STIMULATED, + + ACT_IDLE_AIM_AR1_STIMULATED, + ACT_WALK_AIM_AR1_STIMULATED, + ACT_RUN_AIM_AR1_STIMULATED, + + // SMG2 + ACT_IDLE_SMG2, + ACT_IDLE_ANGRY_SMG2, + ACT_WALK_SMG2, + ACT_RUN_SMG2, + ACT_WALK_AIM_SMG2, + ACT_RUN_AIM_SMG2, + //ACT_RANGE_ATTACK_SMG2, + ACT_RELOAD_SMG2, + ACT_RANGE_ATTACK_SMG2_LOW, + ACT_RELOAD_SMG2_LOW, + ACT_COVER_SMG2_LOW, + ACT_RANGE_AIM_SMG2_LOW, + //ACT_GESTURE_RANGE_ATTACK_SMG2, + ACT_GESTURE_RELOAD_SMG2, + + ACT_IDLE_SMG2_RELAXED, + ACT_IDLE_SMG2_STIMULATED, + ACT_WALK_SMG2_RELAXED, + ACT_RUN_SMG2_RELAXED, + ACT_WALK_SMG2_STIMULATED, + ACT_RUN_SMG2_STIMULATED, + + ACT_IDLE_AIM_SMG2_STIMULATED, + ACT_WALK_AIM_SMG2_STIMULATED, + ACT_RUN_AIM_SMG2_STIMULATED, + + // Sniper Rifle + ACT_IDLE_SNIPER_RIFLE, + ACT_IDLE_ANGRY_SNIPER_RIFLE, + ACT_WALK_SNIPER_RIFLE, + ACT_RUN_SNIPER_RIFLE, + ACT_WALK_AIM_SNIPER_RIFLE, + ACT_RUN_AIM_SNIPER_RIFLE, + //ACT_RANGE_ATTACK_SNIPER_RIFLE, + ACT_RELOAD_SNIPER_RIFLE, + ACT_RANGE_ATTACK_SNIPER_RIFLE_LOW, + ACT_RELOAD_SNIPER_RIFLE_LOW, + ACT_COVER_SNIPER_RIFLE_LOW, + ACT_RANGE_AIM_SNIPER_RIFLE_LOW, + //ACT_GESTURE_RANGE_ATTACK_SNIPER_RIFLE, + ACT_GESTURE_RELOAD_SNIPER_RIFLE, + + ACT_IDLE_SNIPER_RIFLE_RELAXED, + ACT_IDLE_SNIPER_RIFLE_STIMULATED, + ACT_WALK_SNIPER_RIFLE_RELAXED, + ACT_RUN_SNIPER_RIFLE_RELAXED, + ACT_WALK_SNIPER_RIFLE_STIMULATED, + ACT_RUN_SNIPER_RIFLE_STIMULATED, + + ACT_IDLE_AIM_SNIPER_RIFLE_STIMULATED, + ACT_WALK_AIM_SNIPER_RIFLE_STIMULATED, + ACT_RUN_AIM_SNIPER_RIFLE_STIMULATED, +#endif + +#if EXPANDED_NAVIGATION_ACTIVITIES + ACT_CLIMB_ALL, // An actual blend animation which uses pose parameters for direction + ACT_CLIMB_IDLE, + + ACT_CLIMB_MOUNT_TOP, + ACT_CLIMB_MOUNT_BOTTOM, + ACT_CLIMB_DISMOUNT_BOTTOM, +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + // Crouch Cover Medium + ACT_RANGE_ATTACK1_MED, + ACT_RANGE_ATTACK2_MED, + ACT_RANGE_AIM_MED, + + ACT_RANGE_ATTACK_AR2_MED, + ACT_RANGE_ATTACK_SMG1_MED, + ACT_RANGE_ATTACK_SHOTGUN_MED, + ACT_RANGE_ATTACK_PISTOL_MED, + ACT_RANGE_ATTACK_RPG_MED, + ACT_RANGE_ATTACK_REVOLVER_MED, + ACT_RANGE_ATTACK_CROSSBOW_MED, + + ACT_RANGE_AIM_AR2_MED, + ACT_RANGE_AIM_SMG1_MED, + ACT_RANGE_AIM_SHOTGUN_MED, + ACT_RANGE_AIM_PISTOL_MED, + ACT_RANGE_AIM_RPG_MED, + ACT_RANGE_AIM_REVOLVER_MED, + ACT_RANGE_AIM_CROSSBOW_MED, + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + // MED activities for unused weapons + ACT_RANGE_AIM_AR1_MED, + ACT_RANGE_ATTACK_AR1_MED, + ACT_RANGE_AIM_SMG2_MED, + ACT_RANGE_ATTACK_SMG2_MED, + ACT_RANGE_AIM_SNIPER_RIFLE_MED, + ACT_RANGE_ATTACK_SNIPER_RIFLE_MED, +#endif + + // Wall Cover (for use in custom cover hints) + ACT_COVER_WALL_R, + ACT_COVER_WALL_L, + ACT_COVER_WALL_LOW_R, + ACT_COVER_WALL_LOW_L, + + ACT_COVER_WALL_R_RIFLE, + ACT_COVER_WALL_L_RIFLE, + ACT_COVER_WALL_LOW_R_RIFLE, + ACT_COVER_WALL_LOW_L_RIFLE, + + ACT_COVER_WALL_R_PISTOL, + ACT_COVER_WALL_L_PISTOL, + ACT_COVER_WALL_LOW_R_PISTOL, + ACT_COVER_WALL_LOW_L_PISTOL, +#endif + +#if EXPANDED_HL2DM_ACTIVITIES + ACT_HL2MP_WALK, + ACT_HL2MP_WALK_PISTOL, + ACT_HL2MP_WALK_SHOTGUN, + ACT_HL2MP_WALK_SMG1, + ACT_HL2MP_WALK_AR2, + ACT_HL2MP_WALK_PHYSGUN, + ACT_HL2MP_WALK_GRENADE, + ACT_HL2MP_WALK_RPG, + ACT_HL2MP_WALK_CROSSBOW, + ACT_HL2MP_WALK_MELEE, + ACT_HL2MP_WALK_SLAM, + + ACT_HL2MP_GESTURE_RANGE_ATTACK2, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_PISTOL, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_SHOTGUN, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG1, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR2, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_PHYSGUN, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_GRENADE, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_RPG, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_CROSSBOW, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_MELEE, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_SLAM, + + ACT_HL2MP_IDLE_REVOLVER, + ACT_HL2MP_RUN_REVOLVER, + ACT_HL2MP_WALK_REVOLVER, + ACT_HL2MP_IDLE_CROUCH_REVOLVER, + ACT_HL2MP_WALK_CROUCH_REVOLVER, + ACT_HL2MP_GESTURE_RANGE_ATTACK_REVOLVER, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_REVOLVER, + ACT_HL2MP_GESTURE_RELOAD_REVOLVER, + ACT_HL2MP_JUMP_REVOLVER, + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + // Player activities for unused weapons + ACT_HL2MP_IDLE_AR1, + ACT_HL2MP_RUN_AR1, + ACT_HL2MP_WALK_AR1, + ACT_HL2MP_IDLE_CROUCH_AR1, + ACT_HL2MP_WALK_CROUCH_AR1, + ACT_HL2MP_GESTURE_RANGE_ATTACK_AR1, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR1, + ACT_HL2MP_GESTURE_RELOAD_AR1, + ACT_HL2MP_JUMP_AR1, + + ACT_HL2MP_IDLE_SMG2, + ACT_HL2MP_RUN_SMG2, + ACT_HL2MP_WALK_SMG2, + ACT_HL2MP_IDLE_CROUCH_SMG2, + ACT_HL2MP_WALK_CROUCH_SMG2, + ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG2, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG2, + ACT_HL2MP_GESTURE_RELOAD_SMG2, + ACT_HL2MP_JUMP_SMG2, + + ACT_HL2MP_IDLE_SNIPER_RIFLE, + ACT_HL2MP_RUN_SNIPER_RIFLE, + ACT_HL2MP_WALK_SNIPER_RIFLE, + ACT_HL2MP_IDLE_CROUCH_SNIPER_RIFLE, + ACT_HL2MP_WALK_CROUCH_SNIPER_RIFLE, + ACT_HL2MP_GESTURE_RANGE_ATTACK_SNIPER_RIFLE, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_SNIPER_RIFLE, + ACT_HL2MP_GESTURE_RELOAD_SNIPER_RIFLE, + ACT_HL2MP_JUMP_SNIPER_RIFLE, +#endif + + ACT_HL2MP_IDLE_USE, + ACT_HL2MP_RUN_USE, + ACT_HL2MP_WALK_USE, + ACT_HL2MP_IDLE_CROUCH_USE, + ACT_HL2MP_WALK_CROUCH_USE, + ACT_HL2MP_JUMP_USE, + + ACT_HL2MP_IDLE_USE_HEAVY, + ACT_HL2MP_RUN_USE_HEAVY, + ACT_HL2MP_WALK_USE_HEAVY, + ACT_HL2MP_IDLE_CROUCH_USE_HEAVY, + ACT_HL2MP_WALK_CROUCH_USE_HEAVY, + ACT_HL2MP_JUMP_USE_HEAVY, #endif // this is the end of the global activities, private per-monster activities start here. diff --git a/sp/src/game/shared/ai_criteria_new.cpp b/sp/src/game/shared/ai_criteria_new.cpp new file mode 100644 index 0000000000..837c61aad7 --- /dev/null +++ b/sp/src/game/shared/ai_criteria_new.cpp @@ -0,0 +1,38 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// +#include "cbase.h" +#include "AI_Criteria.h" + +#ifdef GAME_DLL +#include "ai_speech.h" +#endif + +#include +#include "engine/IEngineSound.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include + + + +BEGIN_SIMPLE_DATADESC( AI_ResponseParams ) + DEFINE_FIELD( flags, FIELD_SHORT ), + DEFINE_FIELD( odds, FIELD_SHORT ), + DEFINE_FIELD( soundlevel, FIELD_CHARACTER ), + DEFINE_FIELD( delay, FIELD_INTEGER ), // These are compressed down to two float16s, so treat as an INT for saverestore + DEFINE_FIELD( respeakdelay, FIELD_INTEGER ), // +END_DATADESC() + +BEGIN_SIMPLE_DATADESC( AI_Response ) + DEFINE_FIELD( m_Type, FIELD_CHARACTER ), + DEFINE_ARRAY( m_szResponseName, FIELD_CHARACTER, AI_Response::MAX_RESPONSE_NAME ), + DEFINE_ARRAY( m_szMatchingRule, FIELD_CHARACTER, AI_Response::MAX_RULE_NAME ), + // DEFINE_FIELD( m_pCriteria, FIELD_??? ), // Don't need to save this probably + DEFINE_EMBEDDED( m_Params ), +END_DATADESC() + diff --git a/sp/src/game/shared/ai_criteria_new.h b/sp/src/game/shared/ai_criteria_new.h new file mode 100644 index 0000000000..b5d2c4fd67 --- /dev/null +++ b/sp/src/game/shared/ai_criteria_new.h @@ -0,0 +1,41 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef AI_CRITERIA_H +#define AI_CRITERIA_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tier1/utlrbtree.h" +#include "tier1/utlsymbol.h" +#include "tier1/interval.h" +#include "mathlib/compressed_vector.h" +#include "../../public/responserules/response_types.h" + + +using ResponseRules::ResponseType_t; + +extern const char *SplitContext( const char *raw, char *key, int keylen, char *value, int valuelen, float *duration, const char *entireContext ); + +#ifndef AI_CriteriaSet +#define AI_CriteriaSet ResponseRules::CriteriaSet +#endif + +typedef ResponseRules::ResponseParams AI_ResponseParams ; +typedef ResponseRules::CRR_Response AI_Response; + + + +/* +// An AI response that is dynamically new'ed up and returned from SpeakFindResponse. +class AI_ResponseReturnValue : AI_Response +{ + +}; +*/ + +#endif // AI_CRITERIA_H diff --git a/sp/src/game/shared/ai_responsesystem_new.cpp b/sp/src/game/shared/ai_responsesystem_new.cpp new file mode 100644 index 0000000000..9b519902f3 --- /dev/null +++ b/sp/src/game/shared/ai_responsesystem_new.cpp @@ -0,0 +1,1382 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "AI_ResponseSystem.h" +#include "igamesystem.h" +#include "AI_Criteria.h" +#include +#include "filesystem.h" +#include "utldict.h" +#ifdef GAME_DLL +#include "ai_speech.h" +#endif +#include "tier0/icommandline.h" +#include +#include "isaverestore.h" +#include "utlbuffer.h" +#include "stringpool.h" +#include "fmtstr.h" +#include "multiplay_gamerules.h" +#include "characterset.h" +#include "responserules/response_host_interface.h" +#include "../../responserules/runtime/response_types_internal.h" + +#include "scenefilecache/ISceneFileCache.h" + +#ifdef GAME_DLL +#include "sceneentity.h" +#endif + +#include "networkstringtabledefs.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#undef IResponseSystem +using namespace ResponseRules; + +extern ConVar rr_debugresponses; // ( "rr_debugresponses", "0", FCVAR_NONE, "Show verbose matching output (1 for simple, 2 for rule scoring, 3 for noisy). If set to 4, it will only show response success/failure for npc_selected NPCs." ); +extern ConVar rr_debugrule; // ( "rr_debugrule", "", FCVAR_NONE, "If set to the name of the rule, that rule's score will be shown whenever a concept is passed into the response rules system."); +extern ConVar rr_dumpresponses; // ( "rr_dumpresponses", "0", FCVAR_NONE, "Dump all response_rules.txt and rules (requires restart)" ); +extern ConVar rr_debugresponseconcept; // ( "rr_debugresponseconcept", "", FCVAR_NONE, "If set, rr_debugresponses will print only responses testing for the specified concept" ); + +static void CC_RR_DumpHashInfo( const CCommand &args ); + +#ifdef MAPBASE +ConVar rr_enhanced_saverestore( "rr_enhanced_saverestore", "0", FCVAR_NONE, "Enables enhanced save/restore capabilities for the Response System." ); +#endif + +extern ISceneFileCache *scenefilecache; +extern INetworkStringTable *g_pStringTableClientSideChoreoScenes; + +static characterset_t g_BreakSetIncludingColons; + +// Simple class to initialize breakset +class CBreakInit +{ +public: + CBreakInit() + { + CharacterSetBuild( &g_BreakSetIncludingColons, "{}()':" ); + } +} g_BreakInit; + +inline char rr_tolower( char c ) +{ + if ( c >= 'A' && c <= 'Z' ) + return c - 'A' + 'a'; + return c; +} +// BUG BUG: Note that this function doesn't check for data overruns!!! +// Also, this function lowercases the token as it parses!!! +inline const char *RR_Parse(const char *data, char *token ) +{ + unsigned char c; + int len; + characterset_t *breaks = &g_BreakSetIncludingColons; + len = 0; + token[0] = 0; + + if (!data) + return NULL; + + // skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + return NULL; // end of file; + data++; + } + + // skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + + + // handle quoted strings specially + if (c == '\"') + { + data++; + while (1) + { + c = rr_tolower( *data++ ); + if (c=='\"' || !c) + { + token[len] = 0; + return data; + } + token[len] = c; + len++; + } + } + + // parse single characters + if ( IN_CHARACTERSET( *breaks, c ) ) + { + token[len] = c; + len++; + token[len] = 0; + return data+1; + } + + // parse a regular word + do + { + token[len] = rr_tolower( c ); + data++; + len++; + c = rr_tolower( *data ); + if ( IN_CHARACTERSET( *breaks, c ) ) + break; + } while (c>32); + + token[len] = 0; + return data; +} + +#ifdef MAPBASE +// A version of the above which preserves casing and supports escaped quotes +inline const char *RR_Parse_Preserve(const char *data, char *token ) +{ + unsigned char c; + int len; + characterset_t *breaks = &g_BreakSetIncludingColons; + len = 0; + token[0] = 0; + + if (!data) + return NULL; + + // skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + return NULL; // end of file; + data++; + } + + // skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + + // handle quoted strings specially + if (c == '\"') + { + bool escaped = false; + data++; + while (1) + { + c = *data++; + if ((c=='\"' && !escaped) || !c) + { + token[len] = 0; + return data; + } + + escaped = (c == '\\'); + if (!escaped) + { + token[len] = c; + len++; + } + } + } + + // parse single characters + if ( IN_CHARACTERSET( *breaks, c ) ) + { + token[len] = c; + len++; + token[len] = 0; + return data+1; + } + + // parse a regular word + do + { + token[len] = c; + data++; + len++; + c = *data; + if ( IN_CHARACTERSET( *breaks, c ) ) + break; + } while (c>32); + + token[len] = 0; + return data; +} +#endif + +namespace ResponseRules +{ + extern const char *ResponseCopyString( const char *in ); +} + +// Host functions required by the ResponseRules::IEngineEmulator interface +class CResponseRulesToEngineInterface : public ResponseRules::IEngineEmulator +{ + /// Given an input text buffer data pointer, parses a single token into the variable token and returns the new + /// reading position + virtual const char *ParseFile( const char *data, char *token, int maxlen ) + { + NOTE_UNUSED( maxlen ); + return RR_Parse( data, token ); + } + +#ifdef MAPBASE + /// (Optional) Same as ParseFile, but with casing preserved and escaped quotes supported + virtual const char *ParseFilePreserve( const char *data, char *token, int maxlen ) + { + NOTE_UNUSED( maxlen ); + return RR_Parse_Preserve( data, token ); + } +#endif + + /// Return a pointer to an IFileSystem we can use to read and process scripts. + virtual IFileSystem *GetFilesystem() + { + return filesystem; + } + + /// Return a pointer to an instance of an IUniformRandomStream + virtual IUniformRandomStream *GetRandomStream() + { + return random; + } + + /// Return a pointer to a tier0 ICommandLine + virtual ICommandLine *GetCommandLine() + { + return CommandLine(); + } + + /// Emulates the server's UTIL_LoadFileForMe + virtual byte *LoadFileForMe( const char *filename, int *pLength ) + { + return UTIL_LoadFileForMe( filename, pLength ); + } + + /// Emulates the server's UTIL_FreeFile + virtual void FreeFile( byte *buffer ) + { + return UTIL_FreeFile( buffer ); + } + +}; + +CResponseRulesToEngineInterface g_ResponseRulesEngineWrapper; +IEngineEmulator *IEngineEmulator::s_pSingleton = &g_ResponseRulesEngineWrapper; + + +BEGIN_SIMPLE_DATADESC( ParserResponse ) + // DEFINE_FIELD( type, FIELD_INTEGER ), + // DEFINE_ARRAY( value, FIELD_CHARACTER ), + // DEFINE_FIELD( weight, FIELD_FLOAT ), + DEFINE_FIELD( depletioncount, FIELD_CHARACTER ), + // DEFINE_FIELD( first, FIELD_BOOLEAN ), + // DEFINE_FIELD( last, FIELD_BOOLEAN ), +END_DATADESC() + + +BEGIN_SIMPLE_DATADESC( ResponseGroup ) + // DEFINE_FIELD( group, FIELD_UTLVECTOR ), + // DEFINE_FIELD( rp, FIELD_EMBEDDED ), + // DEFINE_FIELD( m_bDepleteBeforeRepeat, FIELD_BOOLEAN ), + DEFINE_FIELD( m_nDepletionCount, FIELD_CHARACTER ), + // DEFINE_FIELD( m_bHasFirst, FIELD_BOOLEAN ), + // DEFINE_FIELD( m_bHasLast, FIELD_BOOLEAN ), + // DEFINE_FIELD( m_bSequential, FIELD_BOOLEAN ), + // DEFINE_FIELD( m_bNoRepeat, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ), + DEFINE_FIELD( m_nCurrentIndex, FIELD_CHARACTER ), +END_DATADESC() + + +/// Add some game-specific code to the basic response system +/// (eg, the scene precacher, which requires the client and server +/// to work) + +class CGameResponseSystem : public CResponseSystem +{ +public: + CGameResponseSystem(); + + virtual void Precache(); + virtual void PrecacheResponses( bool bEnable ) + { + m_bPrecache = bEnable; + } + bool ShouldPrecache() { return m_bPrecache; } + +protected: + bool m_bPrecache; +}; + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CGameResponseSystem::CGameResponseSystem() : m_bPrecache(true) +{}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + +#if 0 +class CScenePrecacheSystem : public CAutoGameSystem +{ +public: + CScenePrecacheSystem() : CAutoGameSystem( "CScenePrecacheSystem" ), m_RepeatCounts( 0, 0, DefLessFunc( int ) ) + { + } + + // Level init, shutdown + virtual void LevelShutdownPreEntity() + { + m_RepeatCounts.Purge(); + } + + bool ShouldPrecache( char const *pszScene ) + { + int hash = HashStringCaselessConventional( pszScene ); + + int slot = m_RepeatCounts.Find( hash ); + if ( slot != m_RepeatCounts.InvalidIndex() ) + { + m_RepeatCounts[ slot ]++; + return false; + } + + m_RepeatCounts.Insert( hash, 0 ); + return true; + } + +private: + + CUtlMap< int, int > m_RepeatCounts; +}; + +static CScenePrecacheSystem g_ScenePrecacheSystem; +//----------------------------------------------------------------------------- +// Purpose: Used for precaching instanced scenes +// Input : *pszScene - +//----------------------------------------------------------------------------- +void PrecacheInstancedScene( char const *pszScene ) +{ + static int nMakingReslists = -1; + + if ( !g_ScenePrecacheSystem.ShouldPrecache( pszScene ) ) + return; + + if ( nMakingReslists == -1 ) + { + nMakingReslists = CommandLine()->FindParm( "-makereslists" ) > 0 ? 1 : 0; + } + + if ( nMakingReslists == 1 ) + { + // Just stat the file to add to reslist + g_pFullFileSystem->Size( pszScene ); + } + + // verify existence, cache is pre-populated, should be there + SceneCachedData_t sceneData; + if ( !scenefilecache->GetSceneCachedData( pszScene, &sceneData ) ) + { + // Scenes are sloppy and don't always exist. + // A scene that is not in the pre-built cache image, but on disk, is a true error. + if ( IsX360() && ( g_pFullFileSystem->GetDVDMode() != DVDMODE_STRICT ) && g_pFullFileSystem->FileExists( pszScene, "GAME" ) ) + { + Warning( "PrecacheInstancedScene: Missing scene '%s' from scene image cache.\nRebuild scene image cache!\n", pszScene ); + } + } + else + { + for ( int i = 0; i < sceneData.numSounds; ++i ) + { + short stringId = scenefilecache->GetSceneCachedSound( sceneData.sceneId, i ); + CBaseEntity::PrecacheScriptSound( scenefilecache->GetSceneString( stringId ) ); + } + } + + g_pStringTableClientSideChoreoScenes->AddString( CBaseEntity::IsServer(), pszScene ); +} +#endif + +static void TouchFile( char const *pchFileName ) +{ + IEngineEmulator::Get()->GetFilesystem()->Size( pchFileName ); +} + +void CGameResponseSystem::Precache() +{ + bool bTouchFiles = CommandLine()->FindParm( "-makereslists" ) != 0; + + // enumerate and mark all the scripts so we know they're referenced + for ( int i = 0; i < (int)m_Responses.Count(); i++ ) + { + ResponseGroup &group = m_Responses[i]; + + for ( int j = 0; j < group.group.Count(); j++) + { + ParserResponse &response = group.group[j]; + + switch ( response.type ) + { + default: + break; + case RESPONSE_SCENE: + { + // fixup $gender references + char file[_MAX_PATH]; + Q_strncpy( file, response.value, sizeof(file) ); + char *gender = strstr( file, "$gender" ); + if ( gender ) + { + // replace with male & female + const char *postGender = gender + strlen("$gender"); + *gender = 0; + char genderFile[_MAX_PATH]; + // male + Q_snprintf( genderFile, sizeof(genderFile), "%smale%s", file, postGender); + + PrecacheInstancedScene( genderFile ); + if ( bTouchFiles ) + { + TouchFile( genderFile ); + } + + Q_snprintf( genderFile, sizeof(genderFile), "%sfemale%s", file, postGender); + + PrecacheInstancedScene( genderFile ); + if ( bTouchFiles ) + { + TouchFile( genderFile ); + } + } + else + { + PrecacheInstancedScene( file ); + if ( bTouchFiles ) + { + TouchFile( file ); + } + } + } + break; + case RESPONSE_SPEAK: + { + CBaseEntity::PrecacheScriptSound( response.value ); + } + break; + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: A special purpose response system associated with a custom entity +//----------------------------------------------------------------------------- +class CInstancedResponseSystem : public CGameResponseSystem +{ + typedef CGameResponseSystem BaseClass; + +public: + CInstancedResponseSystem( const char *scriptfile ) : + m_pszScriptFile( 0 ) + { + Assert( scriptfile ); + + int len = Q_strlen( scriptfile ) + 1; + m_pszScriptFile = new char[ len ]; + Assert( m_pszScriptFile ); + Q_strncpy( m_pszScriptFile, scriptfile, len ); + } + + ~CInstancedResponseSystem() + { + delete[] m_pszScriptFile; + } + virtual const char *GetScriptFile( void ) + { + Assert( m_pszScriptFile ); + return m_pszScriptFile; + } + + // CAutoGameSystem + virtual bool Init() + { + const char *basescript = GetScriptFile(); + LoadRuleSet( basescript ); + return true; + } + + virtual void LevelInitPostEntity() + { +#ifdef MAPBASE + if (!rr_enhanced_saverestore.GetBool() || gpGlobals->eLoadType != MapLoad_Transition) +#endif + ResetResponseGroups(); + } + + virtual void Release() + { + Clear(); + delete this; + } +private: + + char *m_pszScriptFile; +}; + +//----------------------------------------------------------------------------- +// Purpose: The default response system for expressive AIs +//----------------------------------------------------------------------------- +class CDefaultResponseSystem : public CGameResponseSystem, public CAutoGameSystem +{ + typedef CAutoGameSystem BaseClass; + +public: + CDefaultResponseSystem() : CAutoGameSystem( "CDefaultResponseSystem" ) + { + } + + virtual const char *GetScriptFile( void ) + { + return "scripts/talker/response_rules.txt"; + } + + // CAutoServerSystem + virtual bool Init(); + virtual void Shutdown(); + + virtual void LevelInitPostEntity() + { +#ifdef MAPBASE + // CInstancedResponseSystem is not a CAutoGameSystem, so this needs to be called manually. + // The same could've been accomplished by making CInstancedResponseSystem derive from CAutoGameSystem, + // but their instanced nature would've complicated things a lot. + int c = m_InstancedSystems.Count(); + for ( int i = c - 1 ; i >= 0; i-- ) + { + m_InstancedSystems[i]->LevelInitPostEntity(); + } +#endif + } + + virtual void Release() + { + Assert( 0 ); + } + + void AddInstancedResponseSystem( const char *scriptfile, CInstancedResponseSystem *sys ) + { + m_InstancedSystems.Insert( scriptfile, sys ); + } + + CInstancedResponseSystem *FindResponseSystem( const char *scriptfile ) + { + int idx = m_InstancedSystems.Find( scriptfile ); + if ( idx == m_InstancedSystems.InvalidIndex() ) + return NULL; + return m_InstancedSystems[ idx ]; + } + + IResponseSystem *PrecacheCustomResponseSystem( const char *scriptfile ) + { + COM_TimestampedLog( "PrecacheCustomResponseSystem %s - Start", scriptfile ); + CInstancedResponseSystem *sys = ( CInstancedResponseSystem * )FindResponseSystem( scriptfile ); + if ( !sys ) + { + sys = new CInstancedResponseSystem( scriptfile ); + if ( !sys ) + { + Error( "Failed to load response system data from %s", scriptfile ); + } + + if ( !sys->Init() ) + { + Error( "CInstancedResponseSystem: Failed to init response system from %s!", scriptfile ); + } + + AddInstancedResponseSystem( scriptfile, sys ); + } + + sys->Precache(); + + COM_TimestampedLog( "PrecacheCustomResponseSystem %s - Finish", scriptfile ); + + return ( IResponseSystem * )sys; + } + + IResponseSystem *BuildCustomResponseSystemGivenCriteria( const char *pszBaseFile, const char *pszCustomName, AI_CriteriaSet &criteriaSet, float flCriteriaScore ); + void DestroyCustomResponseSystems(); + + virtual void LevelInitPreEntity() + { + // This will precache the default system + // All user installed systems are init'd by PrecacheCustomResponseSystem which will call sys->Precache() on the ones being used + + // FIXME: This is SLOW the first time you run the engine (can take 3 - 10 seconds!!!) + if ( ShouldPrecache() ) + { + Precache(); + } + +#ifdef MAPBASE + if (!rr_enhanced_saverestore.GetBool() || gpGlobals->eLoadType != MapLoad_Transition) +#endif + ResetResponseGroups(); + } + + void ReloadAllResponseSystems() + { + Clear(); + Init(); + + int c = m_InstancedSystems.Count(); + for ( int i = c - 1 ; i >= 0; i-- ) + { + CInstancedResponseSystem *sys = m_InstancedSystems[ i ]; + if ( !IsCustomManagable() ) + { + sys->Clear(); + sys->Init(); + } + else + { + // Custom reponse rules will manage/reload themselves - remove them. + m_InstancedSystems.RemoveAt( i ); + } + } + + // precache sounds in case we added new ones + Precache(); + + } + +private: + + void ClearInstanced() + { + int c = m_InstancedSystems.Count(); + for ( int i = c - 1 ; i >= 0; i-- ) + { + CInstancedResponseSystem *sys = m_InstancedSystems[ i ]; + sys->Release(); + } + m_InstancedSystems.RemoveAll(); + } + + CUtlDict< CInstancedResponseSystem *, int > m_InstancedSystems; + friend void CC_RR_DumpHashInfo( const CCommand &args ); +}; + +IResponseSystem *CDefaultResponseSystem::BuildCustomResponseSystemGivenCriteria( const char *pszBaseFile, const char *pszCustomName, AI_CriteriaSet &criteriaSet, float flCriteriaScore ) +{ + // Create a instanced response system. + CInstancedResponseSystem *pCustomSystem = new CInstancedResponseSystem( pszCustomName ); + if ( !pCustomSystem ) + { + Error( "BuildCustomResponseSystemGivenCriterea: Failed to create custom response system %s!", pszCustomName ); + } + + pCustomSystem->Clear(); + + // Copy the relevant rules and data. + /* + int nRuleCount = m_Rules.Count(); + for ( int iRule = 0; iRule < nRuleCount; ++iRule ) + */ + for ( ResponseRulePartition::tIndex iIdx = m_RulePartitions.First() ; + m_RulePartitions.IsValid(iIdx) ; + iIdx = m_RulePartitions.Next( iIdx ) ) + { + Rule *pRule = &m_RulePartitions[iIdx]; + if ( pRule ) + { + float flScore = 0.0f; + + int nCriteriaCount = pRule->m_Criteria.Count(); + for ( int iCriteria = 0; iCriteria < nCriteriaCount; ++iCriteria ) + { + int iRuleCriteria = pRule->m_Criteria[iCriteria]; + + flScore += LookForCriteria( criteriaSet, iRuleCriteria ); + if ( flScore >= flCriteriaScore ) + { + CopyRuleFrom( pRule, iIdx, pCustomSystem ); + break; + } + } + } + } + + // Set as a custom response system. + m_bCustomManagable = true; + AddInstancedResponseSystem( pszCustomName, pCustomSystem ); + + // pCustomSystem->DumpDictionary( pszCustomName ); + + return pCustomSystem; +} + +void CDefaultResponseSystem::DestroyCustomResponseSystems() +{ + ClearInstanced(); +} + + +static CDefaultResponseSystem defaultresponsesytem; +IResponseSystem *g_pResponseSystem = &defaultresponsesytem; + +CON_COMMAND( rr_reloadresponsesystems, "Reload all response system scripts." ) +{ +#ifdef GAME_DLL + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; +#endif + + defaultresponsesytem.ReloadAllResponseSystems(); +} + +#if RR_DUMPHASHINFO_ENABLED +static void CC_RR_DumpHashInfo( const CCommand &args ) +{ + defaultresponsesytem.m_RulePartitions.PrintBucketInfo( &defaultresponsesytem ); +} +static ConCommand rr_dumphashinfo( "rr_dumphashinfo", CC_RR_DumpHashInfo, "Statistics on primary hash bucketing of response rule partitions"); +#endif + +#ifdef MAPBASE +// Designed for extern magic, this gives the <, >, etc. of response system criteria to the outside world. +// Mostly just used for Matcher_Match in matchers.h. +bool ResponseSystemCompare( const char *criterion, const char *value ) +{ + Criteria criteria; + criteria.value = criterion; + defaultresponsesytem.ComputeMatcher( &criteria, criteria.matcher ); + return defaultresponsesytem.CompareUsingMatcher( value, criteria.matcher, true ); + + return false; +} + +//----------------------------------------------------------------------------- +// CResponseFilePrecacher +// +// Purpose: Precaches a single talker file. That's it. +// +// It copies from a bunch of the original Response System class and therefore it's really messy. +// Despite the horrors a normal programmer might find in here, I think it performs better than anything else I could've come up with. +//----------------------------------------------------------------------------- +/* +class CResponseFilePrecacher +{ +public: + + // Stuff copied from the Response System. + // Direct copy-pastes are very compact, to say the least. + inline bool ParseToken( void ) + { + if ( m_bUnget ) + { m_bUnget = false; return true; } + if ( m_ScriptStack.Count() <= 0 ) + { return false; } + + m_ScriptStack[ 0 ].currenttoken = engine->ParseFile( m_ScriptStack[ 0 ].currenttoken, token, sizeof( token ) ); + m_ScriptStack[ 0 ].tokencount++; + return m_ScriptStack[ 0 ].currenttoken != NULL ? true : false; + } + + CUtlVector< CResponseSystem::ScriptEntry > m_ScriptStack; + bool m_bUnget; + char token[ 1204 ]; + + + void PrecacheResponse( const char *response, byte type ) + { + switch ( type ) + { + default: + break; + case RESPONSE_SCENE: + { + DevMsg("Precaching scene %s...\n", response); + + // fixup $gender references + char file[_MAX_PATH]; + Q_strncpy( file, response, sizeof(file) ); + char *gender = strstr( file, "$gender" ); + if ( gender ) + { + // replace with male & female + const char *postGender = gender + strlen("$gender"); + *gender = 0; + char genderFile[_MAX_PATH]; + + Q_snprintf( genderFile, sizeof(genderFile), "%smale%s", file, postGender); + PrecacheInstancedScene( genderFile ); + + Q_snprintf( genderFile, sizeof(genderFile), "%sfemale%s", file, postGender); + PrecacheInstancedScene( genderFile ); + } + else + { + PrecacheInstancedScene( file ); + } + } + break; + case RESPONSE_SPEAK: + { + DevMsg("Precaching sound %s...\n", response); + CBaseEntity::PrecacheScriptSound( response ); + } + break; + } + } + + bool IsRootCommand() + { + if (!Q_stricmp( token, "#include" ) || !Q_stricmp( token, "response" ) + || !Q_stricmp( token, "enumeration" ) || !Q_stricmp( token, "criteria" ) + || !Q_stricmp( token, "criterion" ) || !Q_stricmp( token, "rule" )) + return true; + return false; + } + + void ParseResponse( void ) + { + // Must go to response group name + ParseToken(); + + while ( 1 ) + { + ParseToken(); + + if ( !Q_stricmp( token, "{" ) ) + { + while ( 1 ) + { + ParseToken(); + if ( !Q_stricmp( token, "}" ) ) + break; + + byte type = ComputeResponseType( token ); + if (type == RESPONSE_NONE) + continue; + + ParseToken(); + char *value = CopyString( token ); + + PrecacheResponse(value, type); + } + break; + } + + byte type = ComputeResponseType( token ); + if (type == RESPONSE_NONE) + break; + + ParseToken(); + char *value = CopyString( token ); + + PrecacheResponse(value, type); + + break; + } + } + + bool LoadFromBuffer(const char *scriptfile, unsigned char *buffer, CStringPool &includedFiles) + { + includedFiles.Allocate( scriptfile ); + + CResponseSystem::ScriptEntry e; + e.name = filesystem->FindOrAddFileName( scriptfile ); + e.buffer = buffer; + e.currenttoken = (char *)e.buffer; + e.tokencount = 0; + m_ScriptStack.AddToHead( e ); + + while ( 1 ) + { + ParseToken(); + if ( !token[0] ) + { + break; + } + + if ( !Q_stricmp( token, "response" ) ) + { + ParseResponse(); + } + else if ( !Q_stricmp( token, "#include" ) || !Q_stricmp( token, "#base" ) ) + { + // Compacted version of ParseInclude(), including new changes. + // Look at that if you want to read. + char includefile[ 256 ]; + ParseToken(); + if (scriptfile) { size_t len = strlen(scriptfile)-1; + for (size_t i = 0; i < len; i++) + { if (scriptfile[i] == CORRECT_PATH_SEPARATOR || scriptfile[i] == INCORRECT_PATH_SEPARATOR) + { len = i; } + } Q_strncpy(includefile, scriptfile, len+1); + if (len+1 != strlen(scriptfile)) + { Q_snprintf(includefile, sizeof(includefile), "%s/%s", includefile, token); } + else includefile[0] = '\0'; + } if (!includefile[0]) Q_snprintf( includefile, sizeof( includefile ), "scripts/%s", token ); + + if ( includedFiles.Find( includefile ) == NULL ) + { + MEM_ALLOC_CREDIT(); + + // Try and load it + CUtlBuffer buf; + if ( filesystem->ReadFile( includefile, "GAME", buf ) ) + { + LoadFromBuffer( includefile, (unsigned char *)buf.PeekGet(), includedFiles ); + } + } + } + } + + if ( m_ScriptStack.Count() > 0 ) + m_ScriptStack.Remove( 0 ); + + return true; + } +}; +*/ + +// Loads a file directly to the main response system +bool LoadResponseSystemFile(const char *scriptfile) +{ + CUtlBuffer buf; + if ( !filesystem->ReadFile( scriptfile, "GAME", buf ) ) + { + return false; + } + + // This is a really messy and specialized system that precaches the responses and only the responses of a talker file. + /* + CStringPool includedFiles; + CResponseFilePrecacher *rs = new CResponseFilePrecacher(); + if (!rs || !rs->LoadFromBuffer(scriptfile, (unsigned char *)buf.PeekGet(), includedFiles)) + { + Warning( "Failed to load response system data from %s", scriptfile ); + delete rs; + return false; + } + delete rs; + */ + + // HACKHACK: This is not very efficient + /* + CInstancedResponseSystem *tempSys = new CInstancedResponseSystem( scriptfile ); + if ( tempSys && tempSys->Init() ) + { + tempSys->Precache(); + + for ( ResponseRulePartition::tIndex idx = tempSys->m_RulePartitions.First() ; + tempSys->m_RulePartitions.IsValid(idx) ; + idx = tempSys->m_RulePartitions.Next(idx) ) + { + Rule &rule = tempSys->m_RulePartitions[idx]; + tempSys->CopyRuleFrom( &rule, idx, &defaultresponsesytem ); + } + + tempSys->Release(); + } + */ + + // HACKHACK: This is even less efficient + defaultresponsesytem.LoadFromBuffer( scriptfile, (const char *)buf.PeekGet() ); + defaultresponsesytem.Precache(); + + return true; +} + +// Called from Mapbase manifests to flush +void ReloadResponseSystem() +{ + defaultresponsesytem.ReloadAllResponseSystems(); +} +#endif + +static short RESPONSESYSTEM_SAVE_RESTORE_VERSION = 1; + +// note: this won't save/restore settings from instanced response systems. Could add that with a CDefSaveRestoreOps implementation if needed +// +class CDefaultResponseSystemSaveRestoreBlockHandler : public CDefSaveRestoreBlockHandler +{ +public: + const char *GetBlockName() + { + return "ResponseSystem"; + } + + void WriteSaveHeaders( ISave *pSave ) + { + pSave->WriteShort( &RESPONSESYSTEM_SAVE_RESTORE_VERSION ); + } + + void ReadRestoreHeaders( IRestore *pRestore ) + { + // No reason why any future version shouldn't try to retain backward compatability. The default here is to not do so. + short version; + pRestore->ReadShort( &version ); + m_fDoLoad = ( version == RESPONSESYSTEM_SAVE_RESTORE_VERSION ); + } + + void Save( ISave *pSave ) + { + CDefaultResponseSystem& rs = defaultresponsesytem; + + int count = rs.m_Responses.Count(); + pSave->WriteInt( &count ); + for ( int i = 0; i < count; ++i ) + { + pSave->StartBlock( "ResponseGroup" ); + + pSave->WriteString( rs.m_Responses.GetElementName( i ) ); + const ResponseGroup *group = &rs.m_Responses[ i ]; + pSave->WriteAll( group ); + + short groupCount = group->group.Count(); + pSave->WriteShort( &groupCount ); + for ( int j = 0; j < groupCount; ++j ) + { + const ParserResponse *response = &group->group[ j ]; + pSave->StartBlock( "Response" ); + pSave->WriteString( response->value ); + pSave->WriteAll( response ); + pSave->EndBlock(); + } + + pSave->EndBlock(); + } + +#ifdef MAPBASE + // Enhanced Response System save/restore + int count2 = 0; + if (rr_enhanced_saverestore.GetBool()) + { + // Rule state save/load + count2 = rs.m_RulePartitions.Count(); + pSave->WriteInt( &count2 ); + for ( ResponseRulePartition::tIndex idx = rs.m_RulePartitions.First() ; + rs.m_RulePartitions.IsValid(idx) ; + idx = rs.m_RulePartitions.Next(idx) ) + { + pSave->StartBlock( "Rule" ); + + pSave->WriteString( rs.m_RulePartitions.GetElementName( idx ) ); + const Rule &rule = rs.m_RulePartitions[ idx ]; + + bool bEnabled = rule.m_bEnabled; + pSave->WriteBool( &bEnabled ); + + pSave->EndBlock(); + } + } + else + { + // Indicate this isn't using enhanced save/restore + pSave->WriteInt( &count2 ); + } +#endif + } + + void Restore( IRestore *pRestore, bool createPlayers ) + { + if ( !m_fDoLoad ) + return; + + CDefaultResponseSystem& rs = defaultresponsesytem; + + int count = pRestore->ReadInt(); + for ( int i = 0; i < count; ++i ) + { + char szResponseGroupBlockName[SIZE_BLOCK_NAME_BUF]; + pRestore->StartBlock( szResponseGroupBlockName ); + if ( !Q_stricmp( szResponseGroupBlockName, "ResponseGroup" ) ) + { + + char groupname[ 256 ]; + pRestore->ReadString( groupname, sizeof( groupname ), 0 ); + + // Try and find it + int idx = rs.m_Responses.Find( groupname ); + if ( idx != rs.m_Responses.InvalidIndex() ) + { + ResponseGroup *group = &rs.m_Responses[ idx ]; + pRestore->ReadAll( group ); + + short groupCount = pRestore->ReadShort(); + for ( int j = 0; j < groupCount; ++j ) + { + char szResponseBlockName[SIZE_BLOCK_NAME_BUF]; + + char responsename[ 256 ]; + pRestore->StartBlock( szResponseBlockName ); + if ( !Q_stricmp( szResponseBlockName, "Response" ) ) + { + pRestore->ReadString( responsename, sizeof( responsename ), 0 ); + + // Find it by name + int ri; + for ( ri = 0; ri < group->group.Count(); ++ri ) + { + ParserResponse *response = &group->group[ ri ]; + if ( !Q_stricmp( response->value, responsename ) ) + { + break; + } + } + + if ( ri < group->group.Count() ) + { + ParserResponse *response = &group->group[ ri ]; + pRestore->ReadAll( response ); + } + } + + pRestore->EndBlock(); + } + } + } + + pRestore->EndBlock(); + } + +#ifdef MAPBASE + // Enhanced Response System save/restore + count = pRestore->ReadInt(); + for ( int i = 0; i < count; ++i ) + { + char szRuleBlockName[SIZE_BLOCK_NAME_BUF]; + pRestore->StartBlock( szRuleBlockName ); + if ( !Q_stricmp( szRuleBlockName, "Rule" ) ) + { + char groupname[ 256 ]; + pRestore->ReadString( groupname, sizeof( groupname ), 0 ); + + // Try and find it + Rule *rule = rs.m_RulePartitions.FindByName( groupname ); + if ( rule ) + { + bool bEnabled; + pRestore->ReadBool( &bEnabled ); + rule->m_bEnabled = bEnabled; + } + else + { + Warning("Warning: Can't find rule %s\n", groupname); + } + } + + pRestore->EndBlock(); + } +#endif + } +private: + + bool m_fDoLoad; + +} g_DefaultResponseSystemSaveRestoreBlockHandler; + +ISaveRestoreBlockHandler *GetDefaultResponseSystemSaveRestoreBlockHandler() +{ + return &g_DefaultResponseSystemSaveRestoreBlockHandler; +} + +//----------------------------------------------------------------------------- +// CResponseSystemSaveRestoreOps +// +// Purpose: Handles save and load for instanced response systems... +// +// BUGBUG: This will save the same response system to file multiple times for "shared" response systems and +// therefore it'll restore the same data onto the same pointer N times on reload (probably benign for now, but we could +// write code to save/restore the instanced ones by filename in the block handler above maybe? +//----------------------------------------------------------------------------- + +class CResponseSystemSaveRestoreOps : public CDefSaveRestoreOps +{ +public: + + virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave ) + { + CResponseSystem *pRS = *(CResponseSystem **)fieldInfo.pField; + if ( !pRS || pRS == &defaultresponsesytem ) + return; + + int count = pRS->m_Responses.Count(); + pSave->WriteInt( &count ); + for ( int i = 0; i < count; ++i ) + { + pSave->StartBlock( "ResponseGroup" ); + + pSave->WriteString( pRS->m_Responses.GetElementName( i ) ); + const ResponseGroup *group = &pRS->m_Responses[ i ]; + pSave->WriteAll( group ); + + short groupCount = group->group.Count(); + pSave->WriteShort( &groupCount ); + for ( int j = 0; j < groupCount; ++j ) + { + const ParserResponse *response = &group->group[ j ]; + pSave->StartBlock( "Response" ); + pSave->WriteString( response->value ); + pSave->WriteAll( response ); + pSave->EndBlock(); + } + + pSave->EndBlock(); + } + } + + virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore ) + { + CResponseSystem *pRS = *(CResponseSystem **)fieldInfo.pField; + if ( !pRS || pRS == &defaultresponsesytem ) + return; + + int count = pRestore->ReadInt(); + for ( int i = 0; i < count; ++i ) + { + char szResponseGroupBlockName[SIZE_BLOCK_NAME_BUF]; + pRestore->StartBlock( szResponseGroupBlockName ); + if ( !Q_stricmp( szResponseGroupBlockName, "ResponseGroup" ) ) + { + + char groupname[ 256 ]; + pRestore->ReadString( groupname, sizeof( groupname ), 0 ); + + // Try and find it + int idx = pRS->m_Responses.Find( groupname ); + if ( idx != pRS->m_Responses.InvalidIndex() ) + { + ResponseGroup *group = &pRS->m_Responses[ idx ]; + pRestore->ReadAll( group ); + + short groupCount = pRestore->ReadShort(); + for ( int j = 0; j < groupCount; ++j ) + { + char szResponseBlockName[SIZE_BLOCK_NAME_BUF]; + + char responsename[ 256 ]; + pRestore->StartBlock( szResponseBlockName ); + if ( !Q_stricmp( szResponseBlockName, "Response" ) ) + { + pRestore->ReadString( responsename, sizeof( responsename ), 0 ); + + // Find it by name + int ri; + for ( ri = 0; ri < group->group.Count(); ++ri ) + { + ParserResponse *response = &group->group[ ri ]; + if ( !Q_stricmp( response->value, responsename ) ) + { + break; + } + } + + if ( ri < group->group.Count() ) + { + ParserResponse *response = &group->group[ ri ]; + pRestore->ReadAll( response ); + } + } + + pRestore->EndBlock(); + } + } + } + + pRestore->EndBlock(); + } + } + +} g_ResponseSystemSaveRestoreOps; + +ISaveRestoreOps *responseSystemSaveRestoreOps = &g_ResponseSystemSaveRestoreOps; + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CDefaultResponseSystem::Init() +{ + /* + Warning( "sizeof( Response ) == %d\n", sizeof( Response ) ); + Warning( "sizeof( ResponseGroup ) == %d\n", sizeof( ResponseGroup ) ); + Warning( "sizeof( Criteria ) == %d\n", sizeof( Criteria ) ); + Warning( "sizeof( AI_ResponseParams ) == %d\n", sizeof( AI_ResponseParams ) ); + */ + const char *basescript = GetScriptFile(); + + LoadRuleSet( basescript ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDefaultResponseSystem::Shutdown() +{ + // Wipe instanced versions + ClearInstanced(); + + // Clear outselves + Clear(); + // IServerSystem chain + BaseClass::Shutdown(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Instance a custom response system +// Input : *scriptfile - +// Output : IResponseSystem +//----------------------------------------------------------------------------- +IResponseSystem *PrecacheCustomResponseSystem( const char *scriptfile ) +{ + return defaultresponsesytem.PrecacheCustomResponseSystem( scriptfile ); +} + +//----------------------------------------------------------------------------- +// Purpose: Instance a custom response system +// Input : *scriptfile - +// set - +// Output : IResponseSystem +//----------------------------------------------------------------------------- +IResponseSystem *BuildCustomResponseSystemGivenCriteria( const char *pszBaseFile, const char *pszCustomName, AI_CriteriaSet &criteriaSet, float flCriteriaScore ) +{ + return defaultresponsesytem.BuildCustomResponseSystemGivenCriteria( pszBaseFile, pszCustomName, criteriaSet, flCriteriaScore ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void DestroyCustomResponseSystems() +{ + defaultresponsesytem.DestroyCustomResponseSystems(); +} diff --git a/sp/src/game/shared/ai_responsesystem_new.h b/sp/src/game/shared/ai_responsesystem_new.h new file mode 100644 index 0000000000..9d2fff6b0c --- /dev/null +++ b/sp/src/game/shared/ai_responsesystem_new.h @@ -0,0 +1,29 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef AI_RESPONSESYSTEM_H +#define AI_RESPONSESYSTEM_H + +#include "utlvector.h" + +#ifdef _WIN32 +#pragma once +#endif + +#include "AI_Criteria.h" +#include "../../public/responserules/response_types.h" + +// using ResponseRules::IResponseFilter; +// using ResponseRules::IResponseSystem; + +ResponseRules::IResponseSystem *PrecacheCustomResponseSystem( const char *scriptfile ); +ResponseRules::IResponseSystem *BuildCustomResponseSystemGivenCriteria( const char *pszBaseFile, const char *pszCustomName, AI_CriteriaSet &criteriaSet, float flCriteriaScore ); +void DestroyCustomResponseSystems(); + +class ISaveRestoreBlockHandler *GetDefaultResponseSystemSaveRestoreBlockHandler(); +class ISaveRestoreOps *GetResponseSystemSaveRestoreOps(); + +#endif // AI_RESPONSESYSTEM_H diff --git a/sp/src/game/shared/ai_speechconcept.cpp b/sp/src/game/shared/ai_speechconcept.cpp new file mode 100644 index 0000000000..c0ae8e36b0 --- /dev/null +++ b/sp/src/game/shared/ai_speechconcept.cpp @@ -0,0 +1,28 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "ai_speechconcept.h" + +#ifdef GAME_DLL +#include "game.h" +#include "ai_basenpc.h" +#include "sceneentity.h" +#endif + +#include "engine/ienginesound.h" +#include "keyvalues.h" +#include "ai_criteria.h" +#include "isaverestore.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include + + +// empty \ No newline at end of file diff --git a/sp/src/game/shared/ai_speechconcept.h b/sp/src/game/shared/ai_speechconcept.h new file mode 100644 index 0000000000..3e375a0ad0 --- /dev/null +++ b/sp/src/game/shared/ai_speechconcept.h @@ -0,0 +1,45 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Class data for an AI Concept, an atom of response-driven dialog. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef AI_SPEECHCONCEPT_H +#define AI_SPEECHCONCEPT_H + +#if defined( _WIN32 ) +#pragma once +#endif + +#include "responserules/response_types.h" + +class CAI_Concept : public ResponseRules::CRR_Concept +{ +public: + CAI_Concept() {}; + // construct concept from a string. + CAI_Concept(const char *fromString) : CRR_Concept(fromString) {} ; + + // get/set BS + inline EHANDLE GetSpeaker() const { return m_hSpeaker; } + inline void SetSpeaker(EHANDLE val) { m_hSpeaker = val; } + + /* + inline EHANDLE GetTarget() const { return m_hTarget; } + inline void SetTarget(EHANDLE val) { m_hTarget = val; } + inline EHANDLE GetTopic() const { return m_hTopic; } + inline void SetTopic(EHANDLE val) { m_hTopic = val; } + */ + +protected: + EHANDLE m_hSpeaker; + + /* + EHANDLE m_hTarget; + EHANDLE m_hTopic; + */ +}; + + +#endif diff --git a/sp/src/game/shared/base_playeranimstate.cpp b/sp/src/game/shared/base_playeranimstate.cpp index d90655ac53..82cb75aae2 100644 --- a/sp/src/game/shared/base_playeranimstate.cpp +++ b/sp/src/game/shared/base_playeranimstate.cpp @@ -539,7 +539,26 @@ bool CBasePlayerAnimState::CanThePlayerMove() void CBasePlayerAnimState::ComputePlaybackRate() { VPROF( "CBasePlayerAnimState::ComputePlaybackRate" ); +#ifdef MAPBASE + if ( m_AnimConfig.m_LegAnimType == LEGANIM_9WAY ) + { + // If the movement would be greater than the pose range, set playback rate anyway + if ( abs(m_vLastMovePose.x) > 1.0f || abs(m_vLastMovePose.y) > 1.0f ) + { + bool bIsMoving; + float flRate = CalcMovementPlaybackRate( &bIsMoving ); + if ( bIsMoving ) + GetOuter()->SetPlaybackRate( flRate ); + else + GetOuter()->SetPlaybackRate( 1 ); + } + else + GetOuter()->SetPlaybackRate( 1 ); + } + else // Allow LEGANIM_8WAY to change playback rate +#else if ( m_AnimConfig.m_LegAnimType != LEGANIM_9WAY && m_AnimConfig.m_LegAnimType != LEGANIM_8WAY ) +#endif { // When using a 9-way blend, playback rate is always 1 and we just scale the pose params // to speed up or slow down the animation. diff --git a/sp/src/game/shared/basecombatweapon_shared.cpp b/sp/src/game/shared/basecombatweapon_shared.cpp index 9f3b9be452..22fe8b977c 100644 --- a/sp/src/game/shared/basecombatweapon_shared.cpp +++ b/sp/src/game/shared/basecombatweapon_shared.cpp @@ -186,7 +186,11 @@ void CBaseCombatWeapon::Spawn( void ) if ( GetWorldModel() ) { +#ifdef MAPBASE + SetModel( (GetDroppedModel() && GetDroppedModel()[0]) ? GetDroppedModel() : GetWorldModel() ); +#else SetModel( GetWorldModel() ); +#endif } #if !defined( CLIENT_DLL ) @@ -291,6 +295,18 @@ void CBaseCombatWeapon::Precache( void ) { m_iWorldModelIndex = CBaseEntity::PrecacheModel( GetWorldModel() ); } +#ifdef MAPBASE + m_iDroppedModelIndex = 0; + if ( GetDroppedModel() && GetDroppedModel()[0] ) + { + m_iDroppedModelIndex = CBaseEntity::PrecacheModel( GetDroppedModel() ); + } + else + { + // Use the world model index + m_iDroppedModelIndex = m_iWorldModelIndex; + } +#endif // Precache sounds, too for ( int i = 0; i < NUM_SHOOT_SOUND_TYPES; ++i ) @@ -461,6 +477,38 @@ bool CBaseCombatWeapon::IsMeleeWeapon() const return GetWpnData().m_bMeleeWeapon; } +#ifdef MAPBASE +float CBaseCombatWeapon::GetViewmodelFOVOverride() const +{ + return GetWpnData().m_flViewmodelFOV; +} + +float CBaseCombatWeapon::GetBobScale() const +{ + return GetWpnData().m_flBobScale; +} + +float CBaseCombatWeapon::GetSwayScale() const +{ + return GetWpnData().m_flSwayScale; +} + +float CBaseCombatWeapon::GetSwaySpeedScale() const +{ + return GetWpnData().m_flSwaySpeedScale; +} + +const char *CBaseCombatWeapon::GetDroppedModel() const +{ + return GetWpnData().szDroppedModel; +} + +bool CBaseCombatWeapon::UsesHands() const +{ + return GetWpnData().m_bUsesHands; +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -1046,7 +1094,18 @@ WeaponClass_t CBaseCombatWeapon::WeaponClassify() Activity idleact = ActivityOverride(ACT_IDLE_ANGRY, NULL); switch (idleact) { +#if EXPANDED_HL2_WEAPON_ACTIVITIES + case ACT_IDLE_ANGRY_REVOLVER: +#endif case ACT_IDLE_ANGRY_PISTOL: return WEPCLASS_HANDGUN; +#if EXPANDED_HL2_WEAPON_ACTIVITIES + case ACT_IDLE_ANGRY_CROSSBOW: // For now, crossbows are rifles +#endif +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + case ACT_IDLE_ANGRY_AR1: + case ACT_IDLE_ANGRY_SMG2: + case ACT_IDLE_ANGRY_SNIPER_RIFLE: +#endif case ACT_IDLE_ANGRY_SMG1: case ACT_IDLE_ANGRY_AR2: return WEPCLASS_RIFLE; case ACT_IDLE_ANGRY_SHOTGUN: return WEPCLASS_SHOTGUN; @@ -1077,6 +1136,23 @@ WeaponClass_t CBaseCombatWeapon::WeaponClassFromString(const char *str) return WEPCLASS_INVALID; } +#ifdef HL2_DLL +extern acttable_t *GetSMG1Acttable(); +extern int GetSMG1ActtableCount(); + +extern acttable_t *GetAR2Acttable(); +extern int GetAR2ActtableCount(); + +extern acttable_t *GetShotgunActtable(); +extern int GetShotgunActtableCount(); + +extern acttable_t *GetPistolActtable(); +extern int GetPistolActtableCount(); + +extern acttable_t *Get357Acttable(); +extern int Get357ActtableCount(); +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -1084,12 +1160,81 @@ bool CBaseCombatWeapon::SupportsBackupActivity(Activity activity) { // Derived classes should override this. - // Pistol and melee users should not use SMG animations for missing pistol activities. - if (WeaponClassify() == WEPCLASS_HANDGUN || IsMeleeWeapon()) +#ifdef HL2_DLL + // Melee users should not use SMG animations for missing activities. + if (IsMeleeWeapon() && GetBackupActivityList() == GetSMG1Acttable()) return false; +#endif return true; } + +acttable_t *CBaseCombatWeapon::GetBackupActivityList() +{ + return NULL; +} + +int CBaseCombatWeapon::GetBackupActivityListCount() +{ + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +acttable_t *CBaseCombatWeapon::GetDefaultBackupActivityList( acttable_t *pTable, int &actCount ) +{ +#ifdef HL2_DLL + // Ensure this isn't already a default backup activity list + if (pTable == GetSMG1Acttable() || pTable == GetPistolActtable()) + return NULL; + + // Use a backup table based on what ACT_IDLE_ANGRY is translated to + Activity actTranslated = ACT_INVALID; + for ( int i = 0; i < actCount; i++, pTable++ ) + { + if ( pTable->baseAct == ACT_IDLE_ANGRY ) + { + actTranslated = (Activity)pTable->weaponAct; + break; + } + } + + if (actTranslated == ACT_INVALID) + return NULL; + + switch (actTranslated) + { +#if EXPANDED_HL2_WEAPON_ACTIVITIES + case ACT_IDLE_ANGRY_REVOLVER: +#endif + case ACT_IDLE_ANGRY_PISTOL: + { + actCount = GetPistolActtableCount(); + return GetPistolActtable(); + } +#if EXPANDED_HL2_WEAPON_ACTIVITIES + case ACT_IDLE_ANGRY_CROSSBOW: // For now, crossbows are rifles +#endif +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + case ACT_IDLE_ANGRY_AR1: + case ACT_IDLE_ANGRY_SMG2: + case ACT_IDLE_ANGRY_SNIPER_RIFLE: +#endif + case ACT_IDLE_ANGRY_SMG1: + case ACT_IDLE_ANGRY_AR2: + case ACT_IDLE_ANGRY_SHOTGUN: + case ACT_IDLE_ANGRY_RPG: + { + actCount = GetSMG1ActtableCount(); + return GetSMG1Acttable(); + } + } +#endif + + actCount = 0; + return NULL; +} #endif @@ -1550,6 +1695,10 @@ bool CBaseCombatWeapon::DefaultDeploy( char *szViewModel, char *szWeaponModel, i SetViewModel(); SendWeaponAnim( iActivity ); + +#ifdef MAPBASE + pOwner->SetAnimation( PLAYER_UNHOLSTER ); +#endif pOwner->SetNextAttack( gpGlobals->curtime + SequenceDuration() ); } @@ -1621,6 +1770,11 @@ bool CBaseCombatWeapon::Holster( CBaseCombatWeapon *pSwitchingTo ) if (pOwner) { pOwner->SetNextAttack( gpGlobals->curtime + flSequenceDuration ); + +#ifdef MAPBASE + if (IsWeaponVisible() && pOwner->IsPlayer()) + static_cast(pOwner)->SetAnimation( PLAYER_HOLSTER ); +#endif } // If we don't have a holster anim, hide immediately to avoid timing issues @@ -2339,6 +2493,28 @@ bool CBaseCombatWeapon::Reload( void ) return DefaultReload( GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCombatWeapon::Reload_NPC( bool bPlaySound ) +{ + if (bPlaySound) + WeaponSound( RELOAD_NPC ); + + if (UsesClipsForAmmo1()) + { + m_iClip1 = GetMaxClip1(); + } + else + { + // For weapons which don't use clips, give the owner ammo. + if (GetOwner()) + GetOwner()->SetAmmoCount( GetDefaultClip1(), m_iPrimaryAmmoType ); + } +} +#endif + //========================================================= void CBaseCombatWeapon::WeaponIdle( void ) { @@ -2924,6 +3100,7 @@ BEGIN_ENT_SCRIPTDESC( CBaseCombatWeapon, CBaseAnimating, "The base class for all DEFINE_SCRIPTFUNC( GetWorldModel, "Get the weapon's world model." ) DEFINE_SCRIPTFUNC( GetViewModel, "Get the weapon's view model." ) + DEFINE_SCRIPTFUNC( GetDroppedModel, "Get the weapon's unique dropped model if it has one." ) DEFINE_SCRIPTFUNC( GetWeight, "Get the weapon's weight." ) @@ -3218,6 +3395,9 @@ BEGIN_NETWORK_TABLE(CBaseCombatWeapon, DT_BaseCombatWeapon) SendPropDataTable("LocalActiveWeaponData", 0, &REFERENCE_SEND_TABLE(DT_LocalActiveWeaponData), SendProxy_SendActiveLocalWeaponDataTable ), SendPropModelIndex( SENDINFO(m_iViewModelIndex) ), SendPropModelIndex( SENDINFO(m_iWorldModelIndex) ), +#ifdef MAPBASE + SendPropModelIndex( SENDINFO(m_iDroppedModelIndex) ), +#endif SendPropInt( SENDINFO(m_iState ), 8, SPROP_UNSIGNED ), SendPropEHandle( SENDINFO(m_hOwner) ), @@ -3230,6 +3410,9 @@ BEGIN_NETWORK_TABLE(CBaseCombatWeapon, DT_BaseCombatWeapon) RecvPropDataTable("LocalActiveWeaponData", 0, 0, &REFERENCE_RECV_TABLE(DT_LocalActiveWeaponData)), RecvPropInt( RECVINFO(m_iViewModelIndex)), RecvPropInt( RECVINFO(m_iWorldModelIndex)), +#ifdef MAPBASE + RecvPropInt( RECVINFO(m_iDroppedModelIndex) ), +#endif RecvPropInt( RECVINFO(m_iState )), RecvPropEHandle( RECVINFO(m_hOwner ) ), diff --git a/sp/src/game/shared/basecombatweapon_shared.h b/sp/src/game/shared/basecombatweapon_shared.h index 2d19727df5..bd439b8c3f 100644 --- a/sp/src/game/shared/basecombatweapon_shared.h +++ b/sp/src/game/shared/basecombatweapon_shared.h @@ -230,6 +230,9 @@ class CBaseCombatWeapon : public BASECOMBATWEAPON_DERIVED_FROM static WeaponClass_t WeaponClassFromString(const char *str); virtual bool SupportsBackupActivity(Activity activity); + virtual acttable_t *GetBackupActivityList(); + virtual int GetBackupActivityListCount(); + static acttable_t *GetDefaultBackupActivityList( acttable_t *pTable, int &actCount ); #endif virtual void Equip( CBaseCombatCharacter *pOwner ); @@ -319,7 +322,7 @@ class CBaseCombatWeapon : public BASECOMBATWEAPON_DERIVED_FROM bool ReloadsSingly( void ) const; #ifdef MAPBASE // Originally created for the crossbow, can be used to add special NPC reloading behavior - virtual void Reload_NPC( void ) { WeaponSound(RELOAD_NPC); m_iClip1 = GetMaxClip1(); } + virtual void Reload_NPC( bool bPlaySound = true ); #endif virtual bool AutoFiresFullClip( void ) { return false; } @@ -415,6 +418,14 @@ class CBaseCombatWeapon : public BASECOMBATWEAPON_DERIVED_FROM virtual bool UsesClipsForAmmo1( void ) const; virtual bool UsesClipsForAmmo2( void ) const; bool IsMeleeWeapon() const; +#ifdef MAPBASE + float GetViewmodelFOVOverride() const; + float GetBobScale() const; + float GetSwayScale() const; + float GetSwaySpeedScale() const; + virtual const char *GetDroppedModel( void ) const; + bool UsesHands( void ) const; +#endif // derive this function if you mod uses encrypted weapon info files virtual const unsigned char *GetEncryptionKey( void ); @@ -673,6 +684,9 @@ class CBaseCombatWeapon : public BASECOMBATWEAPON_DERIVED_FROM // Weapon art CNetworkVar( int, m_iViewModelIndex ); CNetworkVar( int, m_iWorldModelIndex ); +#ifdef MAPBASE + CNetworkVar( int, m_iDroppedModelIndex ); +#endif // Sounds float m_flNextEmptySoundTime; // delay on empty sound playing diff --git a/sp/src/game/shared/baseentity_shared.cpp b/sp/src/game/shared/baseentity_shared.cpp index cae3f5d92f..cd3854ddc4 100644 --- a/sp/src/game/shared/baseentity_shared.cpp +++ b/sp/src/game/shared/baseentity_shared.cpp @@ -2393,8 +2393,67 @@ void CBaseEntity::ModifyEmitSoundParams( EmitSound_t ¶ms ) params.m_pSoundName = GameRules()->TranslateEffectForVisionFilter( "sounds", params.m_pSoundName ); } #endif + +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_ModifyEmitSoundParams.CanRunInScope( m_ScriptScope )) + { + HSCRIPT hParams = g_pScriptVM->RegisterInstance( reinterpret_cast(¶ms) ); + + // params + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { ScriptVariant_t( hParams ) }; + g_Hook_ModifyEmitSoundParams.Call( m_ScriptScope, &functionReturn, args ); + + g_pScriptVM->RemoveInstance( hParams ); + } +#endif } +#if defined(MAPBASE) && defined(GAME_DLL) +void CBaseEntity::ModifySentenceParams( int &iSentenceIndex, int &iChannel, float &flVolume, soundlevel_t &iSoundlevel, int &iFlags, int &iPitch, + const Vector **pOrigin, const Vector **pDirection, bool &bUpdatePositions, float &soundtime, int &iSpecialDSP, int &iSpeakerIndex ) +{ +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_ModifySentenceParams.CanRunInScope( m_ScriptScope )) + { + // This is a bit of a hack, but for consistency with ModifyEmitSoundParams, put them into an EmitSound_t params + ScriptEmitSound_t params; + params.m_pSoundName = engine->SentenceNameFromIndex( iSentenceIndex ); + params.m_nChannel = iChannel; + params.m_flVolume = flVolume; + params.m_SoundLevel = iSoundlevel; + params.m_nFlags = iFlags; + params.m_nPitch = iPitch; + params.m_pOrigin = *pOrigin; + params.m_flSoundTime = soundtime; + params.m_nSpecialDSP = iSpecialDSP; + params.m_nSpeakerEntity = iSpeakerIndex; + + HSCRIPT hParams = g_pScriptVM->RegisterInstance( ¶ms ); + + // params + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { ScriptVariant_t( hParams ) }; + if (g_Hook_ModifySentenceParams.Call( m_ScriptScope, &functionReturn, args )) + { + iSentenceIndex = engine->SentenceIndexFromName( params.m_pSoundName ); + iChannel = params.m_nChannel; + flVolume = params.m_flVolume; + iSoundlevel = params.m_SoundLevel; + iFlags = params.m_nFlags; + iPitch = params.m_nPitch; + *pOrigin = params.m_pOrigin; + soundtime = params.m_flSoundTime; + iSpecialDSP = params.m_nSpecialDSP; + iSpeakerIndex = params.m_nSpeakerEntity; + } + + g_pScriptVM->RemoveInstance( hParams ); + } +#endif +} +#endif + //----------------------------------------------------------------------------- // These methods encapsulate MOVETYPE_FOLLOW, which became obsolete //----------------------------------------------------------------------------- @@ -2713,13 +2772,20 @@ HSCRIPT CBaseEntity::ScriptGetPhysicsObject( void ) return NULL; } + +#ifdef GAME_DLL +#define SCRIPT_NEVER_THINK TICK_NEVER_THINK +#else +#define SCRIPT_NEVER_THINK CLIENT_THINK_NEVER +#endif + static inline void ScriptStopContextThink( scriptthinkfunc_t *context ) { Assert( context->m_hfnThink ); + Assert( context->m_flNextThink == SCRIPT_NEVER_THINK ); g_pScriptVM->ReleaseScript( context->m_hfnThink ); context->m_hfnThink = NULL; - //context->m_nNextThinkTick = TICK_NEVER_THINK; } //----------------------------------------------------------------------------- @@ -2728,55 +2794,49 @@ static inline void ScriptStopContextThink( scriptthinkfunc_t *context ) void CBaseEntity::ScriptContextThink() { float flNextThink = FLT_MAX; - int nScheduledTick = 0; + float flScheduled = 0.0f; + + ScriptVariant_t arg = m_hScriptInstance; for ( int i = 0; i < m_ScriptThinkFuncs.Count(); ++i ) { scriptthinkfunc_t *cur = m_ScriptThinkFuncs[i]; - if ( cur->m_nNextThinkTick == TICK_NEVER_THINK ) + if ( cur->m_flNextThink == SCRIPT_NEVER_THINK ) { continue; } - if ( cur->m_nNextThinkTick > gpGlobals->tickcount ) + if ( cur->m_flNextThink > gpGlobals->curtime ) { - // There is more to execute, don't stop thinking if the rest are done. - - // Find the shortest schedule - if ( !nScheduledTick || nScheduledTick > cur->m_nNextThinkTick ) + if ( ( flScheduled == 0.0f ) || ( flScheduled > cur->m_flNextThink ) ) { - nScheduledTick = cur->m_nNextThinkTick; + flScheduled = cur->m_flNextThink; } continue; } #ifdef _DEBUG // going to run the script func - cur->m_nNextThinkTick = 0; + cur->m_flNextThink = 0; #endif ScriptVariant_t varReturn; +#ifndef CLIENT_DLL if ( !cur->m_bNoParam ) { - ScriptVariant_t arg = m_hScriptInstance; - if ( g_pScriptVM->ExecuteFunction( cur->m_hfnThink, &arg, 1, &varReturn, NULL, true ) == SCRIPT_ERROR ) - { - cur->m_nNextThinkTick = TICK_NEVER_THINK; - continue; - } +#endif + g_pScriptVM->ExecuteFunction( cur->m_hfnThink, &arg, 1, &varReturn, NULL, true ); +#ifndef CLIENT_DLL } else { - if ( g_pScriptVM->ExecuteFunction( cur->m_hfnThink, NULL, 0, &varReturn, NULL, true ) == SCRIPT_ERROR ) - { - cur->m_nNextThinkTick = TICK_NEVER_THINK; - continue; - } + g_pScriptVM->ExecuteFunction( cur->m_hfnThink, NULL, 0, &varReturn, NULL, true ); } +#endif - if ( cur->m_nNextThinkTick == TICK_NEVER_THINK ) + if ( cur->m_flNextThink == SCRIPT_NEVER_THINK ) { // stopped from script while thinking continue; @@ -2785,13 +2845,14 @@ void CBaseEntity::ScriptContextThink() float flReturn; if ( !varReturn.AssignTo( &flReturn ) ) { - cur->m_nNextThinkTick = TICK_NEVER_THINK; + varReturn.Free(); + cur->m_flNextThink = SCRIPT_NEVER_THINK; continue; } if ( flReturn < 0.0f ) { - cur->m_nNextThinkTick = TICK_NEVER_THINK; + cur->m_flNextThink = SCRIPT_NEVER_THINK; continue; } @@ -2800,95 +2861,60 @@ void CBaseEntity::ScriptContextThink() flNextThink = flReturn; } - cur->m_nNextThinkTick = TIME_TO_TICKS( gpGlobals->curtime + flReturn ); + cur->m_flNextThink = gpGlobals->curtime + flReturn - 0.001f; } // deferred safe removal for ( int i = 0; i < m_ScriptThinkFuncs.Count(); ) { - if ( m_ScriptThinkFuncs[i]->m_nNextThinkTick == TICK_NEVER_THINK ) + scriptthinkfunc_t *cur = m_ScriptThinkFuncs[i]; + if ( cur->m_flNextThink == SCRIPT_NEVER_THINK ) { - ScriptStopContextThink( m_ScriptThinkFuncs[i] ); - delete m_ScriptThinkFuncs[i]; + ScriptStopContextThink( cur ); + delete cur; m_ScriptThinkFuncs.Remove(i); } else ++i; } - bool bNewNext = flNextThink < FLT_MAX; - -#ifdef _DEBUG -#ifdef GAME_DLL - int nNextThinkTick = GetNextThinkTick("ScriptContextThink"); // -1 -#else - int nNextThinkTick = GetNextThinkTick(); // 0 -#endif - if ( ( nNextThinkTick <= 0 ) || ( nNextThinkTick >= nScheduledTick ) || ( nNextThinkTick == gpGlobals->tickcount ) ) + if ( flNextThink < FLT_MAX ) { -#endif - if ( nScheduledTick ) + if ( flScheduled > 0.0f ) { - float flScheduledTime = TICKS_TO_TIME( nScheduledTick ); - - if ( bNewNext ) - { - flNextThink = min( gpGlobals->curtime + flNextThink, flScheduledTime ); - } - else - { - flNextThink = flScheduledTime; - } + flNextThink = min( gpGlobals->curtime + flNextThink, flScheduled ); } else { - if ( bNewNext ) - { - flNextThink = gpGlobals->curtime + flNextThink; - } - else - { -#ifdef GAME_DLL - flNextThink = TICK_NEVER_THINK; -#else - flNextThink = CLIENT_THINK_NEVER; -#endif - } + flNextThink = gpGlobals->curtime + flNextThink; } -#ifdef _DEBUG } else { - // Next think was set (from script) to a sooner tick while thinking? - Assert(0); - - if ( nScheduledTick ) + if ( flScheduled > 0.0f ) { - int nNextSchedule = min( nScheduledTick, nNextThinkTick ); - float flNextSchedule = TICKS_TO_TIME( nNextSchedule ); - - if ( bNewNext ) - { - flNextThink = min( gpGlobals->curtime + flNextThink, flNextSchedule ); - } - else - { - flNextThink = flNextSchedule; - } + flNextThink = flScheduled; } else { - float nextthink = TICKS_TO_TIME( nNextThinkTick ); - - if ( bNewNext ) - { - flNextThink = min( gpGlobals->curtime + flNextThink, nextthink ); - } - else - { - flNextThink = nextthink; - } + flNextThink = SCRIPT_NEVER_THINK; } } + +#ifdef _DEBUG +#ifdef GAME_DLL + int nNextThinkTick = GetNextThinkTick("ScriptContextThink"); + float flNextThinkTime = TICKS_TO_TIME(nNextThinkTick); + + // If internal next think tick is earlier than what we have here with flNextThink, + // whoever set that think may fail. In worst case scenario the entity may stop thinking. + if ( nNextThinkTick > gpGlobals->tickcount ) + { + if ( flNextThink == SCRIPT_NEVER_THINK ) + Assert(0); + if ( flNextThinkTime < flNextThink ) + Assert(0); + } +#endif #endif #ifdef GAME_DLL @@ -2898,8 +2924,10 @@ void CBaseEntity::ScriptContextThink() #endif } +#ifndef CLIENT_DLL // see ScriptSetThink static bool s_bScriptContextThinkNoParam = false; +#endif //----------------------------------------------------------------------------- // @@ -2917,7 +2945,7 @@ void CBaseEntity::ScriptSetContextThink( const char* szContext, HSCRIPT hFunc, f #endif scriptthinkfunc_t *pf = NULL; - unsigned short hash = ( szContext && *szContext ) ? HashString( szContext ) : 0; + unsigned hash = szContext ? HashString( szContext ) : 0; FOR_EACH_VEC( m_ScriptThinkFuncs, i ) { @@ -2939,14 +2967,16 @@ void CBaseEntity::ScriptSetContextThink( const char* szContext, HSCRIPT hFunc, f m_ScriptThinkFuncs.SetGrowSize(1); m_ScriptThinkFuncs.AddToTail( pf ); - pf->m_bNoParam = s_bScriptContextThinkNoParam; pf->m_iContextHash = hash; +#ifndef CLIENT_DLL + pf->m_bNoParam = s_bScriptContextThinkNoParam; +#endif } // update existing else { #ifdef _DEBUG - if ( pf->m_nNextThinkTick == 0 ) + if ( pf->m_flNextThink == 0 ) { Warning("Script think ('%s') was changed while it was thinking!\n", szContext); } @@ -2957,31 +2987,29 @@ void CBaseEntity::ScriptSetContextThink( const char* szContext, HSCRIPT hFunc, f float nextthink = gpGlobals->curtime + flTime; pf->m_hfnThink = hFunc; - pf->m_nNextThinkTick = TIME_TO_TICKS( nextthink ); + pf->m_flNextThink = nextthink; #ifdef GAME_DLL int nexttick = GetNextThinkTick( RegisterThinkContext( "ScriptContextThink" ) ); -#else - int nexttick = GetNextThinkTick(); -#endif - - // sooner than next think - if ( nexttick <= 0 || nexttick > pf->m_nNextThinkTick ) + if ( nexttick <= 0 || TICKS_TO_TIME(nexttick) > nextthink ) { -#ifdef GAME_DLL SetContextThink( &CBaseEntity::ScriptContextThink, nextthink, "ScriptContextThink" ); + } #else - SetNextClientThink( nextthink ); -#endif + { + // let it self adjust + SetNextClientThink( gpGlobals->curtime ); } +#endif } // null func input, think exists else if ( pf ) { - pf->m_nNextThinkTick = TICK_NEVER_THINK; + pf->m_flNextThink = SCRIPT_NEVER_THINK; } } +#ifndef CLIENT_DLL //----------------------------------------------------------------------------- // m_bNoParam and s_bScriptContextThinkNoParam exist only to keep backwards compatibility // and are an alternative to this script closure: @@ -2991,7 +3019,6 @@ void CBaseEntity::ScriptSetContextThink( const char* szContext, HSCRIPT hFunc, f // SetContextThink( "", function(_){ return func() }, time ) // } //----------------------------------------------------------------------------- -#ifndef CLIENT_DLL void CBaseEntity::ScriptSetThink( HSCRIPT hFunc, float time ) { s_bScriptContextThinkNoParam = true; diff --git a/sp/src/game/shared/baseviewmodel_shared.cpp b/sp/src/game/shared/baseviewmodel_shared.cpp index b7fef9b773..aa8ddbae1d 100644 --- a/sp/src/game/shared/baseviewmodel_shared.cpp +++ b/sp/src/game/shared/baseviewmodel_shared.cpp @@ -289,6 +289,19 @@ void CBaseViewModel::AddEffects( int nEffects ) SetControlPanelsActive( false ); } +#ifdef MAPBASE + if (GetOwningWeapon() && GetOwningWeapon()->UsesHands()) + { + // If using hands, apply effect changes to any viewmodel children as well + // (fixes hand models) + for (CBaseEntity *pChild = FirstMoveChild(); pChild != NULL; pChild = pChild->NextMovePeer()) + { + if (pChild->GetClassname()[0] == 'h') + pChild->AddEffects( nEffects ); + } + } +#endif + BaseClass::AddEffects( nEffects ); } @@ -302,6 +315,19 @@ void CBaseViewModel::RemoveEffects( int nEffects ) SetControlPanelsActive( true ); } +#ifdef MAPBASE + if (GetOwningWeapon() && GetOwningWeapon()->UsesHands()) + { + // If using hands, apply effect changes to any viewmodel children as well + // (fixes hand models) + for (CBaseEntity *pChild = FirstMoveChild(); pChild != NULL; pChild = pChild->NextMovePeer()) + { + if (pChild->GetClassname()[0] == 'h') + pChild->RemoveEffects( nEffects ); + } + } +#endif + BaseClass::RemoveEffects( nEffects ); } @@ -339,6 +365,18 @@ void CBaseViewModel::SetWeaponModel( const char *modelname, CBaseCombatWeapon *w SetControlPanelsActive( showControlPanels ); } #endif + +#ifdef MAPBASE + // If our owning weapon doesn't support hands, disable the hands viewmodel(s) + bool bSupportsHands = weapon->UsesHands(); + for (CBaseEntity *pChild = FirstMoveChild(); pChild != NULL; pChild = pChild->NextMovePeer()) + { + if (pChild->GetClassname()[0] == 'h') + { + bSupportsHands ? pChild->RemoveEffects( EF_NODRAW ) : pChild->AddEffects( EF_NODRAW ); + } + } +#endif } //----------------------------------------------------------------------------- @@ -424,6 +462,21 @@ void CBaseViewModel::CalcViewModelView( CBasePlayer *owner, const Vector& eyePos g_ClientVirtualReality.OverrideViewModelTransform( vmorigin, vmangles, pWeapon && pWeapon->ShouldUseLargeViewModelVROverride() ); } +#ifdef MAPBASE + // Flip the view if we should be flipping + if (ShouldFlipViewModel()) + { + Vector vecOriginDiff = (eyePosition - vmorigin); + QAngle angAnglesDiff = (eyeAngles - vmangles); + + vmorigin.x = (eyePosition.x + vecOriginDiff.x); + vmorigin.y = (eyePosition.y + vecOriginDiff.y); + + vmangles.y = (eyeAngles.y + angAnglesDiff.y); + vmangles.z = (eyeAngles.z + angAnglesDiff.z); + } +#endif + SetLocalOrigin( vmorigin ); SetLocalAngles( vmangles ); @@ -478,6 +531,23 @@ void CBaseViewModel::CalcViewModelLag( Vector& origin, QAngle& angles, QAngle& o float flSpeed = 5.0f; +#ifdef MAPBASE + CBaseCombatWeapon *pWeapon = m_hWeapon.Get(); + if (pWeapon) + { + const FileWeaponInfo_t *pInfo = &pWeapon->GetWpnData(); + if (pInfo->m_flSwayScale != 1.0f) + { + vDifference *= pInfo->m_flSwayScale; + pInfo->m_flSwayScale != 0.0f ? flSpeed /= pInfo->m_flSwayScale : flSpeed = 0.0f; + } + if (pInfo->m_flSwaySpeedScale != 1.0f) + { + flSpeed *= pInfo->m_flSwaySpeedScale; + } + } +#endif + // If we start to lag too far behind, we'll increase the "catch up" speed. Solves the problem with fast cl_yawspeed, m_yaw or joysticks // rotating quickly. The old code would slam lastfacing with origin causing the viewmodel to pop to a new position float flDiff = vDifference.Length(); @@ -702,7 +772,13 @@ class CHandViewModel : public CBaseViewModel DECLARE_CLASS( CHandViewModel, CBaseViewModel ); public: DECLARE_NETWORKCLASS(); + + CBaseViewModel *GetVMOwner(); + + CBaseCombatWeapon *GetOwningWeapon( void ); + private: + CHandle m_hVMOwner; }; LINK_ENTITY_TO_CLASS(hand_viewmodel, CHandViewModel); @@ -718,4 +794,26 @@ BEGIN_NETWORK_TABLE(CHandViewModel, DT_HandViewModel) RecvPropInt(RECVINFO_NAME(m_hNetworkMoveParent, moveparent), 0, RecvProxy_IntToMoveParent), #endif END_NETWORK_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseViewModel *CHandViewModel::GetVMOwner() +{ + if (!m_hVMOwner) + m_hVMOwner = assert_cast(GetMoveParent()); + return m_hVMOwner; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseCombatWeapon *CHandViewModel::GetOwningWeapon() +{ + CBaseViewModel *pVM = GetVMOwner(); + if (pVM) + return pVM->GetOwningWeapon(); + else + return NULL; +} #endif diff --git a/sp/src/game/shared/beam_shared.cpp b/sp/src/game/shared/beam_shared.cpp index c3e39af82b..fcefa3f965 100644 --- a/sp/src/game/shared/beam_shared.cpp +++ b/sp/src/game/shared/beam_shared.cpp @@ -42,6 +42,9 @@ class CInfoTarget : public CPointEntity DECLARE_CLASS( CInfoTarget, CPointEntity ); void Spawn( void ); +#ifdef MAPBASE + virtual int UpdateTransmitState(); +#endif }; //info targets are like point entities except you can force them to spawn on the client @@ -55,6 +58,19 @@ void CInfoTarget::Spawn( void ) } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Always transmitted to clients +//----------------------------------------------------------------------------- +int CInfoTarget::UpdateTransmitState() +{ + // Spawn flags 2 means we always transmit + if ( HasSpawnFlags(0x02) ) + return SetTransmitState( FL_EDICT_ALWAYS ); + return BaseClass::UpdateTransmitState(); +} +#endif + LINK_ENTITY_TO_CLASS( info_target, CInfoTarget ); #endif diff --git a/sp/src/game/shared/gamemovement.cpp b/sp/src/game/shared/gamemovement.cpp index d77d0c8926..46afe4f238 100644 --- a/sp/src/game/shared/gamemovement.cpp +++ b/sp/src/game/shared/gamemovement.cpp @@ -3907,13 +3907,11 @@ void CGameMovement::CheckFalling( void ) return; #ifdef MAPBASE -#ifdef GAME_DLL // Let's hope we could work without transmitting to the client... if ( player->m_bInTriggerFall ) { - // This lets the fall damage functions do their magic without having to change them. + // This value lets the existing fall damage functions ensure a fatal fall. player->m_Local.m_flFallVelocity += (PLAYER_FATAL_FALL_SPEED + PLAYER_LAND_ON_FLOATING_OBJECT); } -#endif #endif if ( !IsDead() && player->m_Local.m_flFallVelocity >= PLAYER_FALL_PUNCH_THRESHOLD ) @@ -3921,7 +3919,11 @@ void CGameMovement::CheckFalling( void ) bool bAlive = true; float fvol = 0.5; +#ifdef MAPBASE + if ( player->GetWaterLevel() > 0 && !player->m_bInTriggerFall ) +#else if ( player->GetWaterLevel() > 0 ) +#endif { // They landed in water. } diff --git a/sp/src/game/shared/hl2/basehlcombatweapon_shared.cpp b/sp/src/game/shared/hl2/basehlcombatweapon_shared.cpp index 8d64f4a5f8..06c69df7c8 100644 --- a/sp/src/game/shared/hl2/basehlcombatweapon_shared.cpp +++ b/sp/src/game/shared/hl2/basehlcombatweapon_shared.cpp @@ -317,6 +317,14 @@ float CBaseHLCombatWeapon::CalcViewmodelBob( void ) g_lateralBob = speed*0.005f; g_lateralBob = g_lateralBob*0.3 + g_lateralBob*0.7*sin(cycle); g_lateralBob = clamp( g_lateralBob, -7.0f, 4.0f ); + +#ifdef MAPBASE + if (GetBobScale() != 1.0f) + { + //g_verticalBob *= GetBobScale(); + g_lateralBob *= GetBobScale(); + } +#endif //NOTENOTE: We don't use this return value in our case (need to restructure the calculation function setup!) return 0.0f; diff --git a/sp/src/game/shared/hl2/hl2_usermessages.cpp b/sp/src/game/shared/hl2/hl2_usermessages.cpp index 0ab9733023..93ca871e1e 100644 --- a/sp/src/game/shared/hl2/hl2_usermessages.cpp +++ b/sp/src/game/shared/hl2/hl2_usermessages.cpp @@ -44,10 +44,11 @@ void RegisterUserMessages( void ) #ifdef MAPBASE // This sends the credits file now usermessages->Register( "CreditsMsg", -1 ); + usermessages->Register( "LogoTimeMsg", -1 ); #else usermessages->Register( "CreditsMsg", 1 ); -#endif usermessages->Register( "LogoTimeMsg", 4 ); +#endif usermessages->Register( "AchievementEvent", -1 ); usermessages->Register( "UpdateJalopyRadar", -1 ); diff --git a/sp/src/game/shared/hl2mp/weapon_slam.cpp b/sp/src/game/shared/hl2mp/weapon_slam.cpp index 5bdea44f06..c4eb714064 100644 --- a/sp/src/game/shared/hl2mp/weapon_slam.cpp +++ b/sp/src/game/shared/hl2mp/weapon_slam.cpp @@ -115,6 +115,10 @@ acttable_t CWeapon_SLAM::m_acttable[] = { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_SLAM, false }, { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_SLAM, false }, { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_SLAM, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_SLAM, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_SLAM, false }, +#endif }; IMPLEMENT_ACTTABLE(CWeapon_SLAM); diff --git a/sp/src/game/shared/hl2mp/weapon_stunstick.cpp b/sp/src/game/shared/hl2mp/weapon_stunstick.cpp index 1d68b52924..1d6ae161a0 100644 --- a/sp/src/game/shared/hl2mp/weapon_stunstick.cpp +++ b/sp/src/game/shared/hl2mp/weapon_stunstick.cpp @@ -81,6 +81,27 @@ acttable_t CWeaponStunStick::m_acttable[] = #endif { ACT_MELEE_ATTACK1, ACT_MELEE_ATTACK_SWING, true }, { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_MELEE, true }, +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_IDLE, ACT_IDLE_MELEE, false }, + { ACT_RUN, ACT_RUN_MELEE, false }, + { ACT_WALK, ACT_WALK_MELEE, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, true }, + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_MELEE, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_MELEE, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_MELEE, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_MELEE, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_MELEE, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_MELEE, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_MELEE, false }, + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_MELEE, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponStunStick); diff --git a/sp/src/game/shared/hl2mp/weapon_stunstick.h b/sp/src/game/shared/hl2mp/weapon_stunstick.h index da25999c33..93688915d9 100644 --- a/sp/src/game/shared/hl2mp/weapon_stunstick.h +++ b/sp/src/game/shared/hl2mp/weapon_stunstick.h @@ -83,6 +83,12 @@ class CWeaponStunStick : public CBaseHL2MPBludgeonWeapon float GetDamageForActivity( Activity hitActivity ); +#ifdef MAPBASE + // Don't use backup activities + acttable_t *GetBackupActivityList() { return NULL; } + int GetBackupActivityListCount() { return 0; } +#endif + CWeaponStunStick( const CWeaponStunStick & ); private: diff --git a/sp/src/game/shared/mapbase/MapEdit.cpp b/sp/src/game/shared/mapbase/MapEdit.cpp index 326599648a..0891c29ef1 100644 --- a/sp/src/game/shared/mapbase/MapEdit.cpp +++ b/sp/src/game/shared/mapbase/MapEdit.cpp @@ -409,7 +409,6 @@ class CMapEdit : public CAutoGameSystemPerFrame pkvClassname = pkvClassname->GetNextKey(); } - pkvClassname->deleteThis(); } else if (FStrEq(pNodeName, "edit")) { @@ -432,7 +431,6 @@ class CMapEdit : public CAutoGameSystemPerFrame pName = pName->GetNextKey(); } - pName->deleteThis(); } else if (FStrEq(pNodeName, "delete")) { @@ -455,7 +453,6 @@ class CMapEdit : public CAutoGameSystemPerFrame pName = pName->GetNextKey(); } - pName->deleteThis(); } else if (FStrEq(pNodeName, "fire")) { @@ -464,7 +461,7 @@ class CMapEdit : public CAutoGameSystemPerFrame { pNodeName = pName->GetName(); - const char *pInputName = NULL; + string_t pInputName = NULL_STRING; variant_t varInputParam; float flInputDelay = 0.0f; CBaseEntity *pActivator = NULL; @@ -480,7 +477,7 @@ class CMapEdit : public CAutoGameSystemPerFrame { // Input name case 0: - pInputName = inputparams; break; + pInputName = AllocPooledString(inputparams); break; // Input parameter case 1: varInputParam.SetString(AllocPooledString(inputparams)); break; @@ -500,9 +497,10 @@ class CMapEdit : public CAutoGameSystemPerFrame iter++; inputparams = strtok(NULL, ","); } + free(pszValue); DebugMsg("MapEdit Debug: Firing input %s on %s\n", pInputName, pNodeName); - g_EventQueue.AddEvent(pNodeName, pInputName, varInputParam, flInputDelay, pActivator, pCaller, iOutputID); + g_EventQueue.AddEvent(pNodeName, STRING(pInputName), varInputParam, flInputDelay, pActivator, pCaller, iOutputID); pName = pName->GetNextKey(); } @@ -524,12 +522,10 @@ class CMapEdit : public CAutoGameSystemPerFrame pkvNodeData = pkvNodeData->GetNextKey(); } - pkvNodeData->deleteThis(); } pkvNode = pkvNode->GetNextKey(); } - pkvNode->deleteThis(); } void SpawnMapEdit(const char *pFile = NULL) @@ -888,8 +884,8 @@ void CC_MapEdit_Print( const CCommand& args ) pkvNode = pkvNode->GetNextKey(); } - pkvNode->deleteThis(); } + pkvFile->deleteThis(); } } static ConCommand mapedit_print("mapedit_print", CC_MapEdit_Print, "Prints a mapedit file in the console."); diff --git a/sp/src/game/shared/mapbase/mapbase_shared.cpp b/sp/src/game/shared/mapbase/mapbase_shared.cpp index 5aff896f64..fd4825de13 100644 --- a/sp/src/game/shared/mapbase/mapbase_shared.cpp +++ b/sp/src/game/shared/mapbase/mapbase_shared.cpp @@ -9,6 +9,7 @@ #include "cbase.h" #include "tier0/icommandline.h" +#include "tier1/mapbase_con_groups.h" #include "igamesystem.h" #include "filesystem.h" #include @@ -22,6 +23,7 @@ #include "panelmetaclassmgr.h" #include "c_soundscape.h" #include "hud_macros.h" +#include "clientmode_shared.h" #else #include "soundscape_system.h" #include "AI_ResponseSystem.h" @@ -46,33 +48,12 @@ extern ISoundEmitterSystemBase *soundemitterbase; ConVar mapbase_load_default_manifest("mapbase_load_default_manifest", "1", FCVAR_ARCHIVE, "Should we automatically load our default manifest file? (\"maps/%mapname%_manifest.txt\")"); -ConVar mapbase_load_soundscripts("mapbase_load_soundscripts", "1", FCVAR_ARCHIVE, "Should we load map-specific soundscripts? e.g. \"maps/mapname_level_sounds.txt\""); - -//ConVar mapbase_load_propdata("mapbase_load_propdata", "1", FCVAR_ARCHIVE, "Should we load map-specific propdata files? e.g. \"maps/mapname_propdata.txt\""); - -//ConVar mapbase_load_soundscapes("mapbase_load_soundscapes", "1", FCVAR_ARCHIVE, "Should we load map-specific soundscapes? e.g. \"maps/mapname_soundscapes.txt\""); - -ConVar mapbase_load_localization("mapbase_load_localization", "1", FCVAR_ARCHIVE, "Should we load map-specific localized text files? e.g. \"maps/mapname_english.txt\""); - -#ifdef CLIENT_DLL - -//ConVar mapbase_load_cc("mapbase_load_cc", "1", FCVAR_ARCHIVE, "Should we load map-specific closed captioning? e.g. \"maps/mapname_closecaption_english.txt\" and \"maps/mapname_closecaption_english.dat\""); - -#else - -ConVar mapbase_load_sentences("mapbase_load_sentences", "1", FCVAR_ARCHIVE, "Should we load map-specific sentences? e.g. \"maps/mapname_sentences.txt\""); +#ifdef GAME_DLL +// This constant should change with each Mapbase update +ConVar mapbase_version( "mapbase_version", MAPBASE_VERSION, FCVAR_NONE, "The version of Mapbase currently being used in this mod's server.dll" ); -ConVar mapbase_load_talker("mapbase_load_talker", "1", FCVAR_ARCHIVE, "Should we load map-specific talker files? e.g. \"maps/mapname_talker.txt\""); ConVar mapbase_flush_talker("mapbase_flush_talker", "1", FCVAR_NONE, "Normally, when a map with custom talker files is unloaded, the response system resets to rid itself of the custom file(s). Turn this convar off to prevent that from happening."); -ConVar mapbase_load_actbusy("mapbase_load_actbusy", "1", FCVAR_ARCHIVE, "Should we load map-specific actbusy files? e.g. \"maps/mapname_actbusy.txt\""); - -#endif - -#ifdef GAME_DLL -// This cvar should change with each Mapbase update -ConVar mapbase_version( "mapbase_version", "6.3", FCVAR_NONE, "The version of Mapbase currently being used in this mod." ); - extern void MapbaseGameLog_Init(); extern void ParseCustomActbusyFile(const char *file); @@ -82,26 +63,51 @@ extern void ReloadResponseSystem(); // Reloads the response system when the map changes to avoid custom talker leaking static bool g_bMapContainsCustomTalker; +#else +// This constant should change with each Mapbase update +ConVar mapbase_version_client( "mapbase_version_client", MAPBASE_VERSION, FCVAR_NONE, "The version of Mapbase currently being used in this mod's client.dll" ); + +// This is from the vgui_controls library +extern vgui::HScheme g_iCustomClientSchemeOverride; + +bool g_bUsingCustomHudAnimations = false; +bool g_bUsingCustomHudLayout = false; #endif +extern void AddSurfacepropFile( const char *pFileName, IPhysicsSurfaceProps *pProps, IFileSystem *pFileSystem ); + // Indicates this is a core Mapbase mod and not a mod using its code. static bool g_bMapbaseCore; // The game's name found in gameinfo.txt. Mostly used for Discord RPC. char g_iszGameName[128]; +#ifdef GAME_DLL +// Default player configuration +char g_szDefaultPlayerModel[MAX_PATH]; +bool g_bDefaultPlayerDrawExternally; + +char g_szDefaultHandsModel[MAX_PATH]; +int g_iDefaultHandsSkin; +int g_iDefaultHandsBody; +#endif + enum { MANIFEST_SOUNDSCRIPTS, //MANIFEST_PROPDATA, //MANIFEST_SOUNDSCAPES, MANIFEST_LOCALIZATION, + MANIFEST_SURFACEPROPS, #ifdef CLIENT_DLL - //MANIFEST_CLOSECAPTION, + MANIFEST_CLOSECAPTION, MANIFEST_VGUI, + MANIFEST_CLIENTSCHEME, + MANIFEST_HUDANIMATIONS, + MANIFEST_HUDLAYOUT, #else MANIFEST_TALKER, - MANIFEST_SENTENCES, + //MANIFEST_SENTENCES, MANIFEST_ACTBUSY, #endif @@ -111,24 +117,35 @@ enum struct ManifestType_t { + ManifestType_t( const char *_string, const char *cvarname, const char *cvardesc ) : cvar( cvarname, "1", FCVAR_ARCHIVE, cvardesc ) + { + string = _string; + } + //int type; const char *string; - ConVar *cvar; + ConVar cvar; }; +#define DECLARE_MANIFEST_TYPE(name, cvar, desc) { #name, ConVar(#cvar, "1", FCVAR_ARCHIVE, #desc) } + // KEEP THS IN SYNC WITH THE ENUM! static const ManifestType_t gm_szManifestFileStrings[MANIFEST_NUM_TYPES] = { - { "soundscripts", &mapbase_load_soundscripts }, - //{ "propdata", &mapbase_load_propdata }, - //{ "soundscapes", &mapbase_load_soundscapes }, - { "localization", &mapbase_load_localization }, + { "soundscripts", "mapbase_load_soundscripts", "Should we load map-specific soundscripts? e.g. \"maps/_level_sounds.txt\"" }, + //{ "propdata", "mapbase_load_propdata", "Should we load map-specific soundscripts? e.g. \"maps/_level_sounds.txt\"" }, + //{ "soundscapes", "mapbase_load_soundscapes", "Should we load map-specific soundscapes? e.g. \"maps/_soundscapes.txt\"" }, + { "localization", "mapbase_load_localization", "Should we load map-specific localized text files? e.g. \"maps/_english.txt\"" }, + { "surfaceprops", "mapbase_load_surfaceprops", "Should we load map-specific surfaceproperties files? e.g. \"maps/_surfaceproperties.txt\"" }, #ifdef CLIENT_DLL - //{ "closecaption", &mapbase_load_cc }, - { "vgui", NULL }, + { "closecaption", "mapbase_load_closecaption", "Should we load map-specific closed captioning? e.g. \"maps/_closecaption_english.txt\" and \"maps/_closecaption_english.dat\"" }, + { "vgui", "mapbase_load_vgui", "Should we load map-specific VGUI screens? e.g. \"maps/_screens.txt\"" }, + { "clientscheme", "mapbase_load_clientscheme", "Should we load map-specific ClientScheme.res overrides? e.g. \"maps/_clientscheme.res\"" }, + { "hudanimations", "mapbase_load_hudanimations", "Should we load map-specific HUD animation overrides? e.g. \"maps/_hudanimations.txt\"" }, + { "hudlayout", "mapbase_load_hudlayout", "Should we load map-specific HUD layout overrides? e.g. \"maps/_hudlayout.res\"" }, #else - { "talker", &mapbase_load_talker }, - { "sentences", &mapbase_load_sentences }, - { "actbusy", &mapbase_load_actbusy }, + { "talker", "mapbase_load_talker", "Should we load map-specific talker files? e.g. \"maps/_talker.txt\"" }, + //{ "sentences", "mapbase_load_sentences", "Should we load map-specific sentences? e.g. \"maps/_sentences.txt\"" }, + { "actbusy", "mapbase_load_actbusy", "Should we load map-specific actbusy files? e.g. \"maps/_actbusy.txt\"" }, #endif }; @@ -151,6 +168,8 @@ class CMapbaseSystem : public CAutoGameSystem virtual bool Init() { + InitConsoleGroups( g_pFullFileSystem ); + // Checks gameinfo.txt for additional command line options KeyValues *gameinfo = new KeyValues("GameInfo"); if (GetGameInfoKeyValues(gameinfo)) @@ -207,6 +226,15 @@ class CMapbaseSystem : public CAutoGameSystem Q_strncpy(g_iszGameName, pszGameName, sizeof(g_iszGameName)); } + +#ifdef GAME_DLL + Q_strncpy( g_szDefaultPlayerModel, gameinfo->GetString( "player_default_model", "models/player.mdl" ), sizeof( g_szDefaultPlayerModel ) ); + g_bDefaultPlayerDrawExternally = gameinfo->GetBool( "player_default_draw_externally", false ); + + Q_strncpy( g_szDefaultHandsModel, gameinfo->GetString( "player_default_hands", "models/weapons/v_hands.mdl" ), sizeof( g_szDefaultHandsModel ) ); + g_iDefaultHandsSkin = gameinfo->GetInt( "player_default_hands_skin", 0 ); + g_iDefaultHandsBody = gameinfo->GetInt( "player_default_hands_body", 0 ); +#endif } gameinfo->deleteThis(); @@ -255,6 +283,46 @@ class CMapbaseSystem : public CAutoGameSystem g_MapName = NULL; RefreshCustomTalker(); + +#ifdef CLIENT_DLL + CHudCloseCaption *hudCloseCaption = GET_HUDELEMENT( CHudCloseCaption ); + FOR_EACH_VEC( m_CloseCaptionFileNames, i ) + { + hudCloseCaption->RemoveCaptionDictionary( m_CloseCaptionFileNames[i] ); + } + m_CloseCaptionFileNames.RemoveAll(); + + if (g_iCustomClientSchemeOverride != 0 || g_bUsingCustomHudAnimations || g_bUsingCustomHudLayout) + { + CGMsg( 1, CON_GROUP_MAPBASE_MISC, "Mapbase: Reloading client mode and viewport scheme\n" ); + + // TODO: We currently have no way of actually cleaning up custom schemes upon level unload. + // That may or may not be sustainable if there's a ton of custom schemes loaded at once + g_iCustomClientSchemeOverride = 0; + + g_bUsingCustomHudAnimations = false; + g_bUsingCustomHudLayout = false; + + // Reload scheme + ClientModeShared *mode = ( ClientModeShared * )GetClientModeNormal(); + if ( mode ) + { + mode->ReloadScheme(); + + // We need to reload default values, so load a special "hudlayout_mapbase.res" file that only contains + // default Mapbase definitions identical to the defaults in the code + CBaseViewport *pViewport = dynamic_cast(g_pClientMode->GetViewport()); + if (pViewport) + { + KeyValuesAD pConditions( "conditions" ); + g_pClientMode->ComputeVguiResConditions( pConditions ); + + // reload the .res file from disk + pViewport->LoadControlSettings( "scripts/hudlayout_mapbase.res", NULL, NULL, pConditions ); + } + } + } +#endif } bool RefreshMapName() @@ -277,9 +345,6 @@ class CMapbaseSystem : public CAutoGameSystem } #ifdef CLIENT_DLL - bool m_bInitializedRTs = false; - CUtlVector m_CameraTextures; - //----------------------------------------------------------------------------- // Initialize custom RT textures if necessary //----------------------------------------------------------------------------- @@ -380,9 +445,13 @@ class CMapbaseSystem : public CAutoGameSystem case MANIFEST_SOUNDSCRIPTS: { soundemitterbase->AddSoundOverrides(value); } break; //case MANIFEST_PROPDATA: { g_PropDataSystem.ParsePropDataFile(value); } break; case MANIFEST_LOCALIZATION: { g_pVGuiLocalize->AddFile( value, "MOD", true ); } break; + case MANIFEST_SURFACEPROPS: { AddSurfacepropFile( value, physprops, filesystem ); } break; #ifdef CLIENT_DLL - //case MANIFEST_CLOSECAPTION: { todo } break; - case MANIFEST_VGUI: { PanelMetaClassMgr()->LoadMetaClassDefinitionFile( value ); } break; + case MANIFEST_CLOSECAPTION: { ManifestLoadCustomCloseCaption( value ); } break; + case MANIFEST_VGUI: { PanelMetaClassMgr()->LoadMetaClassDefinitionFile( value ); } break; + case MANIFEST_CLIENTSCHEME: { ManifestLoadCustomScheme( value ); } break; + case MANIFEST_HUDANIMATIONS: { ManifestLoadCustomHudAnimations( value ); } break; + case MANIFEST_HUDLAYOUT: { ManifestLoadCustomHudLayout( value ); } break; //case MANIFEST_SOUNDSCAPES: { Soundscape_AddFile(value); } break; #else case MANIFEST_TALKER: { @@ -390,7 +459,7 @@ class CMapbaseSystem : public CAutoGameSystem LoadResponseSystemFile(value); //PrecacheCustomResponseSystem( value ); } break; //case MANIFEST_SOUNDSCAPES: { g_SoundscapeSystem.AddSoundscapeFile(value); } break; - case MANIFEST_SENTENCES: { engine->PrecacheSentenceFile(value); } break; + //case MANIFEST_SENTENCES: { engine->PrecacheSentenceFile(value); } break; case MANIFEST_ACTBUSY: { ParseCustomActbusyFile(value); } break; #endif } @@ -449,7 +518,7 @@ class CMapbaseSystem : public CAutoGameSystem { if (FStrEq(name, gm_szManifestFileStrings[i].string)) { - if (!gm_szManifestFileStrings[i].cvar || gm_szManifestFileStrings[i].cvar->GetBool()) + if (gm_szManifestFileStrings[i].cvar.GetBool()) { LoadFromValue(value, i, bDontWarn); } @@ -459,23 +528,101 @@ class CMapbaseSystem : public CAutoGameSystem } } -#ifdef MAPBASE_VSCRIPT - void ScriptAddManifestFile( const char *szScript ) { AddManifestFile( szScript ); } +private: - void LoadSoundscriptFile( const char *szScript ) { LoadFromValue(szScript, MANIFEST_SOUNDSCRIPTS, false); } -#ifndef CLIENT_DLL - void LoadTalkerFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_TALKER, false ); } - void LoadActbusyFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_ACTBUSY, false ); } +#ifdef CLIENT_DLL + void ManifestLoadCustomCloseCaption( const char *pszFile ) + { + if (GET_HUDELEMENT( CHudCloseCaption )) + (GET_HUDELEMENT( CHudCloseCaption ))->AddCustomCaptionFile( pszFile, m_CloseCaptionFileNames ); + } + + // Custom scheme loading + void ManifestLoadCustomScheme( const char *pszFile ) + { + g_iCustomClientSchemeOverride = vgui::scheme()->LoadSchemeFromFile( pszFile, "CustomClientScheme" ); + + // Reload scheme + ClientModeShared *mode = ( ClientModeShared * )GetClientModeNormal(); + if ( mode ) + { + mode->ReloadScheme(); + } + } + + void ManifestLoadCustomHudAnimations( const char *pszFile ) + { + CBaseViewport *pViewport = dynamic_cast(g_pClientMode->GetViewport()); + if (pViewport) + { + g_bUsingCustomHudAnimations = true; + if (!pViewport->LoadCustomHudAnimations( pszFile )) + { + g_bUsingCustomHudAnimations = false; + CGWarning( 0, CON_GROUP_MAPBASE_MISC, "Custom HUD animations file \"%s\" failed to load\n", pszFile ); + pViewport->ReloadHudAnimations(); + } + else + { + CGMsg( 1, CON_GROUP_MAPBASE_MISC, "Loaded custom HUD animations file \"%s\"\n", pszFile );; + } + } + } + + void ManifestLoadCustomHudLayout( const char *pszFile ) + { + CBaseViewport *pViewport = dynamic_cast(g_pClientMode->GetViewport()); + if (pViewport) + { + g_bUsingCustomHudLayout = true; + + KeyValuesAD pConditions( "conditions" ); + g_pClientMode->ComputeVguiResConditions( pConditions ); + + // reload the .res file from disk + pViewport->LoadControlSettings( pszFile, NULL, NULL, pConditions ); + + CGMsg( 1, CON_GROUP_MAPBASE_MISC, "Loaded custom HUD layout file \"%s\"\n", pszFile );; + } + } +#endif + +public: + + void LoadCustomSoundscriptFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_SOUNDSCRIPTS, false ); } + void LoadCustomLocalizationFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_LOCALIZATION, false ); } + void LoadCustomSurfacePropsFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_SURFACEPROPS, false ); } +#ifdef CLIENT_DLL + void LoadCustomCloseCaptionFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_CLOSECAPTION, false ); } + void LoadCustomVGUIFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_VGUI, false ); } + void LoadCustomClientSchemeFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_CLIENTSCHEME, false ); } + void LoadCustomHUDAnimationsFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_HUDANIMATIONS, false ); } + void LoadCustomHUDLayoutFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_HUDLAYOUT, false ); } +#else + void LoadCustomTalkerFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_TALKER, false ); } + void LoadCustomActbusyFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_ACTBUSY, false ); } #endif const char *GetModName() { return g_iszGameName; } bool IsCoreMapbase() { return g_bMapbaseCore; } +#ifdef MAPBASE_VSCRIPT + void ScriptAddManifestFile( const char *szScript ) { AddManifestFile( szScript ); } + virtual void RegisterVScript() { g_pScriptVM->RegisterInstance( this, "Mapbase" ); } #endif + +private: + +#ifdef CLIENT_DLL + bool m_bInitializedRTs = false; + CUtlVector m_CameraTextures; + + CUtlVector m_CloseCaptionFileNames; +#endif }; CMapbaseSystem g_MapbaseSystem; @@ -488,14 +635,32 @@ END_DATADESC() #ifdef MAPBASE_VSCRIPT BEGIN_SCRIPTDESC_ROOT( CMapbaseSystem, SCRIPT_SINGLETON "All-purpose Mapbase system primarily used for map-specific files." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptAddManifestFile, "AddManifestFile", "Loads a manifest file." ) - DEFINE_SCRIPTFUNC( LoadSoundscriptFile, "Loads a custom soundscript file." ) -#ifndef CLIENT_DLL - DEFINE_SCRIPTFUNC( LoadTalkerFile, "Loads a custom talker file." ) - DEFINE_SCRIPTFUNC( LoadActbusyFile, "Loads a custom actbusy file." ) + DEFINE_SCRIPTFUNC( LoadCustomSoundscriptFile, "Loads a custom soundscript file." ) + DEFINE_SCRIPTFUNC( LoadCustomLocalizationFile, "Loads a custom localization file." ) + DEFINE_SCRIPTFUNC( LoadCustomSurfacePropsFile, "Loads a custom surface properties file." ) +#ifdef CLIENT_DLL + DEFINE_SCRIPTFUNC( LoadCustomCloseCaptionFile, "Loads a custom closed captions file." ) + DEFINE_SCRIPTFUNC( LoadCustomVGUIFile, "Loads a custom VGUI definitions file." ) + DEFINE_SCRIPTFUNC( LoadCustomClientSchemeFile, "Loads a custom ClientScheme.res override file." ) + DEFINE_SCRIPTFUNC( LoadCustomHUDAnimationsFile, "Loads a custom HUD animations override file." ) + DEFINE_SCRIPTFUNC( LoadCustomHUDLayoutFile, "Loads a custom HUD layout override file." ) +#else + DEFINE_SCRIPTFUNC( LoadCustomTalkerFile, "Loads a custom talker file." ) + DEFINE_SCRIPTFUNC( LoadCustomActbusyFile, "Loads a custom actbusy file." ) #endif + DEFINE_SCRIPTFUNC( GetModName, "Gets the name of the mod. This is the name which shows up on Steam, RPC, etc." ) DEFINE_SCRIPTFUNC( IsCoreMapbase, "Indicates whether this is one of the original Mapbase mods or just a separate mod using its code." ) + + // Legacy + DEFINE_SCRIPTFUNC_NAMED( LoadCustomSoundscriptFile, "LoadSoundscriptFile", SCRIPT_HIDE ) +#ifndef CLIENT_DLL + DEFINE_SCRIPTFUNC_NAMED( LoadCustomTalkerFile, "LoadTalkerFile", SCRIPT_HIDE ) + DEFINE_SCRIPTFUNC_NAMED( LoadCustomActbusyFile, "LoadActbusyFile", SCRIPT_HIDE ) +#endif + END_SCRIPTDESC(); #endif @@ -626,3 +791,38 @@ BEGIN_DATADESC( CMapbaseManifestEntity ) END_DATADESC() #endif + +//----------------------------------------------------------------------------- + +void CV_IncludeNameChanged( IConVar *pConVar, const char *pOldString, float flOldValue ); + +#ifdef CLIENT_DLL +ConVar con_group_include_name_client( "con_group_include_name_client", "0", FCVAR_NONE, "Includes groups when printing on the client.", CV_IncludeNameChanged ); + +void CV_IncludeNameChanged( IConVar *pConVar, const char *pOldString, float flOldValue ) +{ + SetConsoleGroupIncludeNames( con_group_include_name_client.GetBool() ); +} +#else +ConVar con_group_include_name( "con_group_include_name", "0", FCVAR_NONE, "Includes groups when printing.", CV_IncludeNameChanged ); + +void CV_IncludeNameChanged( IConVar *pConVar, const char *pOldString, float flOldValue ) +{ + SetConsoleGroupIncludeNames( con_group_include_name.GetBool() ); +} +#endif + +CON_COMMAND_SHARED( con_group_reload, "Reloads all console groups." ) +{ + InitConsoleGroups( g_pFullFileSystem ); +} + +CON_COMMAND_SHARED( con_group_list, "Prints a list of all console groups." ) +{ + PrintAllConsoleGroups(); +} + +CON_COMMAND_SHARED( con_group_toggle, "Toggles a console group." ) +{ + ToggleConsoleGroups( args.Arg( 1 ) ); +} diff --git a/sp/src/game/shared/mapbase/matchers.cpp b/sp/src/game/shared/mapbase/matchers.cpp index f1f1d26b17..b8b915f490 100644 --- a/sp/src/game/shared/mapbase/matchers.cpp +++ b/sp/src/game/shared/mapbase/matchers.cpp @@ -10,16 +10,6 @@ #include "matchers.h" #include "fmtstr.h" -// glibc (Linux) uses these tokens when including , so we must not #define them -#undef max -#undef min -#include -#undef MINMAX_H -#include "minmax.h" - -ConVar mapbase_wildcards_enabled("mapbase_wildcards_enabled", "1", FCVAR_NONE, "Toggles Mapbase's '?' wildcard and true '*' features. Useful for maps that have '?' in their targetnames."); -ConVar mapbase_regex_enabled("mapbase_regex_enabled", "1", FCVAR_NONE, "Toggles Mapbase's regex matching handover."); - #ifdef CLIENT_DLL // FIXME: There is no clientside equivalent to the RS code static bool ResponseSystemCompare(const char *criterion, const char *value) { return Matcher_NamesMatch(criterion, value); } @@ -47,181 +37,6 @@ bool Matcher_Match(const char *pszQuery, const char *szValue) bool Matcher_Match(const char *pszQuery, int iValue) { return Matcher_Match(pszQuery, CNumStr(iValue)); } bool Matcher_Match(const char *pszQuery, float flValue) { return Matcher_Match(pszQuery, CNumStr(flValue)); } -// ------------------------------------------------------------------------------- -// ------------------------------------------------------------------------------- - -// The recursive part of Mapbase's modified version of Valve's NamesMatch(). -bool Matcher_RunCharCompare(const char *pszQuery, const char *szValue) -{ - // This matching model is based off of the ASW SDK - while ( *szValue && *pszQuery ) - { - char cName = *szValue; - char cQuery = *pszQuery; - if ( cName != cQuery && tolower(cName) != tolower(cQuery) ) // people almost always use lowercase, so assume that first - { - // Now we'll try the new and improved Mapbase wildcards! - switch (*pszQuery) - { - case '*': - { - // Return true at classic trailing * - if ( *(pszQuery+1) == 0 ) - return true; - - if (mapbase_wildcards_enabled.GetBool()) - { - // There's text after this * which we need to test. - // This recursion allows for multiple wildcards - int vlen = Q_strlen(szValue); - ++pszQuery; - for (int i = 0; i < vlen; i++) - { - if (Matcher_RunCharCompare(pszQuery, szValue + i)) - return true; - } - } - return false; - } break; - case '?': - // Just skip if we're capable of lazy wildcards - if (mapbase_wildcards_enabled.GetBool()) - break; - default: - return false; - } - } - ++szValue; - ++pszQuery; - } - - // Include a classic trailing * check for when szValue is something like "value" and pszQuery is "value*" - return ( ( *pszQuery == 0 && *szValue == 0 ) || *pszQuery == '*' ); -} - -// Regular expressions based off of the std library. -// The C++ is strong in this one. -bool Matcher_Regex(const char *pszQuery, const char *szValue) -{ - std::regex regex; - - // Since I can't find any other way to check for valid regex, - // use a try-catch here to see if it throws an exception. - try { regex = std::regex(pszQuery); } - catch (std::regex_error &e) - { - Msg("Invalid regex \"%s\" (%s)\n", pszQuery, e.what()); - return false; - } - - std::match_results results; - bool bMatch = std::regex_match( szValue, results, regex ); - if (!bMatch) - return false; - - // Only match the *whole* string - return Q_strlen(results.str(0).c_str()) == Q_strlen(szValue); -} - -// The entry point for Mapbase's modified version of Valve's NamesMatch(). -bool Matcher_NamesMatch(const char *pszQuery, const char *szValue) -{ - if ( szValue == NULL ) - return (*pszQuery == 0 || *pszQuery == '*'); - - // If the pointers are identical, we're identical - if ( szValue == pszQuery ) - return true; - - // Check for regex - if ( *pszQuery == '@' && mapbase_regex_enabled.GetBool() ) - { - // Make sure it has a forward slash - // (prevents confusion with instance fixup escape) - if (*(pszQuery+1) == '/') - { - return Matcher_Regex( pszQuery+2, szValue ); - } - } - - return Matcher_RunCharCompare( pszQuery, szValue ); -} - -bool Matcher_NamesMatch_Classic(const char *pszQuery, const char *szValue) -{ - if ( szValue == NULL ) - return (!pszQuery || *pszQuery == 0 || *pszQuery == '*'); - - // If the pointers are identical, we're identical - if ( szValue == pszQuery ) - return true; - - while ( *szValue && *pszQuery ) - { - unsigned char cName = *szValue; - unsigned char cQuery = *pszQuery; - // simple ascii case conversion - if ( cName == cQuery ) - ; - else if ( cName - 'A' <= (unsigned char)'Z' - 'A' && cName - 'A' + 'a' == cQuery ) - ; - else if ( cName - 'a' <= (unsigned char)'z' - 'a' && cName - 'a' + 'A' == cQuery ) - ; - else - break; - ++szValue; - ++pszQuery; - } - - if ( *pszQuery == 0 && *szValue == 0 ) - return true; - - // @TODO (toml 03-18-03): Perhaps support real wildcards. Right now, only thing supported is trailing * - if ( *pszQuery == '*' ) - return true; - - return false; -} - -bool Matcher_NamesMatch_MutualWildcard(const char *pszQuery, const char *szValue) -{ - if ( szValue == NULL ) - return (!pszQuery || *pszQuery == 0 || *pszQuery == '*'); - - if ( pszQuery == NULL ) - return (!szValue || *szValue == 0 || *szValue == '*'); - - // If the pointers are identical, we're identical - if ( szValue == pszQuery ) - return true; - - while ( *szValue && *pszQuery ) - { - unsigned char cName = *szValue; - unsigned char cQuery = *pszQuery; - // simple ascii case conversion - if ( cName == cQuery ) - ; - else if ( cName - 'A' <= (unsigned char)'Z' - 'A' && cName - 'A' + 'a' == cQuery ) - ; - else if ( cName - 'a' <= (unsigned char)'z' - 'a' && cName - 'a' + 'A' == cQuery ) - ; - else - break; - ++szValue; - ++pszQuery; - } - - if ( *pszQuery == 0 && *szValue == 0 ) - return true; - - // @TODO (toml 03-18-03): Perhaps support real wildcards. Right now, only thing supported is trailing * - if ( *pszQuery == '*' || *szValue == '*' ) - return true; - - return false; -} - // Matcher_Compare is a deprecated alias originally used when Matcher_Match didn't support wildcards. /* bool Matcher_Compare(const char *pszQuery, const char *szValue) diff --git a/sp/src/game/shared/mapbase/matchers.h b/sp/src/game/shared/mapbase/matchers.h index 52e8cc1a28..7d86ad636a 100644 --- a/sp/src/game/shared/mapbase/matchers.h +++ b/sp/src/game/shared/mapbase/matchers.h @@ -11,8 +11,7 @@ #pragma once #endif - -#define MAPBASE_MATCHERS 1 +#include "tier1/mapbase_matchers_base.h" // Compares with != and the like. Basically hijacks the response system matching. // This also loops back around to Matcher_NamesMatch. @@ -22,47 +21,7 @@ bool Matcher_Match( const char *pszQuery, const char *szValue ); bool Matcher_Match( const char *pszQuery, int iValue ); bool Matcher_Match( const char *pszQuery, float flValue ); -// Regular expressions based off of the std library. -// pszQuery = The regex text. -// szValue = The value that should be matched. -bool Matcher_Regex( const char *pszQuery, const char *szValue ); - -// Compares two strings with support for wildcards or regex. This code is an expanded version of baseentity.cpp's NamesMatch(). -// pszQuery = The value that should have the wildcard. -// szValue = The value tested against the query. -// Use Matcher_Match if you want <, !=, etc. as well. -bool Matcher_NamesMatch( const char *pszQuery, const char *szValue ); - -// Identical to baseentity.cpp's original NamesMatch(). -// pszQuery = The value that should have the wildcard. -// szValue = The value tested against the query. -bool Matcher_NamesMatch_Classic( const char *pszQuery, const char *szValue ); - -// Identical to Matcher_NamesMatch_Classic(), but either value could use a wildcard. -// pszQuery = The value that serves as the query. This value can use wildcards. -// szValue = The value tested against the query. This value can use wildcards as well. -bool Matcher_NamesMatch_MutualWildcard( const char *pszQuery, const char *szValue ); - // Deprecated; do not use -static inline bool Matcher_Compare( const char *pszQuery, const char *szValue ) { return Matcher_Match( pszQuery, szValue ); } - -// Taken from the Response System. -// Checks if the specified string appears to be a number of some sort. -static bool AppearsToBeANumber( char const *token ) -{ - if ( atof( token ) != 0.0f ) - return true; - - char const *p = token; - while ( *p ) - { - if ( *p != '0' ) - return false; - - p++; - } - - return true; -} +//static inline bool Matcher_Compare( const char *pszQuery, const char *szValue ) { return Matcher_Match( pszQuery, szValue ); } #endif diff --git a/sp/src/game/shared/mapbase/singleplayer_animstate.cpp b/sp/src/game/shared/mapbase/singleplayer_animstate.cpp new file mode 100644 index 0000000000..7c8b92c98a --- /dev/null +++ b/sp/src/game/shared/mapbase/singleplayer_animstate.cpp @@ -0,0 +1,669 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Single Player animation state 'handler'. This utility is used +// to evaluate the pose parameter value based on the direction +// and speed of the player. +// +// ------------------------------------------------------------------------------ +// +// This was originally based on the following VDC article: +// https://developer.valvesoftware.com/wiki/Fixing_the_player_animation_state_(Single_Player) +// +// It has been modified by Blixibon to derive from CBasePlayerAnimState instead and support 9-way blends. +// Much of the work done to make this derive from CBasePlayerAnimState utilized code from the Alien Swarm SDK. +// +//=============================================================================// + +#include "cbase.h" +#include "singleplayer_animstate.h" +#include "tier0/vprof.h" +#include "animation.h" +#include "studio.h" +#include "apparent_velocity_helper.h" +#include "utldict.h" +#include "filesystem.h" +#include "in_buttons.h" +#include "datacache/imdlcache.h" + +extern ConVar mp_facefronttime, mp_feetyawrate, mp_ik; + +ConVar sv_playeranimstate_animtype( "sv_playeranimstate_animtype", "0", FCVAR_NONE, "The leg animation type used by the singleplayer animation state. 9way = 0, 8way = 1, GoldSrc = 2" ); +ConVar sv_playeranimstate_bodyyaw( "sv_playeranimstate_bodyyaw", "45.0", FCVAR_NONE, "The maximum body yaw used by the singleplayer animation state." ); +ConVar sv_playeranimstate_use_aim_sequences( "sv_playeranimstate_use_aim_sequences", "1", FCVAR_NONE, "Allows the singleplayer animation state to use aim sequences." ); + +#define MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION 15.0f + +#define FIRESEQUENCE_LAYER (AIMSEQUENCE_LAYER+NUM_AIMSEQUENCE_LAYERS) +#define RELOADSEQUENCE_LAYER (FIRESEQUENCE_LAYER + 1) +#define NUM_LAYERS_WANTED (RELOADSEQUENCE_LAYER + 1) + +CSinglePlayerAnimState *CreatePlayerAnimationState( CBasePlayer *pPlayer ) +{ + MDLCACHE_CRITICAL_SECTION(); + + CSinglePlayerAnimState *pState = new CSinglePlayerAnimState( pPlayer ); + + // Setup the movement data. + CModAnimConfig movementData; + movementData.m_LegAnimType = (LegAnimType_t)sv_playeranimstate_animtype.GetInt(); + movementData.m_flMaxBodyYawDegrees = sv_playeranimstate_bodyyaw.GetFloat(); + movementData.m_bUseAimSequences = sv_playeranimstate_use_aim_sequences.GetBool(); + + pState->Init( pPlayer, movementData ); + + return pState; +} + +// Below this many degrees, slow down turning rate linearly +#define FADE_TURN_DEGREES 45.0f +// After this, need to start turning feet +#define MAX_TORSO_ANGLE 90.0f +// Below this amount, don't play a turning animation/perform IK +#define MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION 15.0f + +//static ConVar tf2_feetyawrunscale( "tf2_feetyawrunscale", "2", FCVAR_REPLICATED, "Multiplier on tf2_feetyawrate to allow turning faster when running." ); +extern ConVar sv_backspeed; +extern ConVar mp_feetyawrate; +extern ConVar mp_facefronttime; +extern ConVar mp_ik; + +CSinglePlayerAnimState::CSinglePlayerAnimState( CBasePlayer *pPlayer ): m_pPlayer( pPlayer ) +{ +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Activity CSinglePlayerAnimState::CalcMainActivity() +{ +#ifdef CLIENT_DLL + return ACT_IDLE; +#else + float speed = GetOuter()->GetAbsVelocity().Length2D(); + + if ( HandleJumping() ) + { + return ACT_HL2MP_JUMP; + } + else + { + Activity idealActivity = ACT_HL2MP_IDLE; + + if ( GetOuter()->GetFlags() & ( FL_FROZEN | FL_ATCONTROLS ) ) + { + speed = 0; + } + else + { + if ( GetOuter()->GetFlags() & FL_DUCKING ) + { + if ( speed > 0 ) + { + idealActivity = ACT_HL2MP_WALK_CROUCH; + } + else + { + idealActivity = ACT_HL2MP_IDLE_CROUCH; + } + } + else + { + if ( speed > 0 ) + { +#if EXPANDED_HL2DM_ACTIVITIES + if ( m_pPlayer->GetButtons() & IN_WALK ) + { + idealActivity = ACT_HL2MP_WALK; + } + else +#endif + { + idealActivity = ACT_HL2MP_RUN; + } + } + else + { + idealActivity = ACT_HL2MP_IDLE; + } + } + } + + return idealActivity; + } + + //return m_pPlayer->GetActivity(); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSinglePlayerAnimState::SetPlayerAnimation( PLAYER_ANIM playerAnim ) +{ + if ( playerAnim == PLAYER_ATTACK1 ) + { + m_iFireSequence = SelectWeightedSequence( TranslateActivity( ACT_HL2MP_GESTURE_RANGE_ATTACK ) ); + m_bFiring = m_iFireSequence != -1; + m_flFireCycle = 0; + } + else if ( playerAnim == PLAYER_ATTACK2 ) + { +#if EXPANDED_HL2DM_ACTIVITIES + m_iFireSequence = SelectWeightedSequence( TranslateActivity( ACT_HL2MP_GESTURE_RANGE_ATTACK2 ) ); +#else + m_iFireSequence = SelectWeightedSequence( TranslateActivity( ACT_HL2MP_GESTURE_RANGE_ATTACK ) ); +#endif + m_bFiring = m_iFireSequence != -1; + m_flFireCycle = 0; + } + else if ( playerAnim == PLAYER_JUMP ) + { + // Play the jump animation. + if (!m_bJumping) + { + m_bJumping = true; + m_bFirstJumpFrame = true; + m_flJumpStartTime = gpGlobals->curtime; + } + } + else if ( playerAnim == PLAYER_RELOAD ) + { + m_iReloadSequence = SelectWeightedSequence( TranslateActivity( ACT_HL2MP_GESTURE_RELOAD ) ); + if (m_iReloadSequence != -1) + { + // clear other events that might be playing in our layer + m_bWeaponSwitching = false; + m_fReloadPlaybackRate = 1.0f; + m_bReloading = true; + m_flReloadCycle = 0; + } + } + else if ( playerAnim == PLAYER_UNHOLSTER || playerAnim == PLAYER_HOLSTER ) + { + m_iWeaponSwitchSequence = SelectWeightedSequence( TranslateActivity( playerAnim == PLAYER_UNHOLSTER ? ACT_ARM : ACT_DISARM ) ); + if (m_iWeaponSwitchSequence != -1) + { + // clear other events that might be playing in our layer + m_bPlayingMisc = false; + m_bReloading = false; + + m_bWeaponSwitching = true; + m_flWeaponSwitchCycle = 0; + m_flMiscBlendOut = 0.1f; + m_flMiscBlendIn = 0.1f; + m_bMiscNoOverride = false; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Activity CSinglePlayerAnimState::TranslateActivity( Activity actDesired ) +{ +#ifdef CLIENT_DLL + return actDesired; +#else + return m_pPlayer->Weapon_TranslateActivity( actDesired ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CSinglePlayerAnimState::HandleJumping() +{ + if ( m_bJumping ) + { + if ( m_bFirstJumpFrame ) + { + m_bFirstJumpFrame = false; + RestartMainSequence(); // Reset the animation. + } + + // Don't check if he's on the ground for a sec.. sometimes the client still has the + // on-ground flag set right when the message comes in. + if (m_flJumpStartTime > gpGlobals->curtime) + m_flJumpStartTime = gpGlobals->curtime; + if ( gpGlobals->curtime - m_flJumpStartTime > 0.2f) + { + if ( m_pOuter->GetFlags() & FL_ONGROUND || GetOuter()->GetGroundEntity() != NULL) + { + m_bJumping = false; + RestartMainSequence(); // Reset the animation. + } + } + } + + // Are we still jumping? If so, keep playing the jump animation. + return m_bJumping; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSinglePlayerAnimState::ComputeSequences( CStudioHdr *pStudioHdr ) +{ + CBasePlayerAnimState::ComputeSequences(pStudioHdr); + + ComputeFireSequence(); + ComputeMiscSequence(); + ComputeReloadSequence(); + ComputeWeaponSwitchSequence(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSinglePlayerAnimState::AddMiscSequence( int iSequence, float flBlendIn, float flBlendOut, float flPlaybackRate, bool bHoldAtEnd, bool bOnlyWhenStill ) +{ + Assert( iSequence != -1 ); + + m_iMiscSequence = iSequence; + m_flMiscBlendIn = flBlendIn; + m_flMiscBlendOut = flBlendOut; + + m_bPlayingMisc = true; + m_bMiscHoldAtEnd = bHoldAtEnd; + m_bReloading = false; + m_flMiscCycle = 0; + m_bMiscOnlyWhenStill = bOnlyWhenStill; + m_bMiscNoOverride = true; + m_fMiscPlaybackRate = flPlaybackRate; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSinglePlayerAnimState::ClearAnimationState() +{ + m_bJumping = false; + m_bFiring = false; + m_bReloading = false; + m_bWeaponSwitching = false; + m_bPlayingMisc = false; + m_flReloadBlendIn = 0.0f; + m_flReloadBlendOut = 0.0f; + m_flMiscBlendIn = 0.0f; + m_flMiscBlendOut = 0.0f; + CBasePlayerAnimState::ClearAnimationState(); +} + +void CSinglePlayerAnimState::ClearAnimationLayers() +{ + VPROF( "CBasePlayerAnimState::ClearAnimationLayers" ); + if ( !m_pOuter ) + return; + + m_pOuter->SetNumAnimOverlays( NUM_LAYERS_WANTED ); + for ( int i=0; i < m_pOuter->GetNumAnimOverlays(); i++ ) + { + m_pOuter->GetAnimOverlay( i )->SetOrder( CBaseAnimatingOverlay::MAX_OVERLAYS ); +#ifndef CLIENT_DLL + m_pOuter->GetAnimOverlay( i )->m_fFlags = 0; +#endif + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CSinglePlayerAnimState::CalcAimLayerSequence( float *flCycle, float *flAimSequenceWeight, bool bForceIdle ) +{ + // TODO? + return m_pOuter->LookupSequence( "soldier_Aim_9_directions" ); +} + +void CSinglePlayerAnimState::UpdateLayerSequenceGeneric( int iLayer, bool &bEnabled, + float &flCurCycle, int &iSequence, bool bWaitAtEnd, + float fBlendIn, float fBlendOut, bool bMoveBlend, float fPlaybackRate, bool bUpdateCycle /* = true */ ) +{ + if ( !bEnabled ) + return; + + CStudioHdr *hdr = GetOuter()->GetModelPtr(); + if ( !hdr ) + return; + + if ( iSequence < 0 || iSequence >= hdr->GetNumSeq() ) + return; + + // Increment the fire sequence's cycle. + if ( bUpdateCycle ) + { + flCurCycle += m_pOuter->GetSequenceCycleRate( hdr, iSequence ) * gpGlobals->frametime * fPlaybackRate; + } + + // temp: if the sequence is looping, don't override it - we need better handling of looping anims, + // especially in misc layer from melee (right now the same melee attack is looped manually in asw_melee_system.cpp) + bool bLooping = m_pOuter->IsSequenceLooping( hdr, iSequence ); + + if ( flCurCycle > 1 && !bLooping ) + { + if ( iLayer == RELOADSEQUENCE_LAYER ) + { + m_bReloading = false; + } + if ( bWaitAtEnd ) + { + flCurCycle = 1; + } + else + { + // Not firing anymore. + bEnabled = false; + iSequence = 0; + return; + } + } + + // if this animation should blend out as we move, then check for dropping it completely since we're moving too fast + float speed = 0; + if (bMoveBlend) + { + Vector vel; + GetOuterAbsVelocity( vel ); + + float speed = vel.Length2D(); + + if (speed > 50) + { + bEnabled = false; + iSequence = 0; + return; + } + } + + // Now dump the state into its animation layer. + CAnimationLayer *pLayer = m_pOuter->GetAnimOverlay( iLayer ); + + pLayer->m_flCycle = flCurCycle; + pLayer->m_nSequence = iSequence; + + pLayer->m_flPlaybackRate = fPlaybackRate; + pLayer->m_flWeight = 1.0f; + + if (iLayer == RELOADSEQUENCE_LAYER) + { + // blend this layer in and out for smooth reloading + if (flCurCycle < fBlendIn && fBlendIn>0) + { + pLayer->m_flWeight = ( clamp(flCurCycle / fBlendIn, + 0.001f, 1.0f) ); + } + else if (flCurCycle >= (1.0f - fBlendOut) && fBlendOut>0) + { + pLayer->m_flWeight = ( clamp((1.0f - flCurCycle) / fBlendOut, + 0.001f, 1.0f) ); + } + else + { + pLayer->m_flWeight = 1.0f; + } + } + else + { + pLayer->m_flWeight = 1.0f; + } + if (bMoveBlend) + { + // blend the animation out as we move faster + if (speed <= 50) + pLayer->m_flWeight = ( pLayer->m_flWeight * (50.0f - speed) / 50.0f ); + } + +#ifndef CLIENT_DLL + pLayer->m_fFlags |= ANIM_LAYER_ACTIVE; +#endif + pLayer->SetOrder( iLayer ); +} + +void CSinglePlayerAnimState::ComputeFireSequence() +{ + UpdateLayerSequenceGeneric( FIRESEQUENCE_LAYER, m_bFiring, m_flFireCycle, m_iFireSequence, false ); +} + +void CSinglePlayerAnimState::ComputeReloadSequence() +{ + UpdateLayerSequenceGeneric( RELOADSEQUENCE_LAYER, m_bReloading, m_flReloadCycle, m_iReloadSequence, false, m_flReloadBlendIn, m_flReloadBlendOut, false, m_fReloadPlaybackRate ); +} + +void CSinglePlayerAnimState::ComputeWeaponSwitchSequence() +{ + UpdateLayerSequenceGeneric( RELOADSEQUENCE_LAYER, m_bWeaponSwitching, m_flWeaponSwitchCycle, m_iWeaponSwitchSequence, false, 0, 0.5f ); +} + +// does misc gestures if we're not firing +void CSinglePlayerAnimState::ComputeMiscSequence() +{ + UpdateLayerSequenceGeneric( RELOADSEQUENCE_LAYER, m_bPlayingMisc, m_flMiscCycle, m_iMiscSequence, m_bMiscHoldAtEnd, m_flMiscBlendIn, m_flMiscBlendOut, m_bMiscOnlyWhenStill, m_fMiscPlaybackRate ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : float +//----------------------------------------------------------------------------- +float CSinglePlayerAnimState::GetCurrentMaxGroundSpeed() +{ + CStudioHdr *pStudioHdr = GetOuter()->GetModelPtr(); + + if ( pStudioHdr == NULL ) + return 1.0f; + + int iMoveX = GetOuter()->LookupPoseParameter( "move_x" ); + int iMoveY = GetOuter()->LookupPoseParameter( "move_y" ); + + float prevX = GetOuter()->GetPoseParameter( iMoveX ); + float prevY = GetOuter()->GetPoseParameter( iMoveY ); + + float d = MAX( fabs( prevX ), fabs( prevY ) ); + float newX, newY; + if ( d == 0.0 ) + { + newX = 1.0; + newY = 0.0; + } + else + { + newX = prevX / d; + newY = prevY / d; + } + + GetOuter()->SetPoseParameter( pStudioHdr, iMoveX, newX ); + GetOuter()->SetPoseParameter( pStudioHdr, iMoveY, newY ); + + float speed = GetOuter()->GetSequenceGroundSpeed(GetOuter()->GetSequence() ); + + GetOuter()->SetPoseParameter( pStudioHdr, iMoveX, prevX ); + GetOuter()->SetPoseParameter( pStudioHdr, iMoveY, prevY ); + + return speed; +} + +//----------------------------------------------------------------------------- +// Purpose: Override for backpeddling +// Input : dt - +//----------------------------------------------------------------------------- +void CSinglePlayerAnimState::ComputePoseParam_BodyYaw( void ) +{ + CBasePlayerAnimState::ComputePoseParam_BodyYaw(); + + //ComputePoseParam_BodyLookYaw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSinglePlayerAnimState::ComputePoseParam_BodyLookYaw( void ) +{ + // See if we even have a blender for pitch + int upper_body_yaw = GetOuter()->LookupPoseParameter( "aim_yaw" ); + if ( upper_body_yaw < 0 ) + { + return; + } + + // Assume upper and lower bodies are aligned and that we're not turning + float flGoalTorsoYaw = 0.0f; + int turning = TURN_NONE; + float turnrate = 360.0f; + + Vector vel; + + GetOuterAbsVelocity( vel ); + + bool isMoving = ( vel.Length() > 1.0f ) ? true : false; + + if ( !isMoving ) + { + // Just stopped moving, try and clamp feet + if ( m_flLastTurnTime <= 0.0f ) + { + m_flLastTurnTime = gpGlobals->curtime; + m_flLastYaw = GetOuter()->EyeAngles().y; + // Snap feet to be perfectly aligned with torso/eyes + m_flGoalFeetYaw = GetOuter()->EyeAngles().y; + m_flCurrentFeetYaw = m_flGoalFeetYaw; + m_nTurningInPlace = TURN_NONE; + } + + // If rotating in place, update stasis timer + + if ( m_flLastYaw != GetOuter()->EyeAngles().y ) + { + m_flLastTurnTime = gpGlobals->curtime; + m_flLastYaw = GetOuter()->EyeAngles().y; + } + + if ( m_flGoalFeetYaw != m_flCurrentFeetYaw ) + { + m_flLastTurnTime = gpGlobals->curtime; + } + + turning = ConvergeAngles( m_flGoalFeetYaw, turnrate, m_AnimConfig.m_flMaxBodyYawDegrees, gpGlobals->frametime, m_flCurrentFeetYaw ); + + QAngle eyeAngles = GetOuter()->EyeAngles(); + QAngle vAngle = GetOuter()->GetLocalAngles(); + + // See how far off current feetyaw is from true yaw + float yawdelta = GetOuter()->EyeAngles().y - m_flCurrentFeetYaw; + yawdelta = AngleNormalize( yawdelta ); + + bool rotated_too_far = false; + + float yawmagnitude = fabs( yawdelta ); + + // If too far, then need to turn in place + if ( yawmagnitude > 45 ) + { + rotated_too_far = true; + } + + // Standing still for a while, rotate feet around to face forward + // Or rotated too far + // FIXME: Play an in place turning animation + if ( rotated_too_far || + ( gpGlobals->curtime > m_flLastTurnTime + mp_facefronttime.GetFloat() ) ) + { + m_flGoalFeetYaw = GetOuter()->EyeAngles().y; + m_flLastTurnTime = gpGlobals->curtime; + + /* float yd = m_flCurrentFeetYaw - m_flGoalFeetYaw; + if ( yd > 0 ) + { + m_nTurningInPlace = TURN_RIGHT; + } + else if ( yd < 0 ) + { + m_nTurningInPlace = TURN_LEFT; + } + else + { + m_nTurningInPlace = TURN_NONE; + } + + turning = ConvergeAngles( m_flGoalFeetYaw, turnrate, gpGlobals->frametime, m_flCurrentFeetYaw ); + yawdelta = GetOuter()->EyeAngles().y - m_flCurrentFeetYaw;*/ + + } + + // Snap upper body into position since the delta is already smoothed for the feet + flGoalTorsoYaw = yawdelta; + m_flCurrentTorsoYaw = flGoalTorsoYaw; + } + else + { + m_flLastTurnTime = 0.0f; + m_nTurningInPlace = TURN_NONE; + m_flCurrentFeetYaw = m_flGoalFeetYaw = GetOuter()->EyeAngles().y; + flGoalTorsoYaw = 0.0f; + m_flCurrentTorsoYaw = GetOuter()->EyeAngles().y - m_flCurrentFeetYaw; + } + + if ( turning == TURN_NONE ) + { + m_nTurningInPlace = turning; + } + + if ( m_nTurningInPlace != TURN_NONE ) + { + // If we're close to finishing the turn, then turn off the turning animation + if ( fabs( m_flCurrentFeetYaw - m_flGoalFeetYaw ) < MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION ) + { + m_nTurningInPlace = TURN_NONE; + } + } + + GetOuter()->SetPoseParameter( upper_body_yaw, clamp( m_flCurrentTorsoYaw, -60.0f, 60.0f ) ); + + /* + // FIXME: Adrian, what is this? + int body_yaw = GetOuter()->LookupPoseParameter( "body_yaw" ); + + if ( body_yaw >= 0 ) + { + GetOuter()->SetPoseParameter( body_yaw, 30 ); + } + */ + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSinglePlayerAnimState::ComputePoseParam_BodyPitch( CStudioHdr *pStudioHdr ) +{ + // Get pitch from v_angle + float flPitch = m_flEyePitch; + + if ( flPitch > 180.0f ) + { + flPitch -= 360.0f; + } + flPitch = clamp( flPitch, -90, 90 ); + + // See if we have a blender for pitch + GetOuter()->SetPoseParameter( pStudioHdr, "aim_pitch", flPitch ); + + ComputePoseParam_HeadPitch( pStudioHdr ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSinglePlayerAnimState::ComputePoseParam_HeadPitch( CStudioHdr *pStudioHdr ) +{ + // Get pitch from v_angle + int iHeadPitch = GetOuter()->LookupPoseParameter("head_pitch"); + + float flPitch = m_flEyePitch; + + if ( flPitch > 180.0f ) + { + flPitch -= 360.0f; + } + flPitch = clamp( flPitch, -90, 90 ); + + GetOuter()->SetPoseParameter( pStudioHdr, iHeadPitch, flPitch ); +} diff --git a/sp/src/game/shared/mapbase/singleplayer_animstate.h b/sp/src/game/shared/mapbase/singleplayer_animstate.h new file mode 100644 index 0000000000..a805dffc2d --- /dev/null +++ b/sp/src/game/shared/mapbase/singleplayer_animstate.h @@ -0,0 +1,110 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Single Player animation state 'handler'. This utility is used +// to evaluate the pose parameter value based on the direction +// and speed of the player. +// +// ------------------------------------------------------------------------------ +// +// This was originally based on the following VDC article: +// https://developer.valvesoftware.com/wiki/Fixing_the_player_animation_state_(Single_Player) +// +// It has been modified by Blixibon to derive from CBasePlayerAnimState instead and support 9-way blends. +// Much of the work done to make this derive from CBasePlayerAnimState utilized code from the Alien Swarm SDK. +// +//=============================================================================// + +#ifndef SINGLEPLAYER_ANIMSTATE_H +#define SINGLEPLAYER_ANIMSTATE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "cbase.h" +#include "base_playeranimstate.h" + +#ifdef CLIENT_DLL +#include "c_baseplayer.h" +#else +#include "player.h" +#endif + +#ifdef MAPBASE +// Special definition for differentiating between SP and HL2:DM anim states +#define SP_ANIM_STATE 1 +#endif + +class CSinglePlayerAnimState : public CBasePlayerAnimState +{ +public: + CSinglePlayerAnimState( CBasePlayer *pPlayer ); + + Activity CalcMainActivity(); + int CalcAimLayerSequence( float *flCycle, float *flAimSequenceWeight, bool bForceIdle ); + float GetCurrentMaxGroundSpeed(); + + void SetPlayerAnimation( PLAYER_ANIM playerAnim ); + Activity TranslateActivity( Activity actDesired ); + + void ComputeSequences( CStudioHdr *pStudioHdr ); + + void AddMiscSequence( int iSequence, float flBlendIn = 0.0f, float flBlendOut = 0.0f, float flPlaybackRate = 1.0f, bool bHoldAtEnd = false, bool bOnlyWhenStill = false ); + + void ClearAnimationState(); + void ClearAnimationLayers(); + +private: + + bool HandleJumping(); + + void ComputeFireSequence(); + void ComputeReloadSequence(); + void ComputeWeaponSwitchSequence(); + void ComputeMiscSequence(); + + void UpdateLayerSequenceGeneric( int iLayer, bool &bEnabled, float &flCurCycle, + int &iSequence, bool bWaitAtEnd, + float fBlendIn=0.15f, float fBlendOut=0.15f, bool bMoveBlend = false, + float fPlaybackRate=1.0f, bool bUpdateCycle = true ); + + void ComputePoseParam_BodyYaw( void ); + void ComputePoseParam_BodyPitch( CStudioHdr *pStudioHdr ); + void ComputePoseParam_BodyLookYaw( void ); + void ComputePoseParam_HeadPitch( CStudioHdr *pStudioHdr ); + + CBasePlayer* m_pPlayer; + + // Current state variables. + bool m_bJumping; // Set on a jump event. + float m_flJumpStartTime; + bool m_bFirstJumpFrame; + + // Aim sequence plays reload while this is on. + bool m_bReloading; + float m_flReloadCycle; + int m_iReloadSequence; + float m_flReloadBlendOut, m_flReloadBlendIn; + float m_fReloadPlaybackRate; + + bool m_bWeaponSwitching; + float m_flWeaponSwitchCycle; + int m_iWeaponSwitchSequence; + + bool m_bPlayingMisc; + float m_flMiscCycle, m_flMiscBlendOut, m_flMiscBlendIn; + int m_iMiscSequence; + bool m_bMiscOnlyWhenStill, m_bMiscHoldAtEnd; + bool m_bMiscNoOverride; + float m_fMiscPlaybackRate; + bool m_bMiscCycleRewound; + float m_flMiscRewindCycle; + // This is set to true if ANY animation is being played in the fire layer. + bool m_bFiring; // If this is on, then it'll continue the fire animation in the fire layer + // until it completes. + int m_iFireSequence; // (For any sequences in the fire layer, including grenade throw). + float m_flFireCycle; +}; + +CSinglePlayerAnimState *CreatePlayerAnimationState( CBasePlayer *pPlayer ); + +#endif // SINGLEPLAYER_ANIMSTATE_H diff --git a/sp/src/game/shared/mapbase/vscript_consts_shared.cpp b/sp/src/game/shared/mapbase/vscript_consts_shared.cpp index 31341fb9be..0bcdd8ae23 100644 --- a/sp/src/game/shared/mapbase/vscript_consts_shared.cpp +++ b/sp/src/game/shared/mapbase/vscript_consts_shared.cpp @@ -8,6 +8,8 @@ #include "cbase.h" #include "activitylist.h" #include "in_buttons.h" +#include "rope_shared.h" +#include "eventlist.h" #ifdef CLIENT_DLL #include "c_ai_basenpc.h" #else @@ -310,9 +312,102 @@ void RegisterSharedScriptConstants() ScriptRegisterConstant( g_pScriptVM, MOVETYPE_OBSERVER, "Move type used in GetMoveType(), etc." ); ScriptRegisterConstant( g_pScriptVM, MOVETYPE_CUSTOM, "Move type used in GetMoveType(), etc." ); + // + // Animation Stuff + // + ScriptRegisterConstant( g_pScriptVM, AE_TYPE_SERVER, "Animation event flag which indicates an event is supposed to be serverside only." ); + ScriptRegisterConstant( g_pScriptVM, AE_TYPE_SCRIPTED, "Animation event flag with an unknown purpose." ); + ScriptRegisterConstant( g_pScriptVM, AE_TYPE_SHARED, "Animation event flag which indicates an event is supposed to be shared between the server and client." ); + ScriptRegisterConstant( g_pScriptVM, AE_TYPE_WEAPON, "Animation event flag which indicates an event is part of a weapon." ); + ScriptRegisterConstant( g_pScriptVM, AE_TYPE_CLIENT, "Animation event flag which indicates an event is supposed to be clientside only." ); + ScriptRegisterConstant( g_pScriptVM, AE_TYPE_FACEPOSER, "Animation event flag with an unknown purpose. Presumably related to Faceposer." ); + ScriptRegisterConstant( g_pScriptVM, AE_TYPE_NEWEVENTSYSTEM, "Animation event flag which indicates an event is using the new system. This is often used by class-specific events from NPCs." ); + + // + // Ropes + // + ScriptRegisterConstant( g_pScriptVM, ROPE_RESIZE, "Try to keep the rope dangling the same amount even as the rope length changes. (for use in rope flags)" ); + ScriptRegisterConstant( g_pScriptVM, ROPE_BARBED, "Hack option to draw like a barbed wire. (for use in rope flags)" ); + ScriptRegisterConstant( g_pScriptVM, ROPE_COLLIDE, "Collide with the world. (for use in rope flags)" ); + ScriptRegisterConstant( g_pScriptVM, ROPE_SIMULATE, "Is the rope valid? (for use in rope flags)" ); + ScriptRegisterConstant( g_pScriptVM, ROPE_BREAKABLE, "Can the endpoints detach? (for use in rope flags)" ); + ScriptRegisterConstant( g_pScriptVM, ROPE_USE_WIND, "Wind simulation on this rope. (for use in rope flags)" ); + ScriptRegisterConstant( g_pScriptVM, ROPE_INITIAL_HANG, "By default, ropes will simulate for a bit internally when they are created so they sag, but dynamically created ropes for things like harpoons don't want this. (for use in rope flags)" ); + ScriptRegisterConstant( g_pScriptVM, ROPE_PLAYER_WPN_ATTACH, "If this flag is set, then the second attachment must be a player. The rope will attach to \"buff_attach\" on the player's active weapon. This is a flag because it requires special code on the client to find the weapon. (for use in rope flags)" ); + ScriptRegisterConstant( g_pScriptVM, ROPE_NO_GRAVITY, "Disable gravity on this rope. (for use in rope flags)" ); + ScriptRegisterConstant( g_pScriptVM, ROPE_NUMFLAGS, "The number of rope flags recognized by the game." ); + + ScriptRegisterConstantNamed( g_pScriptVM, Vector( ROPE_GRAVITY ), "ROPE_GRAVITY", "Default rope gravity vector." ); + + // + // Sounds + // + ScriptRegisterConstant( g_pScriptVM, CHAN_REPLACE, "The sound channel used when playing sounds through console commands." ); + ScriptRegisterConstant( g_pScriptVM, CHAN_AUTO, "The default generic sound channel." ); + ScriptRegisterConstant( g_pScriptVM, CHAN_WEAPON, "The sound channel for player and NPC weapons." ); + ScriptRegisterConstant( g_pScriptVM, CHAN_VOICE, "The sound channel used for dialogue, voice lines, etc." ); + ScriptRegisterConstant( g_pScriptVM, CHAN_ITEM, "The sound channel used for generic physics impact sounds, health/suit chargers, +use sounds." ); + ScriptRegisterConstant( g_pScriptVM, CHAN_BODY, "The sound channel used for clothing, ragdoll impacts, footsteps, knocking/pounding/punching etc." ); + ScriptRegisterConstant( g_pScriptVM, CHAN_STREAM, "The sound channel for sounds that can be delayed by an async load, i.e. aren't responses to particular events." ); + ScriptRegisterConstant( g_pScriptVM, CHAN_STATIC, "The sound channel for constant/background sound that doesn't require any reaction." ); + ScriptRegisterConstant( g_pScriptVM, CHAN_VOICE2, "An additional sound channel for voices. Used in TF2 for the announcer." ); + ScriptRegisterConstant( g_pScriptVM, CHAN_VOICE_BASE, "The sound channel used for network voice data (online voice communications)." ); + + ScriptRegisterConstant( g_pScriptVM, VOL_NORM, "The standard volume value." ); + ScriptRegisterConstant( g_pScriptVM, PITCH_NORM, "The standard pitch value." ); + ScriptRegisterConstant( g_pScriptVM, PITCH_LOW, "The standard low pitch value." ); + ScriptRegisterConstant( g_pScriptVM, PITCH_HIGH, "The standard high pitch value." ); + + ScriptRegisterConstant( g_pScriptVM, SNDLVL_NONE, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_20dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_25dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_30dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_35dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_40dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_45dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_50dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_55dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_IDLE, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_60dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_65dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_STATIC, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_70dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_NORM, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_75dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_80dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_TALKING, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_85dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_90dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_95dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_100dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_105dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_110dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_120dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_130dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_GUNFIRE, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_140dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_150dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_180dB, "A standard value used for a sound's sound level." ); + + ScriptRegisterConstant( g_pScriptVM, SND_CHANGE_VOL, "Indicates a sound is a volume change to an already-playing sound." ); + ScriptRegisterConstant( g_pScriptVM, SND_CHANGE_PITCH, "Indicates a sound is a pitch change to an already-playing sound." ); + ScriptRegisterConstant( g_pScriptVM, SND_STOP, "Indicates a sound is stopping an already-playing sound." ); + ScriptRegisterConstant( g_pScriptVM, SND_SPAWNING, "Indicates a sound is spawning, used in some cases for ambients. Not networked." ); + ScriptRegisterConstant( g_pScriptVM, SND_DELAY, "Indicates a sound has an initial delay." ); + ScriptRegisterConstant( g_pScriptVM, SND_STOP_LOOPING, "Stops all looping sounds on an entity." ); + ScriptRegisterConstant( g_pScriptVM, SND_SPEAKER, "Indicates a sound is being played again by a microphone through a speaker." ); + ScriptRegisterConstant( g_pScriptVM, SND_SHOULDPAUSE, "Forces a sound to pause if the game is paused." ); + ScriptRegisterConstant( g_pScriptVM, SND_IGNORE_PHONEMES, "Prevents the entity emitting this sound from using its phonemes (no lip-syncing)." ); + ScriptRegisterConstant( g_pScriptVM, SND_IGNORE_NAME, "Used to change all sounds emitted by an entity, regardless of name." ); + ScriptRegisterConstant( g_pScriptVM, SND_DO_NOT_OVERWRITE_EXISTING_ON_CHANNEL, "Prevents a sound from interrupting other sounds on a channel (if the channel supports interruption)." ); + + ScriptRegisterConstant( g_pScriptVM, GENDER_NONE, "A standard value used to represent no specific gender. Usually used for sounds." ); + ScriptRegisterConstant( g_pScriptVM, GENDER_MALE, "A standard value used to represent male gender. Usually used for sounds." ); + ScriptRegisterConstant( g_pScriptVM, GENDER_FEMALE, "A standard value used to represent female gender. Usually used for sounds." ); + #ifdef GAME_DLL // - // Sound Types, Contexts, and Channels + // AI Sounds // (QueryHearSound hook can use these) // ScriptRegisterConstant( g_pScriptVM, SOUND_NONE, "Sound type used in QueryHearSound hooks, etc." ); @@ -483,7 +578,7 @@ void RegisterSharedScriptConstants() //ScriptRegisterConstant( g_pScriptVM, AISS_AUTO_PVS_AFTER_PVS, "" ); ScriptRegisterConstant( g_pScriptVM, AI_SLEEP_FLAGS_NONE, "No sleep flags. (NPC sleep flag used in Add/Remove/HasSleepFlags())" ); ScriptRegisterConstant( g_pScriptVM, AI_SLEEP_FLAG_AUTO_PVS, "Indicates a NPC will sleep upon exiting PVS. (NPC sleep flag used in Add/Remove/HasSleepFlags())" ); - ScriptRegisterConstant( g_pScriptVM, AI_SLEEP_FLAG_AUTO_PVS_AFTER_PVS, "Indicates a NPC will sleep upon exiting PVS after entering PVS for the first time(?????) (NPC sleep flag used in Add/Remove/HasSleepFlags())" ); + ScriptRegisterConstant( g_pScriptVM, AI_SLEEP_FLAG_AUTO_PVS_AFTER_PVS, "Indicates a NPC will sleep upon exiting PVS after entering PVS for the first time(?) (NPC sleep flag used in Add/Remove/HasSleepFlags())" ); ScriptRegisterConstantNamed( g_pScriptVM, CAI_BaseNPC::SCRIPT_PLAYING, "SCRIPT_PLAYING", "Playing the action animation." ); ScriptRegisterConstantNamed( g_pScriptVM, CAI_BaseNPC::SCRIPT_WAIT, "SCRIPT_WAIT", "Waiting on everyone in the script to be ready. Plays the pre idle animation if there is one." ); @@ -492,6 +587,12 @@ void RegisterSharedScriptConstants() ScriptRegisterConstantNamed( g_pScriptVM, CAI_BaseNPC::SCRIPT_WALK_TO_MARK, "SCRIPT_WALK_TO_MARK", "Walking to the scripted sequence position." ); ScriptRegisterConstantNamed( g_pScriptVM, CAI_BaseNPC::SCRIPT_RUN_TO_MARK, "SCRIPT_RUN_TO_MARK", "Running to the scripted sequence position." ); ScriptRegisterConstantNamed( g_pScriptVM, CAI_BaseNPC::SCRIPT_PLAYING, "SCRIPT_PLAYING", "Moving to the scripted sequence position while playing a custom movement animation." ); + + ScriptRegisterConstant( g_pScriptVM, D_ER, "'Error' relationship definition. Used by NPCs and players for relationship disposition." ); + ScriptRegisterConstant( g_pScriptVM, D_HT, "Denotes a 'Hate' relationship. Used by NPCs and players for relationship disposition." ); + ScriptRegisterConstant( g_pScriptVM, D_FR, "Denotes a 'Fear' relationship. Used by NPCs and players for relationship disposition." ); + ScriptRegisterConstant( g_pScriptVM, D_LI, "Denotes a 'Like' relationship. Used by NPCs and players for relationship disposition." ); + ScriptRegisterConstant( g_pScriptVM, D_NU, "Denotes a 'Neutral' relationship. Used by NPCs and players for relationship disposition." ); #endif // diff --git a/sp/src/game/shared/mapbase/vscript_funcs_shared.cpp b/sp/src/game/shared/mapbase/vscript_funcs_shared.cpp index fed3ea1ba8..9f3d78b773 100644 --- a/sp/src/game/shared/mapbase/vscript_funcs_shared.cpp +++ b/sp/src/game/shared/mapbase/vscript_funcs_shared.cpp @@ -22,7 +22,11 @@ #include "globalstate.h" #include "vscript_server.h" #include "soundent.h" -#endif // !CLIENT_DLL +#include "rope.h" +#include "ai_basenpc.h" +#else +#include "c_rope.h" +#endif // CLIENT_DLL #include "con_nprint.h" #include "particle_parse.h" @@ -320,7 +324,7 @@ BEGIN_SCRIPTDESC_ROOT_NAMED( CTraceInfoAccessor, "CGameTrace", "Handle for acces DEFINE_SCRIPTFUNC( Destroy, "Deletes this instance. Important for preventing memory leaks." ) END_SCRIPTDESC(); -BEGIN_SCRIPTDESC_ROOT_NAMED( surfacedata_t, "surfacedata_t", "Handle for accessing surface data." ) +BEGIN_SCRIPTDESC_ROOT_NAMED( scriptsurfacedata_t, "surfacedata_t", "Handle for accessing surface data." ) DEFINE_SCRIPTFUNC( GetFriction, "The surface's friction." ) DEFINE_SCRIPTFUNC( GetThickness, "The surface's thickness." ) @@ -339,16 +343,16 @@ BEGIN_SCRIPTDESC_ROOT_NAMED( surfacedata_t, "surfacedata_t", "Handle for accessi DEFINE_SCRIPTFUNC( GetSoundStrain, "The surface's strain sound." ) END_SCRIPTDESC(); -const char* surfacedata_t::GetSoundStepLeft() { return physprops->GetString( sounds.stepleft ); } -const char* surfacedata_t::GetSoundStepRight() { return physprops->GetString( sounds.stepright ); } -const char* surfacedata_t::GetSoundImpactSoft() { return physprops->GetString( sounds.impactSoft ); } -const char* surfacedata_t::GetSoundImpactHard() { return physprops->GetString( sounds.impactHard ); } -const char* surfacedata_t::GetSoundScrapeSmooth() { return physprops->GetString( sounds.scrapeSmooth ); } -const char* surfacedata_t::GetSoundScrapeRough() { return physprops->GetString( sounds.scrapeRough ); } -const char* surfacedata_t::GetSoundBulletImpact() { return physprops->GetString( sounds.bulletImpact ); } -const char* surfacedata_t::GetSoundRolling() { return physprops->GetString( sounds.rolling ); } -const char* surfacedata_t::GetSoundBreak() { return physprops->GetString( sounds.breakSound ); } -const char* surfacedata_t::GetSoundStrain() { return physprops->GetString( sounds.strainSound ); } +const char* scriptsurfacedata_t::GetSoundStepLeft() { return physprops->GetString( sounds.stepleft ); } +const char* scriptsurfacedata_t::GetSoundStepRight() { return physprops->GetString( sounds.stepright ); } +const char* scriptsurfacedata_t::GetSoundImpactSoft() { return physprops->GetString( sounds.impactSoft ); } +const char* scriptsurfacedata_t::GetSoundImpactHard() { return physprops->GetString( sounds.impactHard ); } +const char* scriptsurfacedata_t::GetSoundScrapeSmooth() { return physprops->GetString( sounds.scrapeSmooth ); } +const char* scriptsurfacedata_t::GetSoundScrapeRough() { return physprops->GetString( sounds.scrapeRough ); } +const char* scriptsurfacedata_t::GetSoundBulletImpact() { return physprops->GetString( sounds.bulletImpact ); } +const char* scriptsurfacedata_t::GetSoundRolling() { return physprops->GetString( sounds.rolling ); } +const char* scriptsurfacedata_t::GetSoundBreak() { return physprops->GetString( sounds.breakSound ); } +const char* scriptsurfacedata_t::GetSoundStrain() { return physprops->GetString( sounds.strainSound ); } BEGIN_SCRIPTDESC_ROOT_NAMED( CSurfaceScriptAccessor, "csurface_t", "Handle for accessing csurface_t info." ) DEFINE_SCRIPTFUNC( Name, "The surface's name." ) @@ -503,16 +507,36 @@ FireBulletsInfo_t *GetFireBulletsInfoFromInfo( HSCRIPT hBulletsInfo ) } //----------------------------------------------------------------------------- -// +// animevent_t //----------------------------------------------------------------------------- CAnimEventTInstanceHelper g_AnimEventTInstanceHelper; -BEGIN_SCRIPTDESC_ROOT( animevent_t, "Handle for accessing animevent_t info." ) +BEGIN_SCRIPTDESC_ROOT( scriptanimevent_t, "Handle for accessing animevent_t info." ) DEFINE_SCRIPT_INSTANCE_HELPER( &g_AnimEventTInstanceHelper ) + + DEFINE_SCRIPTFUNC( GetEvent, "Gets the event number." ) + DEFINE_SCRIPTFUNC( SetEvent, "Sets the event number." ) + + DEFINE_SCRIPTFUNC( GetOptions, "Gets the event's options/parameters." ) + DEFINE_SCRIPTFUNC( SetOptions, "Sets the event's options/parameters." ) + + DEFINE_SCRIPTFUNC( GetCycle, "Gets the cycle at which the event happens." ) + DEFINE_SCRIPTFUNC( SetCycle, "Sets the cycle at which the event happens." ) + + DEFINE_SCRIPTFUNC( GetEventTime, "Gets the time the event plays." ) + DEFINE_SCRIPTFUNC( SetEventTime, "Sets the time the event plays." ) + + DEFINE_SCRIPTFUNC( GetType, "Gets the event's type flags. See the 'AE_TYPE_' set of constants for valid flags." ) + DEFINE_SCRIPTFUNC( SetType, "Sets the event's type flags. See the 'AE_TYPE_' set of constants for valid flags." ) + + DEFINE_SCRIPTFUNC( GetSource, "Gets the event's source entity." ) + DEFINE_SCRIPTFUNC( SetSource, "Sets the event's source entity." ) END_SCRIPTDESC(); bool CAnimEventTInstanceHelper::Get( void *p, const char *pszKey, ScriptVariant_t &variant ) { + DevWarning( "VScript animevent_t.%s: animevent_t metamethod members are deprecated! Use 'script_help animevent_t' to see the correct functions.\n", pszKey ); + animevent_t *ani = ((animevent_t *)p); if (FStrEq( pszKey, "event" )) variant = ani->event; @@ -534,6 +558,8 @@ bool CAnimEventTInstanceHelper::Get( void *p, const char *pszKey, ScriptVariant_ bool CAnimEventTInstanceHelper::Set( void *p, const char *pszKey, ScriptVariant_t &variant ) { + DevWarning( "VScript animevent_t.%s: animevent_t metamethod members are deprecated! Use 'script_help animevent_t' to see the correct functions.\n", pszKey ); + animevent_t *ani = ((animevent_t *)p); if (FStrEq( pszKey, "event" )) ani->event = variant; @@ -557,10 +583,58 @@ bool CAnimEventTInstanceHelper::Set( void *p, const char *pszKey, ScriptVariant_ return true; } +//----------------------------------------------------------------------------- +// EmitSound_t +//----------------------------------------------------------------------------- +BEGIN_SCRIPTDESC_ROOT_NAMED( ScriptEmitSound_t, "EmitSound_t", "Handle for accessing EmitSound_t info." ) + DEFINE_SCRIPT_CONSTRUCTOR() + + DEFINE_SCRIPTFUNC( GetChannel, "Gets the sound's channel." ) + DEFINE_SCRIPTFUNC( SetChannel, "Gets the sound's channel." ) + + DEFINE_SCRIPTFUNC( GetSoundName, "Gets the sound's file path or soundscript name." ) + DEFINE_SCRIPTFUNC( SetSoundName, "Sets the sound's file path or soundscript name." ) + + DEFINE_SCRIPTFUNC( GetVolume, "Gets the sound's volume. (Note that this may not apply to soundscripts)" ) + DEFINE_SCRIPTFUNC( SetVolume, "Sets the sound's volume. (Note that this may not apply to soundscripts)" ) + + DEFINE_SCRIPTFUNC( GetSoundLevel, "Gets the sound's level in decibels. (Note that this may not apply to soundscripts)" ) + DEFINE_SCRIPTFUNC( SetSoundLevel, "Sets the sound's level in decibels. (Note that this may not apply to soundscripts)" ) + + DEFINE_SCRIPTFUNC( GetFlags, "Gets the sound's flags. See the 'SND_' set of constants for more information." ) + DEFINE_SCRIPTFUNC( SetFlags, "Sets the sound's flags. See the 'SND_' set of constants for more information." ) + + DEFINE_SCRIPTFUNC( GetSpecialDSP, "Gets the sound's special DSP setting." ) + DEFINE_SCRIPTFUNC( SetSpecialDSP, "Sets the sound's special DSP setting." ) + + DEFINE_SCRIPTFUNC( HasOrigin, "Returns true if the sound has an origin override." ) + DEFINE_SCRIPTFUNC( GetOrigin, "Gets the sound's origin override." ) + DEFINE_SCRIPTFUNC( SetOrigin, "Sets the sound's origin override." ) + DEFINE_SCRIPTFUNC( ClearOrigin, "Clears the sound's origin override if it has one." ) + + DEFINE_SCRIPTFUNC( GetSoundTime, "Gets the time the sound will begin, relative to Time()." ) + DEFINE_SCRIPTFUNC( SetSoundTime, "Sets the time the sound will begin, relative to Time()." ) + + DEFINE_SCRIPTFUNC( GetEmitCloseCaption, "Gets whether or not the sound will emit closed captioning/subtitles." ) + DEFINE_SCRIPTFUNC( SetEmitCloseCaption, "Sets whether or not the sound will emit closed captioning/subtitles." ) + + DEFINE_SCRIPTFUNC( GetWarnOnMissingCloseCaption, "Gets whether or not the sound will send a message to the console if there is no corresponding closed captioning token." ) + DEFINE_SCRIPTFUNC( SetWarnOnMissingCloseCaption, "Sets whether or not the sound will send a message to the console if there is no corresponding closed captioning token." ) + + DEFINE_SCRIPTFUNC( GetWarnOnDirectWaveReference, "Gets whether or not the sound will send a message to the console if it references a direct sound file instead of a soundscript." ) + DEFINE_SCRIPTFUNC( SetWarnOnDirectWaveReference, "Sets whether or not the sound will send a message to the console if it references a direct sound file instead of a soundscript." ) + + DEFINE_SCRIPTFUNC( GetSpeakerEntity, "Gets the sound's original source if it is being transmitted by a microphone." ) + DEFINE_SCRIPTFUNC( SetSpeakerEntity, "Sets the sound's original source if it is being transmitted by a microphone." ) + + DEFINE_SCRIPTFUNC( GetSoundScriptHandle, "Gets the sound's script handle." ) + DEFINE_SCRIPTFUNC( SetSoundScriptHandle, "Sets the sound's script handle." ) +END_SCRIPTDESC(); + //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- -BEGIN_SCRIPTDESC_ROOT( CUserCmd, "Handle for accessing CUserCmd info." ) +BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptUserCmd, "CUserCmd", "Handle for accessing CUserCmd info." ) DEFINE_SCRIPTFUNC( GetCommandNumber, "For matching server and client commands for debugging." ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetTickCount, "GetTickCount", "The tick the client created this command." ) @@ -593,6 +667,32 @@ BEGIN_SCRIPTDESC_ROOT( CUserCmd, "Handle for accessing CUserCmd info." ) DEFINE_SCRIPTFUNC( SetMouseY, "Sets mouse accum in y from create move." ) END_SCRIPTDESC(); +#ifdef GAME_DLL +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +#define DEFINE_ENEMY_INFO_SCRIPTFUNCS(name, desc) \ + DEFINE_SCRIPTFUNC_NAMED( Get##name, #name, "Get " desc ) \ + DEFINE_SCRIPTFUNC( Set##name, "Set " desc ) + +BEGIN_SCRIPTDESC_ROOT_NAMED( Script_AI_EnemyInfo_t, "AI_EnemyInfo_t", "Accessor for information about an enemy." ) + DEFINE_SCRIPTFUNC( Enemy, "Get the enemy." ) + DEFINE_SCRIPTFUNC( SetEnemy, "Set the enemy." ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( LastKnownLocation, "the enemy's last known location." ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( LastSeenLocation, "the enemy's last seen location." ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( TimeLastSeen, "the last time the enemy was seen." ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( TimeFirstSeen, "the first time the enemy was seen." ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( TimeLastReacquired, "the last time the enemy was reaquired." ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( TimeValidEnemy, "the time at which the enemy can be selected (reaction delay)." ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( TimeLastReceivedDamageFrom, "the last time damage was received from this enemy." ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( TimeAtFirstHand, "the time at which the enemy was seen firsthand." ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( DangerMemory, "the memory of danger position w/o enemy pointer." ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( EludedMe, "whether the enemy is not at the last known location." ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( Unforgettable, "whether the enemy is unforgettable." ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( MobbedMe, "whether the enemy was part of a mob at some point." ) +END_SCRIPTDESC(); +#endif + //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- @@ -676,6 +776,7 @@ static void AddPhysVelocity( HSCRIPT hPhys, const Vector& vecVelocity, const Vec //============================================================================= //============================================================================= +#ifdef CLIENT_DLL static int ScriptPrecacheModel( const char *modelname ) { return CBaseEntity::PrecacheModel( modelname ); @@ -685,8 +786,17 @@ static void ScriptPrecacheOther( const char *classname ) { UTIL_PrecacheOther( classname ); } +#else +static int ScriptPrecacheModel( const char *modelname, bool bPreload ) +{ + return CBaseEntity::PrecacheModel( modelname, bPreload ); +} + +static void ScriptPrecacheOther( const char *classname, const char *modelName ) +{ + UTIL_PrecacheOther( classname, modelName ); +} -#ifndef CLIENT_DLL // TODO: Move this? static void ScriptInsertSound( int iType, const Vector &vecOrigin, int iVolume, float flDuration, HSCRIPT hOwner, int soundChannelIndex, HSCRIPT hSoundTarget ) { @@ -738,6 +848,45 @@ static void ScriptDecalTrace( HSCRIPT hTrace, const char *decalName ) UTIL_DecalTrace( &traceInfo->GetTrace(), decalName ); } +static HSCRIPT ScriptCreateRope( HSCRIPT hStart, HSCRIPT hEnd, int iStartAttachment, int iEndAttachment, float ropeWidth, const char *pMaterialName, int numSegments, int ropeFlags ) +{ +#ifdef CLIENT_DLL + C_RopeKeyframe *pRope = C_RopeKeyframe::Create( ToEnt( hStart ), ToEnt( hEnd ), iStartAttachment, iEndAttachment, ropeWidth, pMaterialName, numSegments, ropeFlags ); +#else + CRopeKeyframe *pRope = CRopeKeyframe::Create( ToEnt( hStart ), ToEnt( hEnd ), iStartAttachment, iEndAttachment, ropeWidth, pMaterialName, numSegments ); + if (pRope) + pRope->m_RopeFlags |= ropeFlags; // HACKHACK +#endif + + return ToHScript( pRope ); +} + +#ifndef CLIENT_DLL +static HSCRIPT ScriptCreateRopeWithSecondPointDetached( HSCRIPT hStart, int iStartAttachment, int ropeLength, float ropeWidth, const char *pMaterialName, int numSegments, bool initialHang, int ropeFlags ) +{ + CRopeKeyframe *pRope = CRopeKeyframe::CreateWithSecondPointDetached( ToEnt( hStart ), iStartAttachment, ropeLength, ropeWidth, pMaterialName, numSegments, initialHang ); + if (pRope) + pRope->m_RopeFlags |= ropeFlags; // HACKHACK + + return ToHScript( pRope ); +} +#endif + +static void EmitSoundParamsOn( HSCRIPT hParams, HSCRIPT hEnt ) +{ + CBaseEntity *pEnt = ToEnt( hEnt ); + if (!pEnt) + return; + + ScriptEmitSound_t *pParams = (ScriptEmitSound_t*)g_pScriptVM->GetInstanceValue( hParams, GetScriptDescForClass( ScriptEmitSound_t ) ); + if (!pParams) + return; + + CPASAttenuationFilter filter( pEnt, pParams->m_pSoundName ); + + CBaseEntity::EmitSound( filter, pEnt->entindex(), *pParams ); +} + //----------------------------------------------------------------------------- // Simple particle effect dispatch //----------------------------------------------------------------------------- @@ -791,7 +940,7 @@ bool ScriptIsClient() // Notification printing on the right edge of the screen void NPrint( int pos, const char* fmt ) { - engine->Con_NPrintf(pos, fmt); + engine->Con_NPrintf( pos, "%s", fmt ); } void NXPrint( int pos, int r, int g, int b, bool fixed, float ftime, const char* fmt ) @@ -805,7 +954,7 @@ void NXPrint( int pos, int r, int g, int b, bool fixed, float ftime, const char* info.color[2] = b / 255.f; info.fixed_width_font = fixed; - engine->Con_NXPrintf( &info, fmt ); + engine->Con_NXPrintf( &info, "%s", fmt ); } static float IntervalPerTick() @@ -857,7 +1006,6 @@ void RegisterSharedScriptFunctions() ScriptRegisterFunction( g_pScriptVM, CreateDamageInfo, "Creates damage info." ); ScriptRegisterFunction( g_pScriptVM, DestroyDamageInfo, "Destroys damage info." ); - ScriptRegisterFunction( g_pScriptVM, ImpulseScale, "Returns an impulse scale required to push an object." ); ScriptRegisterFunctionNamed( g_pScriptVM, ScriptCalculateExplosiveDamageForce, "CalculateExplosiveDamageForce", "Fill out a damage info handle with a damage force for an explosive." ); ScriptRegisterFunctionNamed( g_pScriptVM, ScriptCalculateBulletDamageForce, "CalculateBulletDamageForce", "Fill out a damage info handle with a damage force for a bullet impact." ); ScriptRegisterFunctionNamed( g_pScriptVM, ScriptCalculateMeleeDamageForce, "CalculateMeleeDamageForce", "Fill out a damage info handle with a damage force for a melee impact." ); @@ -890,6 +1038,8 @@ void RegisterSharedScriptFunctions() // #ifndef CLIENT_DLL ScriptRegisterFunctionNamed( g_pScriptVM, ScriptInsertSound, "InsertAISound", "Inserts an AI sound." ); + + ScriptRegisterFunctionNamed( g_pScriptVM, CAI_BaseNPC::GetActivityName, "GetActivityName", "Gets the name of the specified activity index." ); #endif // @@ -902,6 +1052,13 @@ void RegisterSharedScriptFunctions() ScriptRegisterFunctionNamed( g_pScriptVM, ScriptDecalTrace, "DecalTrace", "Creates a dynamic decal based on the given trace info. The trace information can be generated by TraceLineComplex() and the decal name must be from decals_subrect.txt." ); ScriptRegisterFunctionNamed( g_pScriptVM, ScriptDispatchParticleEffect, "DoDispatchParticleEffect", SCRIPT_ALIAS( "DispatchParticleEffect", "Dispatches a one-off particle system" ) ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptCreateRope, "CreateRope", "Creates a single rope between two entities. Can optionally follow specific attachments." ); +#ifndef CLIENT_DLL + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptCreateRopeWithSecondPointDetached, "CreateRopeWithSecondPointDetached", "Creates a single detached rope hanging from a point. Can optionally follow a specific start attachment." ); +#endif + + ScriptRegisterFunction( g_pScriptVM, EmitSoundParamsOn, "Play EmitSound_t params on an entity." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptMatcherMatch, "Matcher_Match", "Compares a string to a query using Mapbase's matcher system, supporting wildcards, RS matchers, etc." ); ScriptRegisterFunction( g_pScriptVM, Matcher_NamesMatch, "Compares a string to a query using Mapbase's matcher system using wildcards only." ); ScriptRegisterFunction( g_pScriptVM, AppearsToBeANumber, "Checks if the given string appears to be a number." ); diff --git a/sp/src/game/shared/mapbase/vscript_funcs_shared.h b/sp/src/game/shared/mapbase/vscript_funcs_shared.h index 0a38e5a15a..c7ad9ca718 100644 --- a/sp/src/game/shared/mapbase/vscript_funcs_shared.h +++ b/sp/src/game/shared/mapbase/vscript_funcs_shared.h @@ -11,13 +11,41 @@ #pragma once #endif +#include "npcevent.h" +#ifdef GAME_DLL +#include "ai_memory.h" +#endif + +//----------------------------------------------------------------------------- +// Exposes surfacedata_t to VScript +//----------------------------------------------------------------------------- +struct scriptsurfacedata_t : public surfacedata_t +{ + float GetFriction() { return physics.friction; } + float GetThickness() { return physics.thickness; } + + float GetJumpFactor() { return game.jumpFactor; } + char GetMaterialChar() { return game.material; } + + const char* GetSoundStepLeft(); + const char* GetSoundStepRight(); + const char* GetSoundImpactSoft(); + const char* GetSoundImpactHard(); + const char* GetSoundScrapeSmooth(); + const char* GetSoundScrapeRough(); + const char* GetSoundBulletImpact(); + const char* GetSoundRolling(); + const char* GetSoundBreak(); + const char* GetSoundStrain(); +}; + //----------------------------------------------------------------------------- // Exposes csurface_t to VScript //----------------------------------------------------------------------------- class CSurfaceScriptAccessor { public: - CSurfaceScriptAccessor( csurface_t &surf ) { m_surf = &surf; m_surfaceData = g_pScriptVM->RegisterInstance( physprops->GetSurfaceData( m_surf->surfaceProps ) ); } + CSurfaceScriptAccessor( csurface_t &surf ) { m_surf = &surf; m_surfaceData = g_pScriptVM->RegisterInstance( reinterpret_cast(physprops->GetSurfaceData( m_surf->surfaceProps )) ); } ~CSurfaceScriptAccessor() { delete m_surfaceData; } // cplane_t stuff @@ -124,10 +152,149 @@ class CTraceInfoAccessor //----------------------------------------------------------------------------- // Exposes animevent_t to VScript //----------------------------------------------------------------------------- +struct scriptanimevent_t : public animevent_t +{ + int GetEvent() { return event; } + void SetEvent( int nEvent ) { event = nEvent; } + + const char *GetOptions() { return options; } + void SetOptions( const char *pOptions ) { options = pOptions; } + + float GetCycle() { return cycle; } + void SetCycle( float flCycle ) { cycle = flCycle; } + + float GetEventTime() { return eventtime; } + void SetEventTime( float flEventTime ) { eventtime = flEventTime; } + + int GetType() { return type; } + void SetType( int nType ) { eventtime = type; } + + HSCRIPT GetSource() { return ToHScript( pSource ); } + void SetSource( HSCRIPT hSource ) + { + CBaseEntity *pEnt = ToEnt( hSource ); + if (pEnt) + pSource = pEnt->GetBaseAnimating(); + } +}; + class CAnimEventTInstanceHelper : public IScriptInstanceHelper { bool Get( void *p, const char *pszKey, ScriptVariant_t &variant ); bool Set( void *p, const char *pszKey, ScriptVariant_t &variant ); }; +//----------------------------------------------------------------------------- +// Exposes EmitSound_t to VScript +//----------------------------------------------------------------------------- +struct ScriptEmitSound_t : public EmitSound_t +{ + int GetChannel() { return m_nChannel; } + void SetChannel( int nChannel ) { m_nChannel = nChannel; } + + const char *GetSoundName() { return m_pSoundName; } + void SetSoundName( const char *pSoundName ) { m_pSoundName = pSoundName; } + + float GetVolume() { return m_flVolume; } + void SetVolume( float flVolume ) { m_flVolume = flVolume; } + + int GetSoundLevel() { return m_SoundLevel; } + void SetSoundLevel( int iSoundLevel ) { m_SoundLevel = (soundlevel_t)iSoundLevel; } + + int GetFlags() { return m_nFlags; } + void SetFlags( int nFlags ) { m_nFlags = nFlags; } + + int GetSpecialDSP() { return m_nSpecialDSP; } + void SetSpecialDSP( int nSpecialDSP ) { m_nSpecialDSP = nSpecialDSP; } + + bool HasOrigin() { return m_pOrigin ? true : false; } + const Vector &GetOrigin() { return m_pOrigin ? *m_pOrigin : vec3_origin; } + void SetOrigin( const Vector &origin ) { static Vector tempOrigin; tempOrigin = origin; m_pOrigin = &tempOrigin; } + void ClearOrigin() { m_pOrigin = NULL; } + + float GetSoundTime() { return m_flSoundTime; } + void SetSoundTime( float flSoundTime ) { m_flSoundTime = flSoundTime; } + + float GetEmitCloseCaption() { return m_bEmitCloseCaption; } + void SetEmitCloseCaption( bool bEmitCloseCaption ) { m_bEmitCloseCaption = bEmitCloseCaption; } + + float GetWarnOnMissingCloseCaption() { return m_bWarnOnMissingCloseCaption; } + void SetWarnOnMissingCloseCaption( bool bWarnOnMissingCloseCaption ) { m_bWarnOnMissingCloseCaption = bWarnOnMissingCloseCaption; } + + float GetWarnOnDirectWaveReference() { return m_bWarnOnDirectWaveReference; } + void SetWarnOnDirectWaveReference( bool bWarnOnDirectWaveReference ) { m_bWarnOnDirectWaveReference = bWarnOnDirectWaveReference; } + + int GetSpeakerEntity() { return m_nSpeakerEntity; } + void SetSpeakerEntity( int nSpeakerEntity ) { m_nSpeakerEntity = nSpeakerEntity; } + + int GetSoundScriptHandle() { return m_hSoundScriptHandle; } + void SetSoundScriptHandle( int hSoundScriptHandle ) { m_hSoundScriptHandle = hSoundScriptHandle; } +}; + +//----------------------------------------------------------------------------- +// Exposes CUserCmd to VScript +//----------------------------------------------------------------------------- +class CScriptUserCmd : public CUserCmd +{ +public: + int GetCommandNumber() { return command_number; } + + int ScriptGetTickCount() { return tick_count; } + + const QAngle& GetViewAngles() { return viewangles; } + void SetViewAngles( const QAngle& val ) { viewangles = val; } + + float GetForwardMove() { return forwardmove; } + void SetForwardMove( float val ) { forwardmove = val; } + float GetSideMove() { return sidemove; } + void SetSideMove( float val ) { sidemove = val; } + float GetUpMove() { return upmove; } + void SetUpMove( float val ) { upmove = val; } + + int GetButtons() { return buttons; } + void SetButtons( int val ) { buttons = val; } + int GetImpulse() { return impulse; } + void SetImpulse( int val ) { impulse = val; } + + int GetWeaponSelect() { return weaponselect; } + void SetWeaponSelect( int val ) { weaponselect = val; } + int GetWeaponSubtype() { return weaponsubtype; } + void SetWeaponSubtype( int val ) { weaponsubtype = val; } + + int GetRandomSeed() { return random_seed; } + + int GetMouseX() { return mousedx; } + void SetMouseX( int val ) { mousedx = val; } + int GetMouseY() { return mousedy; } + void SetMouseY( int val ) { mousedy = val; } +}; + +#ifdef GAME_DLL +//----------------------------------------------------------------------------- +// Exposes AI_EnemyInfo_t to VScript +//----------------------------------------------------------------------------- +struct Script_AI_EnemyInfo_t : public AI_EnemyInfo_t +{ + #define ENEMY_INFO_SCRIPT_FUNCS(type, name, var) \ + type Get##name() { return var; } \ + void Set##name( type v ) { var = v; } + + HSCRIPT Enemy() { return ToHScript(hEnemy); } + void SetEnemy( HSCRIPT ent ) { hEnemy = ToEnt(ent); } + + ENEMY_INFO_SCRIPT_FUNCS( Vector, LastKnownLocation, vLastKnownLocation ); + ENEMY_INFO_SCRIPT_FUNCS( Vector, LastSeenLocation, vLastSeenLocation ); + ENEMY_INFO_SCRIPT_FUNCS( float, TimeLastSeen, timeLastSeen ); + ENEMY_INFO_SCRIPT_FUNCS( float, TimeFirstSeen, timeFirstSeen ); + ENEMY_INFO_SCRIPT_FUNCS( float, TimeLastReacquired, timeLastReacquired ); + ENEMY_INFO_SCRIPT_FUNCS( float, TimeValidEnemy, timeValidEnemy ); + ENEMY_INFO_SCRIPT_FUNCS( float, TimeLastReceivedDamageFrom, timeLastReceivedDamageFrom ); + ENEMY_INFO_SCRIPT_FUNCS( float, TimeAtFirstHand, timeAtFirstHand ); + ENEMY_INFO_SCRIPT_FUNCS( bool, DangerMemory, bDangerMemory ); + ENEMY_INFO_SCRIPT_FUNCS( bool, EludedMe, bEludedMe ); + ENEMY_INFO_SCRIPT_FUNCS( bool, Unforgettable, bUnforgettable ); + ENEMY_INFO_SCRIPT_FUNCS( bool, MobbedMe, bMobbedMe ); +}; +#endif + #endif diff --git a/sp/src/game/shared/mapbase/vscript_singletons.cpp b/sp/src/game/shared/mapbase/vscript_singletons.cpp index c5e514996d..dc3092f1de 100644 --- a/sp/src/game/shared/mapbase/vscript_singletons.cpp +++ b/sp/src/game/shared/mapbase/vscript_singletons.cpp @@ -30,6 +30,10 @@ #include "c_te_legacytempents.h" #include "iefx.h" #include "dlight.h" + +#if !defined(NO_STEAM) +#include "steam/steam_api.h" +#endif #endif #include "vscript_singletons.h" @@ -38,7 +42,6 @@ #include "tier0/memdbgon.h" extern IScriptManager *scriptmanager; -CNetMsgScriptHelper *g_ScriptNetMsg = new CNetMsgScriptHelper(); //============================================================================= // Net Prop Manager @@ -363,6 +366,11 @@ END_SCRIPTDESC(); //============================================================================= // Game Event Listener // Based on Source 2 API +// +// NOTE: In Source 2 vscript (Lua) event listener contexts are tables that are +// passed to the callback function as the call environment. +// In mapbase implementation these are string identifiers because unlike Lua, +// Squirrel has closure methods such as 'bindenv' which can bind functions to specified environments. //============================================================================= // Define to use the older code that loads all events manually independent from the game event manager. @@ -372,7 +380,13 @@ END_SCRIPTDESC(); class CScriptGameEventListener : public IGameEventListener2, public CAutoGameSystem { public: - CScriptGameEventListener() : m_bActive(false) /*, m_nEventTick(0)*/ {} + CScriptGameEventListener() : m_bActive(false) + { +#ifdef _DEBUG + m_nEventTick = 0; +#endif + } + ~CScriptGameEventListener() { StopListeningForEvent(); @@ -394,10 +408,12 @@ class CScriptGameEventListener : public IGameEventListener2, public CAutoGameSys HSCRIPT m_hCallback; unsigned int m_iContextHash; bool m_bActive; - //int m_nEventTick; +#ifdef _DEBUG + int m_nEventTick; +#endif static StringHashFunctor Hash; - static inline unsigned int HashContext( const char* c ) { return (c && *c) ? Hash(c) : 0; } + static inline unsigned int HashContext( const char* c ) { return c ? Hash(c) : 0; } inline int GetIndex() { @@ -570,11 +586,10 @@ void CScriptGameEventListener::LoadEventsFromFile( const char *filename, const c void CScriptGameEventListener::DumpEventListeners() { CGMsg( 0, CON_GROUP_VSCRIPT, "--- Script game event listener dump start\n" ); - CGMsg( 0, CON_GROUP_VSCRIPT, "# ADDRESS ID CONTEXT\n" ); + CGMsg( 0, CON_GROUP_VSCRIPT, "# ID CONTEXT\n" ); FOR_EACH_VEC( s_Listeners, i ) { - CGMsg( 0, CON_GROUP_VSCRIPT, " %d (0x%p) %d : %u\n", i, - (void*)s_Listeners[i], + CGMsg( 0, CON_GROUP_VSCRIPT, " %d : %d : %u\n", i, s_Listeners[i]->GetIndex(), s_Listeners[i]->m_iContextHash ); } @@ -590,7 +605,9 @@ void CScriptGameEventListener::LevelShutdownPreEntity() void CScriptGameEventListener::FireGameEvent( IGameEvent *event ) { - //m_nEventTick = gpGlobals->tickcount; +#ifdef _DEBUG + m_nEventTick = gpGlobals->tickcount; +#endif ScriptVariant_t hTable; g_pScriptVM->CreateTable( hTable ); WriteEventData( event, hTable ); @@ -720,13 +737,15 @@ void CScriptGameEventListener::StopListeningForEvent() if ( gameeventmanager ) gameeventmanager->RemoveListener( this ); +#ifdef _DEBUG // Event listeners are iterated forwards in the game event manager, // removing while iterating will cause it to skip one listener. // This could be prevented by writing a custom game event manager. - //if ( m_nEventTick == gpGlobals->tickcount ) - //{ - // Warning("CScriptGameEventListener stopped in the same frame it was fired. This will break other event listeners!\n"); - //} + if ( m_nEventTick == gpGlobals->tickcount ) + { + Warning("CScriptGameEventListener stopped in the same frame it was fired. This will break other event listeners!\n"); + } +#endif } //----------------------------------------------------------------------------- @@ -751,14 +770,12 @@ bool CScriptGameEventListener::StopListeningToGameEvent( int listener ) void CScriptGameEventListener::StopListeningToAllGameEvents( const char* szContext ) { unsigned int hash = HashContext( szContext ); - - // Iterate from the end so they can be safely removed as they are deleted for ( int i = s_Listeners.Count(); i--; ) { CScriptGameEventListener *pCur = s_Listeners[i]; if ( pCur->m_iContextHash == hash ) { - s_Listeners.Remove(i); // keep list order + s_Listeners.FastRemove(i); delete pCur; } } @@ -835,6 +852,9 @@ static void FireGameEventLocal( const char* szEvent, HSCRIPT hTable ) } #endif // !CLIENT_DLL +static ScriptHook_t g_Hook_OnSave; +static ScriptHook_t g_Hook_OnRestore; + //============================================================================= // Save/Restore Utility // Based on L4D2 API @@ -852,6 +872,9 @@ class CScriptSaveRestoreUtil : public CAutoGameSystem { if ( g_pScriptVM ) { + g_Hook_OnSave.Call( NULL, NULL, NULL ); + + // Legacy hook HSCRIPT hFunc = g_pScriptVM->LookupFunction( "OnSave" ); if ( hFunc ) { @@ -870,6 +893,9 @@ class CScriptSaveRestoreUtil : public CAutoGameSystem { if ( g_pScriptVM ) { + g_Hook_OnRestore.Call( NULL, NULL, NULL ); + + // Legacy hook HSCRIPT hFunc = g_pScriptVM->LookupFunction( "OnRestore" ); if ( hFunc ) { @@ -985,7 +1011,7 @@ void CScriptSaveRestoreUtil::ClearSavedTable( const char *szId ) // Read/Write to File // Based on L4D2/Source 2 API //============================================================================= -#define SCRIPT_MAX_FILE_READ_SIZE (16 * 1024) // 16KB +#define SCRIPT_MAX_FILE_READ_SIZE (64 * 1024 * 1024) // 64MB #define SCRIPT_MAX_FILE_WRITE_SIZE (64 * 1024 * 1024) // 64MB #define SCRIPT_RW_PATH_ID "MOD" #define SCRIPT_RW_FULL_PATH_FMT "vscript_io/%s" @@ -997,6 +1023,7 @@ class CScriptReadWriteFile : public CAutoGameSystem public: static bool FileWrite( const char *szFile, const char *szInput ); static const char *FileRead( const char *szFile ); + static bool FileExists( const char *szFile ); // NOTE: These two functions are new with Mapbase and have no Valve equivalent static bool KeyValuesWrite( const char *szFile, HSCRIPT hInput ); @@ -1012,11 +1039,11 @@ class CScriptReadWriteFile : public CAutoGameSystem } private: - static const char *m_pszReturnReadFile; + static char *m_pszReturnReadFile; } g_ScriptReadWrite; -const char *CScriptReadWriteFile::m_pszReturnReadFile = NULL; +char *CScriptReadWriteFile::m_pszReturnReadFile = NULL; //----------------------------------------------------------------------------- // @@ -1074,19 +1101,49 @@ const char *CScriptReadWriteFile::FileRead( const char *szFile ) return NULL; } - CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); - if ( !g_pFullFileSystem->ReadFile( pszFullName, SCRIPT_RW_PATH_ID, buf, SCRIPT_MAX_FILE_READ_SIZE ) ) + FileHandle_t file = g_pFullFileSystem->Open( pszFullName, "rb", SCRIPT_RW_PATH_ID ); + if ( !file ) { return NULL; } - // first time calling, allocate - if ( !m_pszReturnReadFile ) - m_pszReturnReadFile = new char[SCRIPT_MAX_FILE_READ_SIZE]; + // Close the previous buffer + if (m_pszReturnReadFile) + g_pFullFileSystem->FreeOptimalReadBuffer( m_pszReturnReadFile ); - V_strncpy( const_cast(m_pszReturnReadFile), (const char*)buf.Base(), buf.Size() ); - buf.Purge(); - return m_pszReturnReadFile; + unsigned bufSize = g_pFullFileSystem->GetOptimalReadSize( file, size + 2 ); + m_pszReturnReadFile = (char*)g_pFullFileSystem->AllocOptimalReadBuffer( file, bufSize ); + + bool bRetOK = ( g_pFullFileSystem->ReadEx( m_pszReturnReadFile, bufSize, size, file ) != 0 ); + g_pFullFileSystem->Close( file ); // close file after reading + + if ( bRetOK ) + { + m_pszReturnReadFile[size] = 0; // null terminate file as EOF + //buffer[size+1] = 0; // double NULL terminating in case this is a unicode file + return m_pszReturnReadFile; + } + else + { + return NULL; + } +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CScriptReadWriteFile::FileExists( const char *szFile ) +{ + char pszFullName[MAX_PATH]; + V_snprintf( pszFullName, sizeof(pszFullName), SCRIPT_RW_FULL_PATH_FMT, szFile ); + + if ( !V_RemoveDotSlashes( pszFullName, CORRECT_PATH_SEPARATOR, true ) ) + { + DevWarning( 2, "Invalid file location : %s\n", szFile ); + return NULL; + } + + return g_pFullFileSystem->FileExists( pszFullName, SCRIPT_RW_PATH_ID ); } //----------------------------------------------------------------------------- @@ -1185,10 +1242,7 @@ HSCRIPT CScriptReadWriteFile::KeyValuesRead( const char *szFile ) return hScript; } -#undef SCRIPT_MAX_FILE_READ_SIZE -#undef SCRIPT_MAX_FILE_WRITE_SIZE -#undef SCRIPT_RW_PATH_ID -#undef SCRIPT_RW_FULL_PATH_FMT + //============================================================================= // Network message helper @@ -1198,6 +1252,8 @@ HSCRIPT CScriptReadWriteFile::KeyValuesRead( const char *szFile ) // The custom message name is hashed and sent as word with the message. //============================================================================= +static CNetMsgScriptHelper scriptnetmsg; +CNetMsgScriptHelper *g_ScriptNetMsg = &scriptnetmsg; #ifdef GAME_DLL #define m_MsgIn_() m_MsgIn-> @@ -2164,22 +2220,23 @@ END_SCRIPTDESC(); //============================================================================= // ConVars //============================================================================= -class CScriptConCommand : public ICommandCallback, public ICommandCompletionCallback +class CScriptConCommand : public ConCommand, public ICommandCallback, public ICommandCompletionCallback { + typedef ConCommand BaseClass; + public: ~CScriptConCommand() { Unregister(); - delete m_cmd; } - CScriptConCommand( const char *name, HSCRIPT fn, const char *helpString, int flags ) + CScriptConCommand( const char *name, HSCRIPT fn, const char *helpString, int flags, ConCommand *pLinked = NULL ) + : BaseClass( name, this, helpString, flags, 0 ), + m_pLinked(pLinked), + m_hCallback(fn), + m_hCompletionCallback(NULL) { - m_cmd = new ConCommand( name, this, helpString, flags, 0 ); - m_hCallback = fn; - m_hCompletionCallback = NULL; m_nCmdNameLen = V_strlen(name) + 1; - Assert( m_nCmdNameLen - 1 <= 128 ); } @@ -2191,10 +2248,15 @@ class CScriptConCommand : public ICommandCallback, public ICommandCompletionCall { vArgv[i] = command[i]; } - if ( g_pScriptVM->ExecuteFunction( m_hCallback, vArgv, count, NULL, NULL, true ) == SCRIPT_ERROR ) + ScriptVariant_t ret; + if ( g_pScriptVM->ExecuteFunction( m_hCallback, vArgv, count, &ret, NULL, true ) == SCRIPT_ERROR ) { DevWarning( 1, "CScriptConCommand: invalid callback for '%s'\n", command[0] ); } + if ( m_pLinked && (ret.m_type == FIELD_BOOLEAN) && ret.m_bool ) + { + m_pLinked->Dispatch( command ); + } } int CommandCompletionCallback( const char *partial, CUtlVector< CUtlString > &commands ) @@ -2249,17 +2311,17 @@ class CScriptConCommand : public ICommandCallback, public ICommandCompletionCall if (fn) { - if ( !m_cmd->IsRegistered() ) + if ( !BaseClass::IsRegistered() ) return; - m_cmd->m_pCommandCompletionCallback = this; - m_cmd->m_bHasCompletionCallback = true; + BaseClass::m_pCommandCompletionCallback = this; + BaseClass::m_bHasCompletionCallback = true; m_hCompletionCallback = fn; } else { - m_cmd->m_pCommandCompletionCallback = NULL; - m_cmd->m_bHasCompletionCallback = false; + BaseClass::m_pCommandCompletionCallback = NULL; + BaseClass::m_bHasCompletionCallback = false; m_hCompletionCallback = NULL; } } @@ -2268,7 +2330,7 @@ class CScriptConCommand : public ICommandCallback, public ICommandCompletionCall { if (fn) { - if ( !m_cmd->IsRegistered() ) + if ( !BaseClass::IsRegistered() ) Register(); if ( m_hCallback ) @@ -2283,8 +2345,8 @@ class CScriptConCommand : public ICommandCallback, public ICommandCompletionCall inline void Unregister() { - if ( g_pCVar && m_cmd->IsRegistered() ) - g_pCVar->UnregisterConCommand( m_cmd ); + if ( g_pCVar && BaseClass::IsRegistered() ) + g_pCVar->UnregisterConCommand( this ); if ( g_pScriptVM ) { @@ -2301,45 +2363,72 @@ class CScriptConCommand : public ICommandCallback, public ICommandCompletionCall inline void Register() { if ( g_pCVar ) - g_pCVar->RegisterConCommand( m_cmd ); + g_pCVar->RegisterConCommand( this ); } HSCRIPT m_hCallback; + ConCommand *m_pLinked; HSCRIPT m_hCompletionCallback; int m_nCmdNameLen; - ConCommand *m_cmd; }; -class CScriptConVar +class CScriptConVar : public ConVar { + typedef ConVar BaseClass; + public: ~CScriptConVar() { Unregister(); - delete m_cvar; } CScriptConVar( const char *pName, const char *pDefaultValue, const char *pHelpString, int flags/*, float fMin, float fMax*/ ) + : BaseClass( pName, pDefaultValue, flags, pHelpString ), + m_hCallback(NULL) + {} + + void SetChangeCallback( HSCRIPT fn ) { - m_cvar = new ConVar( pName, pDefaultValue, flags, pHelpString ); + void ScriptConVarCallback( IConVar*, const char*, float ); + + if ( m_hCallback ) + g_pScriptVM->ReleaseScript( m_hCallback ); + + if (fn) + { + m_hCallback = fn; + BaseClass::InstallChangeCallback( (FnChangeCallback_t)ScriptConVarCallback ); + } + else + { + m_hCallback = NULL; + BaseClass::InstallChangeCallback( NULL ); + } } inline void Unregister() { - if ( g_pCVar && m_cvar->IsRegistered() ) - g_pCVar->UnregisterConCommand( m_cvar ); + if ( g_pCVar && BaseClass::IsRegistered() ) + g_pCVar->UnregisterConCommand( this ); + + if ( g_pScriptVM ) + { + SetChangeCallback( NULL ); + } } - ConVar *m_cvar; + HSCRIPT m_hCallback; }; +static CUtlMap< unsigned int, bool > g_ConVarsBlocked( DefLessFunc(unsigned int) ); +static CUtlMap< unsigned int, bool > g_ConCommandsOverridable( DefLessFunc(unsigned int) ); +static CUtlMap< unsigned int, CScriptConCommand* > g_ScriptConCommands( DefLessFunc(unsigned int) ); +static CUtlMap< unsigned int, CScriptConVar* > g_ScriptConVars( DefLessFunc(unsigned int) ); + + class CScriptConvarAccessor : public CAutoGameSystem { public: - static CUtlMap< unsigned int, bool > g_ConVarsBlocked; - static CUtlMap< unsigned int, bool > g_ConCommandsOverridable; - static CUtlMap< unsigned int, CScriptConCommand* > g_ScriptConCommands; - static CUtlMap< unsigned int, CScriptConVar* > g_ScriptConVars; static inline unsigned int Hash( const char*sz ){ return HashStringCaseless(sz); } public: @@ -2351,9 +2440,7 @@ class CScriptConvarAccessor : public CAutoGameSystem inline bool IsOverridable( unsigned int hash ) { int idx = g_ConCommandsOverridable.Find( hash ); - if ( idx == g_ConCommandsOverridable.InvalidIndex() ) - return false; - return g_ConCommandsOverridable[idx]; + return ( idx != g_ConCommandsOverridable.InvalidIndex() ); } inline void AddBlockedConVar( const char *name ) @@ -2364,9 +2451,7 @@ class CScriptConvarAccessor : public CAutoGameSystem inline bool IsBlockedConvar( const char *name ) { int idx = g_ConVarsBlocked.Find( Hash(name) ); - if ( idx == g_ConVarsBlocked.InvalidIndex() ) - return false; - return g_ConVarsBlocked[idx]; + return ( idx != g_ConVarsBlocked.InvalidIndex() ); } public: @@ -2374,6 +2459,7 @@ class CScriptConvarAccessor : public CAutoGameSystem void SetCompletionCallback( const char *name, HSCRIPT fn ); void UnregisterCommand( const char *name ); void RegisterConvar( const char *name, const char *pDefaultValue, const char *helpString, int flags ); + void SetChangeCallback( const char *name, HSCRIPT fn ); HSCRIPT GetCommandClient() { @@ -2482,18 +2568,14 @@ class CScriptConvarAccessor : public CAutoGameSystem } g_ScriptConvarAccessor; -CUtlMap< unsigned int, bool > CScriptConvarAccessor::g_ConVarsBlocked( DefLessFunc(unsigned int) ); -CUtlMap< unsigned int, bool > CScriptConvarAccessor::g_ConCommandsOverridable( DefLessFunc(unsigned int) ); -CUtlMap< unsigned int, CScriptConCommand* > CScriptConvarAccessor::g_ScriptConCommands( DefLessFunc(unsigned int) ); -CUtlMap< unsigned int, CScriptConVar* > CScriptConvarAccessor::g_ScriptConVars( DefLessFunc(unsigned int) ); - void CScriptConvarAccessor::RegisterCommand( const char *name, HSCRIPT fn, const char *helpString, int flags ) { unsigned int hash = Hash(name); int idx = g_ScriptConCommands.Find(hash); if ( idx == g_ScriptConCommands.InvalidIndex() ) { - if ( g_pCVar->FindVar(name) || ( g_pCVar->FindCommand(name) && !IsOverridable(hash) ) ) + ConCommandBase *pBase = g_pCVar->FindCommandBase(name); + if ( pBase && ( !pBase->IsCommand() || !IsOverridable(hash) ) ) { DevWarning( 1, "CScriptConvarAccessor::RegisterCommand unable to register blocked ConCommand: %s\n", name ); return; @@ -2502,14 +2584,13 @@ void CScriptConvarAccessor::RegisterCommand( const char *name, HSCRIPT fn, const if ( !fn ) return; - CScriptConCommand *p = new CScriptConCommand( name, fn, helpString, flags ); + CScriptConCommand *p = new CScriptConCommand( name, fn, helpString, flags, static_cast< ConCommand* >(pBase) ); g_ScriptConCommands.Insert( hash, p ); } else { CScriptConCommand *pCmd = g_ScriptConCommands[idx]; pCmd->SetCallback( fn ); - pCmd->m_cmd->AddFlags( flags ); //CGMsg( 1, CON_GROUP_VSCRIPT, "CScriptConvarAccessor::RegisterCommand replacing command already registered: %s\n", name ); } } @@ -2541,7 +2622,7 @@ void CScriptConvarAccessor::RegisterConvar( const char *name, const char *pDefau int idx = g_ScriptConVars.Find(hash); if ( idx == g_ScriptConVars.InvalidIndex() ) { - if ( g_pCVar->FindVar(name) || g_pCVar->FindCommand(name) ) + if ( g_pCVar->FindCommandBase(name) ) { DevWarning( 1, "CScriptConvarAccessor::RegisterConvar unable to register blocked ConCommand: %s\n", name ); return; @@ -2552,11 +2633,39 @@ void CScriptConvarAccessor::RegisterConvar( const char *name, const char *pDefau } else { - g_ScriptConVars[idx]->m_cvar->AddFlags( flags ); //CGMsg( 1, CON_GROUP_VSCRIPT, "CScriptConvarAccessor::RegisterConvar convar %s already registered\n", name ); } } +void CScriptConvarAccessor::SetChangeCallback( const char *name, HSCRIPT fn ) +{ + unsigned int hash = Hash(name); + int idx = g_ScriptConVars.Find(hash); + if ( idx != g_ScriptConVars.InvalidIndex() ) + { + g_ScriptConVars[idx]->SetChangeCallback( fn ); + } +} + +void ScriptConVarCallback( IConVar *var, const char* pszOldValue, float flOldValue ) +{ + ConVar *cvar = (ConVar*)var; + const char *name = cvar->GetName(); + unsigned int hash = CScriptConvarAccessor::Hash( name ); + int idx = g_ScriptConVars.Find(hash); + if ( idx != g_ScriptConVars.InvalidIndex() ) + { + Assert( g_ScriptConVars[idx]->m_hCallback ); + + ScriptVariant_t args[5] = { name, pszOldValue, flOldValue, cvar->GetString(), cvar->GetFloat() }; + if ( g_pScriptVM->ExecuteFunction( g_ScriptConVars[idx]->m_hCallback, args, 5, NULL, NULL, true ) == SCRIPT_ERROR ) + { + DevWarning( 1, "CScriptConVar: invalid change callback for '%s'\n", name ); + } + } +} + + bool CScriptConvarAccessor::Init() { static bool bExecOnce = false; @@ -2584,6 +2693,7 @@ bool CScriptConvarAccessor::Init() AddOverridable( "+grenade1" ); AddOverridable( "+grenade2" ); AddOverridable( "+showscores" ); + AddOverridable( "+voicerecord" ); AddOverridable( "-attack" ); AddOverridable( "-attack2" ); @@ -2605,12 +2715,16 @@ bool CScriptConvarAccessor::Init() AddOverridable( "-grenade1" ); AddOverridable( "-grenade2" ); AddOverridable( "-showscores" ); + AddOverridable( "-voicerecord" ); AddOverridable( "toggle_duck" ); + AddOverridable( "impulse" ); + AddOverridable( "use" ); AddOverridable( "lastinv" ); AddOverridable( "invnext" ); AddOverridable( "invprev" ); AddOverridable( "phys_swap" ); + AddOverridable( "slot0" ); AddOverridable( "slot1" ); AddOverridable( "slot2" ); AddOverridable( "slot3" ); @@ -2618,10 +2732,16 @@ bool CScriptConvarAccessor::Init() AddOverridable( "slot5" ); AddOverridable( "slot6" ); AddOverridable( "slot7" ); + AddOverridable( "slot8" ); + AddOverridable( "slot9" ); + AddOverridable( "slot10" ); AddOverridable( "save" ); AddOverridable( "load" ); + AddOverridable( "say" ); + AddOverridable( "say_team" ); + AddBlockedConVar( "con_enable" ); AddBlockedConVar( "cl_allowdownload" ); @@ -2634,7 +2754,8 @@ bool CScriptConvarAccessor::Init() BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptConvarAccessor, "CConvars", SCRIPT_SINGLETON "Provides an interface to convars." ) DEFINE_SCRIPTFUNC( RegisterConvar, "register a new console variable." ) DEFINE_SCRIPTFUNC( RegisterCommand, "register a console command." ) - DEFINE_SCRIPTFUNC( SetCompletionCallback, "callback is called with 3 parameters (cmdname, partial, commands), user strings must be appended to 'commands' array" ) + DEFINE_SCRIPTFUNC( SetCompletionCallback, "callback is called with 3 parameters (cmd, partial, commands), user strings must be appended to 'commands' array" ) + DEFINE_SCRIPTFUNC( SetChangeCallback, "callback is called with 5 parameters (var, szOldValue, flOldValue, szNewValue, flNewValue)" ) DEFINE_SCRIPTFUNC( UnregisterCommand, "unregister a console command." ) DEFINE_SCRIPTFUNC( GetCommandClient, "returns the player who issued this console command." ) #ifdef GAME_DLL @@ -2671,9 +2792,6 @@ END_SCRIPTDESC(); class CEffectsScriptHelper { -private: - C_RecipientFilter filter; - public: void DynamicLight( int index, const Vector& origin, int r, int g, int b, int exponent, float radius, float die, float decay, int style = 0, int flags = 0 ) @@ -2694,6 +2812,7 @@ class CEffectsScriptHelper void Explosion( const Vector& pos, float scale, int radius, int magnitude, int flags ) { + C_RecipientFilter filter; filter.AddAllPlayers(); // framerate, modelindex, normal and materialtype are unused // radius for ragdolls @@ -2803,7 +2922,150 @@ BEGIN_SCRIPTDESC_ROOT_NAMED( CEffectsScriptHelper, "CEffects", SCRIPT_SINGLETON DEFINE_SCRIPTFUNC( Sprite, "" ) DEFINE_SCRIPTFUNC( ClientProjectile, "" ) END_SCRIPTDESC(); -#endif + + + +//============================================================================= +//============================================================================= + +extern CGlowObjectManager g_GlowObjectManager; + +class CScriptGlowObjectManager : public CAutoGameSystem +{ +public: + CUtlVector m_RegisteredObjects; + + void LevelShutdownPostEntity() + { + FOR_EACH_VEC( m_RegisteredObjects, i ) + g_GlowObjectManager.UnregisterGlowObject( m_RegisteredObjects[i] ); + m_RegisteredObjects.Purge(); + } + +public: + int Register( HSCRIPT hEntity, int r, int g, int b, int a, bool bRenderWhenOccluded, bool bRenderWhenUnoccluded ) + { + Vector vGlowColor; + vGlowColor.x = r * ( 1.0f / 255.0f ); + vGlowColor.y = g * ( 1.0f / 255.0f ); + vGlowColor.z = b * ( 1.0f / 255.0f ); + float flGlowAlpha = a * ( 1.0f / 255.0f ); + int idx = g_GlowObjectManager.RegisterGlowObject( ToEnt(hEntity), vGlowColor, flGlowAlpha, bRenderWhenOccluded, bRenderWhenUnoccluded, -1 ); + m_RegisteredObjects.AddToTail( idx ); + return idx; + } + + void Unregister( int nGlowObjectHandle ) + { + if ( (nGlowObjectHandle < 0) || (nGlowObjectHandle >= g_GlowObjectManager.m_GlowObjectDefinitions.Count()) ) + return; + g_GlowObjectManager.UnregisterGlowObject( nGlowObjectHandle ); + m_RegisteredObjects.FindAndFastRemove( nGlowObjectHandle ); + } + + void SetEntity( int nGlowObjectHandle, HSCRIPT hEntity ) + { + g_GlowObjectManager.SetEntity( nGlowObjectHandle, ToEnt(hEntity) ); + } + + void SetColor( int nGlowObjectHandle, int r, int g, int b ) + { + Vector vGlowColor; + vGlowColor.x = r * ( 1.0f / 255.0f ); + vGlowColor.y = g * ( 1.0f / 255.0f ); + vGlowColor.z = b * ( 1.0f / 255.0f ); + g_GlowObjectManager.SetColor( nGlowObjectHandle, vGlowColor ); + } + + void SetAlpha( int nGlowObjectHandle, int a ) + { + float flGlowAlpha = a * ( 1.0f / 255.0f ); + g_GlowObjectManager.SetAlpha( nGlowObjectHandle, flGlowAlpha ); + } + + void SetRenderFlags( int nGlowObjectHandle, bool bRenderWhenOccluded, bool bRenderWhenUnoccluded ) + { + g_GlowObjectManager.SetRenderFlags( nGlowObjectHandle, bRenderWhenOccluded, bRenderWhenUnoccluded ); + } + +} g_ScriptGlowObjectManager; + +BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptGlowObjectManager, "CGlowObjectManager", SCRIPT_SINGLETON "" ) + DEFINE_SCRIPTFUNC( Register, "( HSCRIPT hEntity, int r, int g, int b, int a, bool bRenderWhenOccluded, bool bRenderWhenUnoccluded )" ) + DEFINE_SCRIPTFUNC( Unregister, "" ) + DEFINE_SCRIPTFUNC( SetEntity, "" ) + DEFINE_SCRIPTFUNC( SetColor, "" ) + DEFINE_SCRIPTFUNC( SetAlpha, "" ) + DEFINE_SCRIPTFUNC( SetRenderFlags, "" ) +END_SCRIPTDESC(); + + +//============================================================================= +//============================================================================= + + +#if !defined(NO_STEAM) +class CScriptSteamAPI +{ +public: + int GetSecondsSinceComputerActive() + { + if ( !steamapicontext || !steamapicontext->SteamUtils() ) + return 0; + + return steamapicontext->SteamUtils()->GetSecondsSinceComputerActive(); + } + + int GetCurrentBatteryPower() + { + if ( !steamapicontext || !steamapicontext->SteamUtils() ) + return 0; + + return steamapicontext->SteamUtils()->GetCurrentBatteryPower(); + } + + const char *GetIPCountry() + { + if ( !steamapicontext || !steamapicontext->SteamUtils() ) + return NULL; + + const char *get = steamapicontext->SteamUtils()->GetIPCountry(); + if ( !get ) + return NULL; + + static char ret[3]; + V_strncpy( ret, get, 3 ); + + return ret; + } + + const char *GetCurrentGameLanguage() + { + if ( !steamapicontext || !steamapicontext->SteamApps() ) + return NULL; + + const char *lang = steamapicontext->SteamApps()->GetCurrentGameLanguage(); + if ( !lang ) + return NULL; + + static char ret[16]; + V_strncpy( ret, lang, sizeof(ret) ); + + return ret; + } + +} g_ScriptSteamAPI; + +BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptSteamAPI, "CSteamAPI", SCRIPT_SINGLETON "" ) + //DEFINE_SCRIPTFUNC( IsVACBanned, "" ) + DEFINE_SCRIPTFUNC( GetSecondsSinceComputerActive, "Returns the number of seconds since the user last moved the mouse." ) + DEFINE_SCRIPTFUNC( GetCurrentBatteryPower, "Return the amount of battery power left in the current system in % [0..100], 255 for being on AC power" ) + //DEFINE_SCRIPTFUNC( GetIPCountry, "Returns the 2 digit ISO 3166-1-alpha-2 format country code this client is running in (as looked up via an IP-to-location database)" ) + DEFINE_SCRIPTFUNC( GetCurrentGameLanguage, "Gets the current language that the user has set as API language code. This falls back to the Steam UI language if the user hasn't explicitly picked a language for the title." ) +END_SCRIPTDESC(); +#endif // !NO_STEAM + +#endif // CLIENT_DLL void RegisterScriptSingletons() @@ -2811,8 +3073,11 @@ void RegisterScriptSingletons() ScriptRegisterFunctionNamed( g_pScriptVM, CScriptSaveRestoreUtil::SaveTable, "SaveTable", "Store a table with primitive values that will persist across level transitions and save loads." ); ScriptRegisterFunctionNamed( g_pScriptVM, CScriptSaveRestoreUtil::RestoreTable, "RestoreTable", "Retrieves a table from storage. Write into input table." ); ScriptRegisterFunctionNamed( g_pScriptVM, CScriptSaveRestoreUtil::ClearSavedTable, "ClearSavedTable", "Removes the table with the given context." ); + ScriptRegisterSimpleHook( g_pScriptVM, g_Hook_OnSave, "OnSave", FIELD_VOID, "Called when the game is saved." ); + ScriptRegisterSimpleHook( g_pScriptVM, g_Hook_OnRestore, "OnRestore", FIELD_VOID, "Called when the game is restored." ); ScriptRegisterFunctionNamed( g_pScriptVM, CScriptReadWriteFile::FileWrite, "StringToFile", "Stores the string into the file" ); ScriptRegisterFunctionNamed( g_pScriptVM, CScriptReadWriteFile::FileRead, "FileToString", "Returns the string from the file, null if no file or file is too big." ); + ScriptRegisterFunctionNamed( g_pScriptVM, CScriptReadWriteFile::FileExists, "FileExists", "Returns true if the file exists." ); ScriptRegisterFunctionNamed( g_pScriptVM, CScriptReadWriteFile::KeyValuesWrite, "KeyValuesToFile", "Stores the CScriptKeyValues into the file" ); ScriptRegisterFunctionNamed( g_pScriptVM, CScriptReadWriteFile::KeyValuesRead, "FileToKeyValues", "Returns the CScriptKeyValues from the file, null if no file or file is too big." ); @@ -2831,6 +3096,11 @@ void RegisterScriptSingletons() g_pScriptVM->RegisterInstance( &g_ScriptConvarAccessor, "Convars" ); #ifdef CLIENT_DLL g_pScriptVM->RegisterInstance( &g_ScriptEffectsHelper, "effects" ); + g_pScriptVM->RegisterInstance( &g_ScriptGlowObjectManager, "GlowObjectManager" ); + +#if !defined(NO_STEAM) + g_pScriptVM->RegisterInstance( &g_ScriptSteamAPI, "steam" ); +#endif #endif // Singletons not unique to VScript (not declared or defined here) diff --git a/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp b/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp index 7bd4862c44..d9155f3219 100644 --- a/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp +++ b/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp @@ -398,9 +398,10 @@ bool CWeaponCustomScripted::Reload( void ) return BaseClass::Reload(); } -void CWeaponCustomScripted::Reload_NPC( void ) +void CWeaponCustomScripted::Reload_NPC( bool bPlaySound ) { - SIMPLE_VOID_OVERRIDE( Reload_NPC, NULL ); + ScriptVariant_t pArgs[] = { bPlaySound }; + SIMPLE_VOID_OVERRIDE( Reload_NPC, pArgs ); BaseClass::Reload_NPC(); } diff --git a/sp/src/game/shared/mapbase/weapon_custom_scripted.h b/sp/src/game/shared/mapbase/weapon_custom_scripted.h index f1493ce019..924f10310b 100644 --- a/sp/src/game/shared/mapbase/weapon_custom_scripted.h +++ b/sp/src/game/shared/mapbase/weapon_custom_scripted.h @@ -77,7 +77,7 @@ class CWeaponCustomScripted : public SCRIPTED_WEAPON_DERIVED_FROM void FinishReload( void ); void AbortReload( void ); bool Reload( void ); - void Reload_NPC( void ); + void Reload_NPC( bool bPlaySound = true ); // Weapon firing void PrimaryAttack( void ); // do "+ATTACK" diff --git a/sp/src/game/shared/movevars_shared.cpp b/sp/src/game/shared/movevars_shared.cpp index b5b94c72a2..563d96e36f 100644 --- a/sp/src/game/shared/movevars_shared.cpp +++ b/sp/src/game/shared/movevars_shared.cpp @@ -36,7 +36,7 @@ float GetCurrentGravity( void ) ConVar sv_gravity ( "sv_gravity", DEFAULT_GRAVITY_STRING, FCVAR_NOTIFY | FCVAR_REPLICATED, "World gravity." ); -#if defined( DOD_DLL ) || defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) +#if defined( DOD_DLL ) || defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) || defined( MAPBASE ) ConVar sv_stopspeed ( "sv_stopspeed","100", FCVAR_NOTIFY | FCVAR_REPLICATED, "Minimum stopping speed when on ground." ); #else ConVar sv_stopspeed ( "sv_stopspeed","100", FCVAR_NOTIFY | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Minimum stopping speed when on ground." ); @@ -48,7 +48,7 @@ ConVar sv_specaccelerate( "sv_specaccelerate", "5", FCVAR_NOTIFY | FCVAR_ARCHIVE ConVar sv_specspeed ( "sv_specspeed", "3", FCVAR_ARCHIVE | FCVAR_NOTIFY | FCVAR_REPLICATED); ConVar sv_specnoclip ( "sv_specnoclip", "1", FCVAR_ARCHIVE | FCVAR_NOTIFY | FCVAR_REPLICATED); -#if defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) +#if defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) || defined( MAPBASE ) ConVar sv_maxspeed ( "sv_maxspeed", "320", FCVAR_NOTIFY | FCVAR_REPLICATED); #else ConVar sv_maxspeed ( "sv_maxspeed", "320", FCVAR_NOTIFY | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY); @@ -58,7 +58,7 @@ ConVar sv_maxspeed ( "sv_maxspeed", "320", FCVAR_NOTIFY | FCVAR_REPLICATED | FC ConVar sv_accelerate ( "sv_accelerate", "7", FCVAR_NOTIFY | FCVAR_REPLICATED); #else -#if defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) +#if defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) || defined( MAPBASE ) ConVar sv_accelerate ( "sv_accelerate", "10", FCVAR_NOTIFY | FCVAR_REPLICATED); #else ConVar sv_accelerate ( "sv_accelerate", "10", FCVAR_NOTIFY | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY); @@ -66,7 +66,7 @@ ConVar sv_maxspeed ( "sv_maxspeed", "320", FCVAR_NOTIFY | FCVAR_REPLICATED | FC #endif//_XBOX -#if defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) +#if defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) || defined( MAPBASE ) ConVar sv_airaccelerate( "sv_airaccelerate", "10", FCVAR_NOTIFY | FCVAR_REPLICATED); ConVar sv_wateraccelerate( "sv_wateraccelerate", "10", FCVAR_NOTIFY | FCVAR_REPLICATED); ConVar sv_waterfriction( "sv_waterfriction", "1", FCVAR_NOTIFY | FCVAR_REPLICATED); @@ -82,13 +82,13 @@ ConVar sv_rollspeed ( "sv_rollspeed", "200", FCVAR_NOTIFY | FCVAR_REPLICATED | F ConVar sv_rollangle ( "sv_rollangle", "0", FCVAR_NOTIFY | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Max view roll angle"); #endif // CSTRIKE_DLL -#if defined( DOD_DLL ) || defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) +#if defined( DOD_DLL ) || defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) || defined( MAPBASE ) ConVar sv_friction ( "sv_friction","4", FCVAR_NOTIFY | FCVAR_REPLICATED, "World friction." ); #else ConVar sv_friction ( "sv_friction","4", FCVAR_NOTIFY | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "World friction." ); #endif // DOD_DLL || CSTRIKE_DLL -#if defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) +#if defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) || defined( MAPBASE ) ConVar sv_bounce ( "sv_bounce","0", FCVAR_NOTIFY | FCVAR_REPLICATED, "Bounce multiplier for when physically simulated objects collide with other objects." ); ConVar sv_maxvelocity ( "sv_maxvelocity","3500", FCVAR_REPLICATED, "Maximum speed any ballistically moving object is allowed to attain per axis." ); ConVar sv_stepsize ( "sv_stepsize","18", FCVAR_NOTIFY | FCVAR_REPLICATED ); diff --git a/sp/src/game/shared/ragdoll_shared.cpp b/sp/src/game/shared/ragdoll_shared.cpp index ded9377371..a9da261302 100644 --- a/sp/src/game/shared/ragdoll_shared.cpp +++ b/sp/src/game/shared/ragdoll_shared.cpp @@ -830,6 +830,33 @@ void CRagdollLRURetirement::Update( float frametime ) // EPISODIC VERSION m_iRagdollCount = 0; m_iSimulatedRagdollCount = 0; +#ifdef MAPBASE // From Alien Swarm SDK + // remove ragdolls with a forced retire time + for ( i = m_LRU.Head(); i < m_LRU.InvalidIndex(); i = next ) + { + next = m_LRU.Next(i); + + CBaseAnimating *pRagdoll = m_LRU[i].Get(); + + //Just ignore it until we're done burning/dissolving. + if ( pRagdoll && pRagdoll->GetEffectEntity() ) + continue; + + // ignore if it's not time to force retire this ragdoll + if ( m_LRU[i].GetForcedRetireTime() == 0.0f || gpGlobals->curtime < m_LRU[i].GetForcedRetireTime() ) + continue; + + //Msg(" Removing ragdoll %s due to forced retire time of %f (now = %f)\n", pRagdoll->GetModelName(), m_LRU[i].GetForcedRetireTime(), gpGlobals->curtime ); + +#ifdef CLIENT_DLL + pRagdoll->SUB_Remove(); +#else + pRagdoll->SUB_StartFadeOut( 0 ); +#endif + m_LRU.Remove(i); + } +#endif + // First, find ragdolls that are good candidates for deletion because they are not // visible at all, or are in a culled visibility box for ( i = m_LRU.Head(); i < m_LRU.InvalidIndex(); i = next ) @@ -847,12 +874,12 @@ void CRagdollLRURetirement::Update( float frametime ) // EPISODIC VERSION if ( m_LRU.Count() > iMaxRagdollCount ) { //Found one, we're done. - if ( ShouldRemoveThisRagdoll( m_LRU[i] ) == true ) + if ( ShouldRemoveThisRagdoll( pRagdoll ) == true ) { #ifdef CLIENT_DLL - m_LRU[ i ]->SUB_Remove(); + pRagdoll->SUB_Remove(); #else - m_LRU[ i ]->SUB_StartFadeOut( 0 ); + pRagdoll->SUB_StartFadeOut( 0 ); #endif m_LRU.Remove(i); @@ -933,10 +960,11 @@ void CRagdollLRURetirement::Update( float frametime ) // EPISODIC VERSION } } + CBaseAnimating *pRemoveRagdoll = m_LRU[ furthestOne ].Get(); #ifdef CLIENT_DLL - m_LRU[ furthestOne ]->SUB_Remove(); + pRemoveRagdoll->SUB_Remove(); #else - m_LRU[ furthestOne ]->SUB_StartFadeOut( 0 ); + pRemoveRagdoll->SUB_StartFadeOut( 0 ); #endif } @@ -957,9 +985,9 @@ void CRagdollLRURetirement::Update( float frametime ) // EPISODIC VERSION continue; #ifdef CLIENT_DLL - m_LRU[ i ]->SUB_Remove(); + pRagdoll->SUB_Remove(); #else - m_LRU[ i ]->SUB_StartFadeOut( 0 ); + pRagdoll->SUB_StartFadeOut( 0 ); #endif m_LRU.Remove(i); } @@ -989,6 +1017,33 @@ void CRagdollLRURetirement::Update( float frametime ) // Non-episodic version m_iRagdollCount = 0; m_iSimulatedRagdollCount = 0; +#ifdef MAPBASE // From Alien Swarm SDK + // remove ragdolls with a forced retire time + for ( i = m_LRU.Head(); i < m_LRU.InvalidIndex(); i = next ) + { + next = m_LRU.Next(i); + + CBaseAnimating *pRagdoll = m_LRU[i].Get(); + + //Just ignore it until we're done burning/dissolving. + if ( pRagdoll && pRagdoll->GetEffectEntity() ) + continue; + + // ignore if it's not time to force retire this ragdoll + if ( m_LRU[i].GetForcedRetireTime() == 0.0f || gpGlobals->curtime < m_LRU[i].GetForcedRetireTime() ) + continue; + + //Msg(" Removing ragdoll %s due to forced retire time of %f (now = %f)\n", pRagdoll->GetModelName(), m_LRU[i].GetForcedRetireTime(), gpGlobals->curtime ); + +#ifdef CLIENT_DLL + pRagdoll->SUB_Remove(); +#else + pRagdoll->SUB_StartFadeOut( 0 ); +#endif + m_LRU.Remove(i); + } +#endif + for ( i = m_LRU.Head(); i < m_LRU.InvalidIndex(); i = next ) { next = m_LRU.Next(i); @@ -1004,12 +1059,12 @@ void CRagdollLRURetirement::Update( float frametime ) // Non-episodic version if ( m_LRU.Count() > iMaxRagdollCount ) { //Found one, we're done. - if ( ShouldRemoveThisRagdoll( m_LRU[i] ) == true ) + if ( ShouldRemoveThisRagdoll( pRagdoll ) == true ) { #ifdef CLIENT_DLL - m_LRU[ i ]->SUB_Remove(); + pRagdoll->SUB_Remove(); #else - m_LRU[ i ]->SUB_StartFadeOut( 0 ); + pRagdoll->SUB_StartFadeOut( 0 ); #endif m_LRU.Remove(i); @@ -1053,9 +1108,9 @@ void CRagdollLRURetirement::Update( float frametime ) // Non-episodic version #endif #ifdef CLIENT_DLL - m_LRU[ i ]->SUB_Remove(); + pRagdoll->SUB_Remove(); #else - m_LRU[ i ]->SUB_StartFadeOut( 0 ); + pRagdoll->SUB_StartFadeOut( 0 ); #endif m_LRU.Remove(i); } @@ -1074,11 +1129,19 @@ ConVar g_ragdoll_important_maxcount( "g_ragdoll_important_maxcount", "2", FCVAR_ //----------------------------------------------------------------------------- // Move it to the top of the LRU //----------------------------------------------------------------------------- +#ifdef MAPBASE // From Alien Swarm SDK +void CRagdollLRURetirement::MoveToTopOfLRU( CBaseAnimating *pRagdoll, bool bImportant, float flForcedRetireTime ) +#else void CRagdollLRURetirement::MoveToTopOfLRU( CBaseAnimating *pRagdoll, bool bImportant ) +#endif { if ( bImportant ) { +#ifdef MAPBASE // From Alien Swarm SDK + m_LRUImportantRagdolls.AddToTail( CRagdollEntry( pRagdoll, flForcedRetireTime ) ); +#else m_LRUImportantRagdolls.AddToTail( pRagdoll ); +#endif if ( m_LRUImportantRagdolls.Count() > g_ragdoll_important_maxcount.GetInt() ) { @@ -1108,7 +1171,11 @@ void CRagdollLRURetirement::MoveToTopOfLRU( CBaseAnimating *pRagdoll, bool bImpo } } +#ifdef MAPBASE // From Alien Swarm SDK + m_LRU.AddToTail( CRagdollEntry( pRagdoll, flForcedRetireTime ) ); +#else m_LRU.AddToTail( pRagdoll ); +#endif } diff --git a/sp/src/game/shared/ragdoll_shared.h b/sp/src/game/shared/ragdoll_shared.h index f230e99f1c..5f4f705808 100644 --- a/sp/src/game/shared/ragdoll_shared.h +++ b/sp/src/game/shared/ragdoll_shared.h @@ -27,7 +27,11 @@ class CBoneAccessor; #include "bone_accessor.h" // UNDONE: Remove and make dynamic? +#ifdef MAPBASE +#define RAGDOLL_MAX_ELEMENTS 32 // Mapbase boosts this limit to the level of later Source games. +#else #define RAGDOLL_MAX_ELEMENTS 24 +#endif #define RAGDOLL_INDEX_BITS 5 // NOTE 1<= RAGDOLL_MAX_ELEMENTS #define CORE_DISSOLVE_FADE_START 0.2f @@ -79,6 +83,22 @@ struct ragdollparams_t bool fixedConstraints; }; +#ifdef MAPBASE // From Alien Swarm SDK +class CRagdollEntry +{ +public: + CRagdollEntry( CBaseAnimating *pRagdoll, float flForcedRetireTime ) : m_hRagdoll( pRagdoll ), m_flForcedRetireTime( flForcedRetireTime ) + { + } + CBaseAnimating* Get() { return m_hRagdoll.Get(); } + float GetForcedRetireTime() { return m_flForcedRetireTime; } + +private: + CHandle m_hRagdoll; + float m_flForcedRetireTime; +}; +#endif + //----------------------------------------------------------------------------- // This hooks the main game systems callbacks to allow the AI system to manage memory //----------------------------------------------------------------------------- @@ -94,7 +114,11 @@ class CRagdollLRURetirement : public CAutoGameSystemPerFrame virtual void FrameUpdatePostEntityThink( void ); // Move it to the top of the LRU +#ifdef MAPBASE // From Alien Swarm SDK + void MoveToTopOfLRU( CBaseAnimating *pRagdoll, bool bImportant = false, float flForcedRetireTime = 0.0f ); +#else void MoveToTopOfLRU( CBaseAnimating *pRagdoll, bool bImportant = false ); +#endif void SetMaxRagdollCount( int iMaxCount ){ m_iMaxRagdolls = iMaxCount; } virtual void LevelInitPreEntity( void ); @@ -102,8 +126,13 @@ class CRagdollLRURetirement : public CAutoGameSystemPerFrame private: typedef CHandle CRagdollHandle; +#ifdef MAPBASE + CUtlLinkedList< CRagdollEntry > m_LRU; + CUtlLinkedList< CRagdollEntry > m_LRUImportantRagdolls; +#else CUtlLinkedList< CRagdollHandle > m_LRU; CUtlLinkedList< CRagdollHandle > m_LRUImportantRagdolls; +#endif int m_iMaxRagdolls; int m_iSimulatedRagdollCount; diff --git a/sp/src/game/shared/shareddefs.h b/sp/src/game/shared/shareddefs.h index d30843dcdf..7e533ceb74 100644 --- a/sp/src/game/shared/shareddefs.h +++ b/sp/src/game/shared/shareddefs.h @@ -366,6 +366,14 @@ enum PLAYER_ANIM PLAYER_RELOAD, PLAYER_START_AIMING, PLAYER_LEAVE_AIMING, + +#ifdef MAPBASE + // New player animations from Mapbase + PLAYER_ATTACK2, + PLAYER_ATTACK3, + PLAYER_UNHOLSTER, + PLAYER_HOLSTER, +#endif }; #ifdef HL2_DLL @@ -1048,4 +1056,16 @@ enum }; #endif // TF_DLL || TF_CLIENT_DLL +#ifdef MAPBASE +// Developer commentary types +enum +{ + COMMENTARY_TYPE_AUDIO, // Play commentary audio (default) + + COMMENTARY_TYPE_TEXT, // Display text data + COMMENTARY_TYPE_IMAGE, // Display an image + COMMENTARY_TYPE_SCENE, // Play a VCD file +}; +#endif + #endif // SHAREDDEFS_H diff --git a/sp/src/game/shared/usercmd.h b/sp/src/game/shared/usercmd.h index 6b87f888d6..04ff44ed03 100644 --- a/sp/src/game/shared/usercmd.h +++ b/sp/src/game/shared/usercmd.h @@ -127,39 +127,6 @@ class CUserCmd impulse = 0; } -#ifdef MAPBASE_VSCRIPT // These functions are needed for exposing CUserCmd to VScript. - int GetCommandNumber() { return command_number; } - - int ScriptGetTickCount() { return tick_count; } - - const QAngle& GetViewAngles() { return viewangles; } - void SetViewAngles( const QAngle& val ) { viewangles = val; } - - float GetForwardMove() { return forwardmove; } - void SetForwardMove( float val ) { forwardmove = val; } - float GetSideMove() { return sidemove; } - void SetSideMove( float val ) { sidemove = val; } - float GetUpMove() { return upmove; } - void SetUpMove( float val ) { upmove = val; } - - int GetButtons() { return buttons; } - void SetButtons( int val ) { buttons = val; } - int GetImpulse() { return impulse; } - void SetImpulse( int val ) { impulse = val; } - - int GetWeaponSelect() { return weaponselect; } - void SetWeaponSelect( int val ) { weaponselect = val; } - int GetWeaponSubtype() { return weaponsubtype; } - void SetWeaponSubtype( int val ) { weaponsubtype = val; } - - int GetRandomSeed() { return random_seed; } - - int GetMouseX() { return mousedx; } - void SetMouseX( int val ) { mousedx = val; } - int GetMouseY() { return mousedy; } - void SetMouseY( int val ) { mousedy = val; } -#endif - // For matching server and client commands for debugging int command_number; diff --git a/sp/src/game/shared/util_shared.cpp b/sp/src/game/shared/util_shared.cpp index f1dd7e0022..be44201d54 100644 --- a/sp/src/game/shared/util_shared.cpp +++ b/sp/src/game/shared/util_shared.cpp @@ -1126,7 +1126,7 @@ float CountdownTimer::Now( void ) const #endif -char* ReadAndAllocStringValue( KeyValues *pSub, const char *pName, const char *pFilename ) +const char* ReadAndAllocStringValue( KeyValues *pSub, const char *pName, const char *pFilename ) { const char *pValue = pSub->GetString( pName, NULL ); if ( !pValue ) diff --git a/sp/src/game/shared/util_shared.h b/sp/src/game/shared/util_shared.h index dc8523e302..98f6ba4ebb 100644 --- a/sp/src/game/shared/util_shared.h +++ b/sp/src/game/shared/util_shared.h @@ -606,7 +606,7 @@ class CountdownTimer float Now( void ) const; // work-around since client header doesn't like inlined gpGlobals->curtime }; -char* ReadAndAllocStringValue( KeyValues *pSub, const char *pName, const char *pFilename = NULL ); +const char* ReadAndAllocStringValue( KeyValues *pSub, const char *pName, const char *pFilename = NULL ); int UTIL_StringFieldToInt( const char *szValue, const char **pValueStrings, int iNumStrings ); diff --git a/sp/src/game/shared/weapon_parse.cpp b/sp/src/game/shared/weapon_parse.cpp index 51dcff107e..d8844916e4 100644 --- a/sp/src/game/shared/weapon_parse.cpp +++ b/sp/src/game/shared/weapon_parse.cpp @@ -401,6 +401,14 @@ FileWeaponInfo_t::FileWeaponInfo_t() bShowUsageHint = false; m_bAllowFlipping = true; m_bBuiltRightHanded = true; +#ifdef MAPBASE + m_flViewmodelFOV = 0.0f; + m_flBobScale = 1.0f; + m_flSwayScale = 1.0f; + m_flSwaySpeedScale = 1.0f; + szDroppedModel[0] = 0; + m_bUsesHands = false; +#endif } #ifdef CLIENT_DLL @@ -466,6 +474,17 @@ void FileWeaponInfo_t::Parse( KeyValues *pKeyValuesData, const char *szWeaponNam m_bAllowFlipping = ( pKeyValuesData->GetInt( "AllowFlipping", 1 ) != 0 ) ? true : false; m_bMeleeWeapon = ( pKeyValuesData->GetInt( "MeleeWeapon", 0 ) != 0 ) ? true : false; +#ifdef MAPBASE + m_flViewmodelFOV = pKeyValuesData->GetFloat( "viewmodel_fov", 0.0f ); + m_flBobScale = pKeyValuesData->GetFloat( "bob_scale", 1.0f ); + m_flSwayScale = pKeyValuesData->GetFloat( "sway_scale", 1.0f ); + m_flSwaySpeedScale = pKeyValuesData->GetFloat( "sway_speed_scale", 1.0f ); + + Q_strncpy( szDroppedModel, pKeyValuesData->GetString( "droppedmodel" ), MAX_WEAPON_STRING ); + + m_bUsesHands = ( pKeyValuesData->GetInt( "uses_hands", 0 ) != 0 ) ? true : false; +#endif + #ifndef MAPBASE // Mapbase makes weapons in the same slot & position swap each other out, which is a feature mods can intentionally use. #if defined(_DEBUG) && defined(HL2_CLIENT_DLL) // make sure two weapons aren't in the same slot & position diff --git a/sp/src/game/shared/weapon_parse.h b/sp/src/game/shared/weapon_parse.h index 56803dfa69..280327447d 100644 --- a/sp/src/game/shared/weapon_parse.h +++ b/sp/src/game/shared/weapon_parse.h @@ -116,6 +116,17 @@ class FileWeaponInfo_t bool m_bAllowFlipping; // False to disallow flipping the model, regardless of whether // it is built left or right handed. +#ifdef MAPBASE + float m_flViewmodelFOV; + float m_flBobScale; + float m_flSwayScale; + float m_flSwaySpeedScale; + + char szDroppedModel[MAX_WEAPON_STRING]; // Model of this weapon when dropped on the ground + + bool m_bUsesHands; +#endif + // CLIENT DLL // Sprite data, read from the data file int iSpriteCount; diff --git a/sp/src/materialsystem/stdshaders/BaseVSShader.cpp b/sp/src/materialsystem/stdshaders/BaseVSShader.cpp index 49aafc10e2..98e6d6c2ab 100644 --- a/sp/src/materialsystem/stdshaders/BaseVSShader.cpp +++ b/sp/src/materialsystem/stdshaders/BaseVSShader.cpp @@ -55,6 +55,9 @@ static ConVar mat_fullbright( "mat_fullbright","0", FCVAR_CHEAT ); ConVar r_flashlightbrightness( "r_flashlightbrightness", "0.25", FCVAR_CHEAT ); #ifdef MAPBASE +// This constant should change with each Mapbase update +ConVar mapbase_version_shaders( "mapbase_version_shaders", MAPBASE_VERSION, FCVAR_NONE, "The version of Mapbase currently being used in this mod's game_shader_dx9.dll" ); + ConVar mat_specular_disable_on_missing( "mat_specular_disable_on_missing", "1", FCVAR_ARCHIVE, "Disables specular reflections on a material when the envmap cannot be found." ); #endif diff --git a/sp/src/materialsystem/stdshaders/SDK_core_ps2x.fxc b/sp/src/materialsystem/stdshaders/SDK_core_ps2x.fxc new file mode 100644 index 0000000000..3db01c242e --- /dev/null +++ b/sp/src/materialsystem/stdshaders/SDK_core_ps2x.fxc @@ -0,0 +1,222 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: +// +//============================================================================= + +// STATIC: "CONVERT_TO_SRGB" "0..1" [ps20b][= g_pHardwareConfig->NeedsShaderSRGBConversion()] [PC] +// STATIC: "CONVERT_TO_SRGB" "0..0" [= 0] [XBOX] +// STATIC: "CUBEMAP" "0..1" +// STATIC: "FLOWMAP" "0..1" +// STATIC: "CORECOLORTEXTURE" "0..1" +// STATIC: "REFRACT" "0..1" +// DYNAMIC: "PIXELFOGTYPE" "0..1" + +// SKIP: ( $REFRACT || $CORECOLORTEXTURE ) && $CUBEMAP + +#include "common_ps_fxc.h" + +sampler RefractSampler : register( s2 ); +sampler NormalSampler : register( s3 ); +#if CUBEMAP +sampler EnvmapSampler : register( s4 ); +#endif +#if FLOWMAP +sampler FlowmapSampler : register( s6 ); +#endif + +#if CORECOLORTEXTURE +sampler CoreColorSampler : register( s7 ); +#endif + +const HALF3 g_EnvmapTint : register( c0 ); +const HALF3 g_RefractTint : register( c1 ); +const HALF3 g_EnvmapContrast : register( c2 ); +const HALF3 g_EnvmapSaturation : register( c3 ); +const HALF2 g_RefractScale : register( c5 ); +#if FLOWMAP +const float g_Time : register( c6 ); +const float2 g_FlowScrollRate : register( c7 ); +const float g_CoreColorTexCoordOffset : register( c9 ); +#endif + +const float3 g_EyePos : register( c8 ); +const float4 g_FogParams : register( c11 ); + + +const float3 g_SphereCenter : register( c12 ); +const float3 g_SphereRadius : register( c15 ); + +float LengthThroughSphere( float3 vecRayOrigin, float3 vecRayDelta, + float3 vecSphereCenter, float flRadius, out float alpha ) +{ + // Solve using the ray equation + the sphere equation + // P = o + dt + // (x - xc)^2 + (y - yc)^2 + (z - zc)^2 = r^2 + // (ox + dx * t - xc)^2 + (oy + dy * t - yc)^2 + (oz + dz * t - zc)^2 = r^2 + // (ox - xc)^2 + 2 * (ox-xc) * dx * t + dx^2 * t^2 + + // (oy - yc)^2 + 2 * (oy-yc) * dy * t + dy^2 * t^2 + + // (oz - zc)^2 + 2 * (oz-zc) * dz * t + dz^2 * t^2 = r^2 + // (dx^2 + dy^2 + dz^2) * t^2 + 2 * ((ox-xc)dx + (oy-yc)dy + (oz-zc)dz) t + + // (ox-xc)^2 + (oy-yc)^2 + (oz-zc)^2 - r^2 = 0 + // or, t = (-b +/- sqrt( b^2 - 4ac)) / 2a + // a = DotProduct( vecRayDelta, vecRayDelta ); + // b = 2 * DotProduct( vecRayOrigin - vecCenter, vecRayDelta ) + // c = DotProduct(vecRayOrigin - vecCenter, vecRayOrigin - vecCenter) - flRadius * flRadius; + + float3 vecSphereToRay; + vecSphereToRay = vecRayOrigin - vecSphereCenter; + + float a = dot( vecRayDelta, vecRayDelta ); + + // This would occur in the case of a zero-length ray + // if ( a == 0.0f ) + // { + // *pT1 = *pT2 = 0.0f; + // return vecSphereToRay.LengthSqr() <= flRadius * flRadius; + // } + + float b = 2 * dot( vecSphereToRay, vecRayDelta ); + float c = dot( vecSphereToRay, vecSphereToRay ) - flRadius * flRadius; + float flDiscrim = b * b - 4 * a * c; + // if ( flDiscrim < 0.0f ) + // return 0.0f; + + float hack = flDiscrim; + flDiscrim = sqrt( flDiscrim ); + float oo2a = 0.5f / a; + + + //if( hack < 0.0f ) + //{ + // alpha = 0.0f; + // return 0.0f; + //} + //else + //{ + // alpha = 1.0f; + // return abs( flDiscrim ) * 2 * oo2a; + //} + + //replacing the if's above because if's in hlsl are bad..... + float fHackGreaterThanZero = step( 0.0f, hack ); + alpha = fHackGreaterThanZero; + return (fHackGreaterThanZero * (abs( flDiscrim ) * 2 * oo2a)); + + + // *pT1 = ( - b - flDiscrim ) * oo2a; + // *pT2 = ( - b + flDiscrim ) * oo2a; + // return true; +} + + +struct PS_INPUT +{ + float2 vBumpTexCoord : TEXCOORD0; // dudvMapAndNormalMapTexCoord + HALF3 vWorldVertToEyeVector : TEXCOORD1; + HALF3x3 tangentSpaceTranspose : TEXCOORD2; + float3 vRefractXYW : TEXCOORD5; + float3 projNormal : TEXCOORD6; + float4 worldPos_projPosZ : TEXCOORD7; +}; + +float4 main( PS_INPUT i ) : COLOR +{ + HALF3 result = 0.0f; + + HALF blend = 1.0f; + +#if FLOWMAP + // Mapbase tries to un-hack some of this code + //float3 g_SphereCenter = { 2688.0f, 12139.0f, 5170.0f }; + //float g_SphereDiameter = 430.0f; + //float g_SphereRadius = g_SphereDiameter * 0.5f; + + float g_SphereDiameter = g_SphereRadius * 2.0f; + + float3 tmp = i.worldPos_projPosZ.xyz - g_SphereCenter; + float hackRadius = 1.05f * sqrt( dot( tmp, tmp ) ); + + float sphereAlpha; + float lengthThroughSphere = LengthThroughSphere( g_EyePos, normalize( i.worldPos_projPosZ.xyz - g_EyePos ), + g_SphereCenter, /*g_SphereRadius*/ hackRadius, sphereAlpha ); + + float normalizedLengthThroughSphere = lengthThroughSphere / g_SphereDiameter; + + + float3 hackWorldSpaceNormal = normalize( i.worldPos_projPosZ.xyz - g_SphereCenter ); + float3 realFuckingNormal = abs( hackWorldSpaceNormal ); + hackWorldSpaceNormal = 0.5f * ( hackWorldSpaceNormal + 1.0f ); + + // hackWorldSpaceNormal = abs( hackWorldSpaceNormal ); + + // return float4( hackWorldSpaceNormal.x, 0.0f, 0.0f, 1.0f ); + + i.vBumpTexCoord.xy = 0.0f; + i.vBumpTexCoord.xy = realFuckingNormal.z * tex2D( FlowmapSampler, hackWorldSpaceNormal.xy ); + i.vBumpTexCoord.xy += realFuckingNormal.y * tex2D( FlowmapSampler, hackWorldSpaceNormal.xz ); + i.vBumpTexCoord.xy += realFuckingNormal.x * tex2D( FlowmapSampler, hackWorldSpaceNormal.yz ); + i.vBumpTexCoord.xy += g_Time * g_FlowScrollRate; + // return float4( i.vBumpTexCoord.xy, 0.0f, 0.0f ); +#endif + + // Load normal and expand range + HALF4 vNormalSample = tex2D( NormalSampler, i.vBumpTexCoord ); + // return vNormalSample; + HALF3 tangentSpaceNormal = vNormalSample * 2.0 - 1.0; + + HALF3 refractTintColor = g_RefractTint; + + // Perform division by W only once + float ooW = 1.0f / i.vRefractXYW.z; + + // Compute coordinates for sampling refraction + float2 vRefractTexCoordNoWarp = i.vRefractXYW.xy * ooW; + float2 vRefractTexCoord = tangentSpaceNormal.xy; + HALF scale = vNormalSample.a * g_RefractScale.x; +#if FLOWMAP + scale *= normalizedLengthThroughSphere; +#endif + vRefractTexCoord *= scale; +#if FLOWMAP + float2 hackOffset = vRefractTexCoord; +#endif + vRefractTexCoord += vRefractTexCoordNoWarp; + + float3 colorWarp = tex2D( RefractSampler, vRefractTexCoord.xy ); + float3 colorNoWarp = tex2D( RefractSampler, vRefractTexCoordNoWarp.xy ); + + colorWarp *= refractTintColor; +#if REFRACT + result = lerp( colorNoWarp, colorWarp, blend ); + // return float4( 1.0f, 0.0f, 0.0f, 1.0f ); +#endif + +#if CUBEMAP + HALF specularFactor = vNormalSample.a; + + HALF3 worldSpaceNormal = mul( i.tangentSpaceTranspose, tangentSpaceNormal ); + + HALF3 reflectVect = CalcReflectionVectorUnnormalized( worldSpaceNormal, i.vWorldVertToEyeVector ); + HALF3 specularLighting = texCUBE( EnvmapSampler, reflectVect ); + specularLighting *= specularFactor; + specularLighting *= g_EnvmapTint; + HALF3 specularLightingSquared = specularLighting * specularLighting; + specularLighting = lerp( specularLighting, specularLightingSquared, g_EnvmapContrast ); + HALF3 greyScale = dot( specularLighting, HALF3( 0.299f, 0.587f, 0.114f ) ); + specularLighting = lerp( greyScale, specularLighting, g_EnvmapSaturation ); + result += specularLighting; +#endif + +#if CORECOLORTEXTURE && FLOWMAP + float4 coreColorTexel = tex2D( CoreColorSampler, hackOffset + float2( normalizedLengthThroughSphere, g_CoreColorTexCoordOffset ) ); + HALF4 rgba = HALF4( lerp( result, coreColorTexel, coreColorTexel.a /*normalizedLengthThroughSphere*/ ), sphereAlpha ); +#else + HALF4 rgba = HALF4( result, vNormalSample.a ); +#endif + + + float fogFactor = CalcPixelFogFactor( PIXELFOGTYPE, g_FogParams, g_EyePos.z, i.worldPos_projPosZ.z, i.worldPos_projPosZ.w ); + return FinalOutput( rgba, fogFactor, PIXELFOGTYPE, TONEMAP_SCALE_NONE ); +} + diff --git a/sp/src/materialsystem/stdshaders/SDK_core_vs20.fxc b/sp/src/materialsystem/stdshaders/SDK_core_vs20.fxc new file mode 100644 index 0000000000..9b7c965289 --- /dev/null +++ b/sp/src/materialsystem/stdshaders/SDK_core_vs20.fxc @@ -0,0 +1,103 @@ +// STATIC: "MODEL" "0..1" + +// DYNAMIC: "COMPRESSED_VERTS" "0..1" +// DYNAMIC: "SKINNING" "0..1" + +#include "common_vs_fxc.h" + +static const bool g_bSkinning = SKINNING ? true : false; +static const bool g_bModel = MODEL ? true : false; + +const float4 cBumpTexCoordTransform[2] : register( SHADER_SPECIFIC_CONST_1 ); + +struct VS_INPUT +{ + float4 vPos : POSITION; + float4 vBoneWeights : BLENDWEIGHT; + float4 vBoneIndices : BLENDINDICES; + float4 vNormal : NORMAL; + float4 vBaseTexCoord : TEXCOORD0; +#if !MODEL + float3 vTangentS : TANGENT; + float3 vTangentT : BINORMAL0; +#else + float4 vUserData : TANGENT; +#endif +}; + +struct VS_OUTPUT +{ + float4 vProjPos_POSITION : POSITION; + float vFog : FOG; + float2 vBumpTexCoord : TEXCOORD0; + float3 vTangentEyeVect : TEXCOORD1; + float3x3 tangentSpaceTranspose : TEXCOORD2; + float3 vRefractXYW : TEXCOORD5; + float4 projNormal_screenCoordW : TEXCOORD6; + float4 worldPos_projPosZ : TEXCOORD7; + float4 fogFactorW : COLOR1; +}; + +VS_OUTPUT main( const VS_INPUT v ) +{ + VS_OUTPUT o = ( VS_OUTPUT )0; + + float3 worldNormal, worldPos, worldTangentS, worldTangentT; + + float3 vObjNormal; +#if MODEL + float4 vObjTangent; + DecompressVertex_NormalTangent( v.vNormal, v.vUserData, vObjNormal, vObjTangent ); + + SkinPositionNormalAndTangentSpace( + g_bSkinning, + v.vPos, vObjNormal, vObjTangent, + v.vBoneWeights, v.vBoneIndices, + worldPos, worldNormal, worldTangentS, worldTangentT ); +#else + DecompressVertex_Normal( v.vNormal, vObjNormal ); + + worldPos = mul( v.vPos, cModel[0] ); + worldTangentS = mul( v.vTangentS, ( const float3x3 )cModel[0] ); + worldTangentT = mul( v.vTangentT, ( const float3x3 )cModel[0] ); + worldNormal = mul( vObjNormal, ( float3x3 )cModel[0] ); +#endif + + + // Projected position + float4 vProjPos = mul( float4( worldPos, 1 ), cViewProj ); + o.vProjPos_POSITION = vProjPos; + o.projNormal_screenCoordW.xyz = mul( worldNormal, cViewProj ); + + o.worldPos_projPosZ = float4( worldPos.xyz, vProjPos.z ); + + // Map projected position to the refraction texture + float2 vRefractPos; + vRefractPos.x = vProjPos.x; + vRefractPos.y = -vProjPos.y; // invert Y + vRefractPos = (vRefractPos + vProjPos.w) * 0.5f; + + // Refraction transform + o.vRefractXYW = float3(vRefractPos.x, vRefractPos.y, vProjPos.w); + + // Compute fog based on the position + float3 vWorldPos = mul( v.vPos, cModel[0] ); + o.fogFactorW = o.vFog = CalcFog( vWorldPos, vProjPos, FOGTYPE_RANGE ); + + // Eye vector + float3 vWorldEyeVect = cEyePos - vWorldPos; + // Transform to the tangent space + o.vTangentEyeVect.x = dot( vWorldEyeVect, worldTangentS ); + o.vTangentEyeVect.y = dot( vWorldEyeVect, worldTangentT ); + o.vTangentEyeVect.z = dot( vWorldEyeVect, worldNormal ); + + // Tranform bump coordinates + o.vBumpTexCoord.x = dot( v.vBaseTexCoord, cBumpTexCoordTransform[0] ); + o.vBumpTexCoord.y = dot( v.vBaseTexCoord, cBumpTexCoordTransform[1] ); + + o.tangentSpaceTranspose[0] = worldTangentS; + o.tangentSpaceTranspose[1] = worldTangentT; + o.tangentSpaceTranspose[2] = worldNormal; + + return o; +} diff --git a/sp/src/materialsystem/stdshaders/SDK_teeth_bump_vs20.fxc b/sp/src/materialsystem/stdshaders/SDK_teeth_bump_vs20.fxc index 1dd834b524..ddca526a8f 100644 --- a/sp/src/materialsystem/stdshaders/SDK_teeth_bump_vs20.fxc +++ b/sp/src/materialsystem/stdshaders/SDK_teeth_bump_vs20.fxc @@ -55,7 +55,7 @@ struct VS_INPUT struct VS_OUTPUT { float4 projPos : POSITION; -#if !defined( _X360 ) +#if !defined( _X360 ) && !defined( SHADER_MODEL_VS_3_0 ) && !INTRO float fog : FOG; #endif float2 baseTexCoord : TEXCOORD0; @@ -105,10 +105,9 @@ VS_OUTPUT main( const VS_INPUT v ) o.projPos = vProjPos; vProjPos.z = dot( float4( worldPos, 1 ), cViewProjZ ); - o.worldPos_projPosZ = float4( worldPos, vProjPos.z ); -#if !defined( _X360 ) - // Set fixed-function fog factor - o.fog = CalcFog( worldPos, vProjPos, g_FogType ); + o.worldPos_projPosZ = float4( worldPos.xyz, vProjPos.z ); +#if !defined( _X360 )&& !defined( SHADER_MODEL_VS_3_0 ) && !INTRO + o.fog = CalcFixedFunctionFog( worldPos, g_FogType ); #endif // Needed for specular o.worldVertToEyeVector_Darkening.xyz = cEyePos - worldPos; diff --git a/sp/src/materialsystem/stdshaders/SDK_windowimposter_ps2x.fxc b/sp/src/materialsystem/stdshaders/SDK_windowimposter_ps2x.fxc index fc3ea25a63..8863683eba 100644 --- a/sp/src/materialsystem/stdshaders/SDK_windowimposter_ps2x.fxc +++ b/sp/src/materialsystem/stdshaders/SDK_windowimposter_ps2x.fxc @@ -24,11 +24,11 @@ struct PS_INPUT float3 eyeToVertVector : TEXCOORD0; float4 vertexColor : COLOR; + float4 worldPos_projPosZ : TEXCOORD1; // Necessary for pixel fog + #if PARALLAXCORRECT - float3 worldSpaceNormal : TEXCOORD1; + float3 worldSpaceNormal : TEXCOORD2; #endif - - float4 worldPos_projPosZ : TEXCOORD2; // Necessary for pixel fog }; float4 main( PS_INPUT i ) : COLOR diff --git a/sp/src/materialsystem/stdshaders/SDK_windowimposter_vs20.fxc b/sp/src/materialsystem/stdshaders/SDK_windowimposter_vs20.fxc index f67676ee37..3a6a933484 100644 --- a/sp/src/materialsystem/stdshaders/SDK_windowimposter_vs20.fxc +++ b/sp/src/materialsystem/stdshaders/SDK_windowimposter_vs20.fxc @@ -28,11 +28,11 @@ struct VS_OUTPUT float3 eyeToVertVector : TEXCOORD0; float4 vertexColor : COLOR; + float4 worldPos_projPosZ : TEXCOORD1; // Necessary for pixel fog + #if PARALLAXCORRECT - float3 worldNormal : TEXCOORD1; + float3 worldNormal : TEXCOORD2; #endif - - float4 worldPos_projPosZ : TEXCOORD2; // Necessary for pixel fog }; VS_OUTPUT main( const VS_INPUT v ) diff --git a/sp/src/materialsystem/stdshaders/core_dx9.cpp b/sp/src/materialsystem/stdshaders/core_dx9.cpp new file mode 100644 index 0000000000..ceff056355 --- /dev/null +++ b/sp/src/materialsystem/stdshaders/core_dx9.cpp @@ -0,0 +1,307 @@ +//========= Copyright © 1996-2006, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "BaseVSShader.h" +#include "SDK_core_vs20.inc" +#include "SDK_core_ps20.inc" +#include "SDK_core_ps20b.inc" + +#define MAXBLUR 1 + +DEFINE_FALLBACK_SHADER( SDK_Core, SDK_Core_DX90 ) + +BEGIN_VS_SHADER( SDK_Core_DX90, + "Help for Core" ) + + BEGIN_SHADER_PARAMS + SHADER_PARAM_OVERRIDE( COLOR, SHADER_PARAM_TYPE_COLOR, "{255 255 255}", "unused", SHADER_PARAM_NOT_EDITABLE ) + SHADER_PARAM_OVERRIDE( ALPHA, SHADER_PARAM_TYPE_FLOAT, "1.0", "unused", SHADER_PARAM_NOT_EDITABLE ) + SHADER_PARAM( REFRACTAMOUNT, SHADER_PARAM_TYPE_FLOAT, "2", "" ) + SHADER_PARAM( REFRACTTINT, SHADER_PARAM_TYPE_COLOR, "[1 1 1]", "refraction tint" ) + SHADER_PARAM( NORMALMAP, SHADER_PARAM_TYPE_TEXTURE, "models/shadertest/shader1_normal", "normal map" ) + SHADER_PARAM( BUMPFRAME, SHADER_PARAM_TYPE_INTEGER, "0", "frame number for $bumpmap" ) + SHADER_PARAM( BUMPTRANSFORM, SHADER_PARAM_TYPE_MATRIX, "center .5 .5 scale 1 1 rotate 0 translate 0 0", "$bumpmap texcoord transform" ) + SHADER_PARAM( TIME, SHADER_PARAM_TYPE_FLOAT, "0.0f", "" ) + SHADER_PARAM( ENVMAP, SHADER_PARAM_TYPE_TEXTURE, "shadertest/shadertest_env", "envmap" ) + SHADER_PARAM( ENVMAPFRAME, SHADER_PARAM_TYPE_INTEGER, "0", "envmap frame number" ) + SHADER_PARAM( ENVMAPTINT, SHADER_PARAM_TYPE_COLOR, "[1 1 1]", "envmap tint" ) + SHADER_PARAM( ENVMAPCONTRAST, SHADER_PARAM_TYPE_FLOAT, "0.0", "contrast 0 == normal 1 == color*color" ) + SHADER_PARAM( ENVMAPSATURATION, SHADER_PARAM_TYPE_FLOAT, "1.0", "saturation 0 == greyscale 1 == normal" ) + SHADER_PARAM( FLOWMAP, SHADER_PARAM_TYPE_TEXTURE, "", "flowmap" ) + SHADER_PARAM( FLOWMAPFRAME, SHADER_PARAM_TYPE_INTEGER, "0", "frame number for $flowmap" ) + SHADER_PARAM( FLOWMAPSCROLLRATE, SHADER_PARAM_TYPE_VEC2, "[0 0", "2D rate to scroll $flowmap" ) + SHADER_PARAM( CORECOLORTEXTURE, SHADER_PARAM_TYPE_TEXTURE, "", "" ); + SHADER_PARAM( CORECOLORTEXTUREFRAME, SHADER_PARAM_TYPE_INTEGER, "", "" ); + SHADER_PARAM( FLOWMAPTEXCOORDOFFSET, SHADER_PARAM_TYPE_FLOAT, "0.0", "" ); +#ifdef MAPBASE + SHADER_PARAM( SPHERECENTER, SHADER_PARAM_TYPE_VEC3, "2688.0, 12139.0, 5170.0", "The sphere's worldspace center (was previously hardcoded)" ); + SHADER_PARAM( SPHERERADIUS, SHADER_PARAM_TYPE_FLOAT, "215.0", "The sphere's worldspace radius (was previously hardcoded)" ); +#endif + END_SHADER_PARAMS + SHADER_INIT_PARAMS() + { + SET_FLAGS2( MATERIAL_VAR2_NEEDS_TANGENT_SPACES ); + SET_FLAGS( MATERIAL_VAR_TRANSLUCENT ); + if( !params[ENVMAPTINT]->IsDefined() ) + { + params[ENVMAPTINT]->SetVecValue( 1.0f, 1.0f, 1.0f ); + } + if( !params[ENVMAPCONTRAST]->IsDefined() ) + { + params[ENVMAPCONTRAST]->SetFloatValue( 0.0f ); + } + if( !params[ENVMAPSATURATION]->IsDefined() ) + { + params[ENVMAPSATURATION]->SetFloatValue( 1.0f ); + } + if( !params[ENVMAPFRAME]->IsDefined() ) + { + params[ENVMAPFRAME]->SetIntValue( 0 ); + } + if( !params[BASETEXTURE]->IsDefined() ) + { + SET_FLAGS2( MATERIAL_VAR2_NEEDS_POWER_OF_TWO_FRAME_BUFFER_TEXTURE ); + } + } + + SHADER_FALLBACK + { + if( g_pHardwareConfig->GetDXSupportLevel() < 90 ) + return "Core_dx90"; + + return 0; + } + + SHADER_INIT + { + if (params[BASETEXTURE]->IsDefined() ) + { + LoadTexture( BASETEXTURE ); + } + if (params[NORMALMAP]->IsDefined() ) + { + LoadBumpMap( NORMALMAP ); + } + if ( params[ENVMAP]->IsDefined() ) + { + LoadCubeMap( ENVMAP ); + } + if ( params[FLOWMAP]->IsDefined() ) + { + LoadTexture( FLOWMAP ); + } + if ( params[CORECOLORTEXTURE]->IsDefined() ) + { + LoadTexture( CORECOLORTEXTURE ); + } + } + + inline void DrawPass( IMaterialVar **params, IShaderShadow* pShaderShadow, + IShaderDynamicAPI* pShaderAPI, int nPass, VertexCompressionType_t vertexCompression ) + { + bool bIsModel = IS_FLAG_SET( MATERIAL_VAR_MODEL ); + bool bHasEnvmap = params[ENVMAP]->IsTexture(); + bool bHasFlowmap = params[FLOWMAP]->IsTexture(); + bool bHasCoreColorTexture = params[CORECOLORTEXTURE]->IsTexture(); + + SHADOW_STATE + { + SetInitialShadowState( ); + + if( nPass == 0 ) + { + // Alpha test: FIXME: shouldn't this be handled in Shader_t::SetInitialShadowState + pShaderShadow->EnableAlphaTest( IS_FLAG_SET(MATERIAL_VAR_ALPHATEST) ); + } + else + { + pShaderShadow->DepthFunc( SHADER_DEPTHFUNC_EQUAL ); + EnableAlphaBlending( SHADER_BLEND_ONE, SHADER_BLEND_ONE ); + } + + // If envmap is not specified, the alpha channel is the translucency + // (If envmap *is* specified, alpha channel is the reflection amount) + if ( params[NORMALMAP]->IsTexture() && !bHasEnvmap ) + { + SetDefaultBlendingShadowState( NORMALMAP, false ); + } + + // source render target that contains the image that we are warping. + pShaderShadow->EnableTexture( SHADER_SAMPLER2, true ); + if( g_pHardwareConfig->GetHDRType() == HDR_TYPE_INTEGER ) + { + pShaderShadow->EnableSRGBRead( SHADER_SAMPLER2, true ); + } + + // normal map + pShaderShadow->EnableTexture( SHADER_SAMPLER3, true ); + if( bHasEnvmap ) + { + // envmap + pShaderShadow->EnableTexture( SHADER_SAMPLER4, true ); + if( g_pHardwareConfig->GetHDRType() == HDR_TYPE_INTEGER ) + { + pShaderShadow->EnableSRGBRead( SHADER_SAMPLER4, true ); + } + } + + if( bHasFlowmap ) + { + pShaderShadow->EnableTexture( SHADER_SAMPLER6, true ); + } + + if( bHasCoreColorTexture ) + { + pShaderShadow->EnableTexture( SHADER_SAMPLER7, true ); + } + + if( g_pHardwareConfig->GetHDRType() != HDR_TYPE_NONE ) + { + pShaderShadow->EnableSRGBWrite( true ); + } + + unsigned int flags = VERTEX_POSITION | VERTEX_NORMAL; + int userDataSize = 0; + int nTexCoordCount = 1; + if( bIsModel ) + { + userDataSize = 4; + } + else + { + flags |= VERTEX_TANGENT_S | VERTEX_TANGENT_T; + } + + // This shader supports compressed vertices, so OR in that flag: + flags |= VERTEX_FORMAT_COMPRESSED; + + pShaderShadow->VertexShaderVertexFormat( flags, nTexCoordCount, NULL, userDataSize ); + + DECLARE_STATIC_VERTEX_SHADER( sdk_core_vs20 ); + SET_STATIC_VERTEX_SHADER_COMBO( MODEL, bIsModel ); + SET_STATIC_VERTEX_SHADER( sdk_core_vs20 ); + + if ( g_pHardwareConfig->SupportsPixelShaders_2_b() ) + { + DECLARE_STATIC_PIXEL_SHADER( sdk_core_ps20b ); + SET_STATIC_PIXEL_SHADER_COMBO( CUBEMAP, bHasEnvmap && ( nPass == 1 ) ); + SET_STATIC_PIXEL_SHADER_COMBO( FLOWMAP, bHasFlowmap ); + SET_STATIC_PIXEL_SHADER_COMBO( CORECOLORTEXTURE, bHasCoreColorTexture && ( nPass == 0 ) ); + SET_STATIC_PIXEL_SHADER_COMBO( REFRACT, nPass == 0 ); + SET_STATIC_PIXEL_SHADER( sdk_core_ps20b ); + } + else + { + DECLARE_STATIC_PIXEL_SHADER( sdk_core_ps20 ); + SET_STATIC_PIXEL_SHADER_COMBO( CUBEMAP, bHasEnvmap && ( nPass == 1 ) ); + SET_STATIC_PIXEL_SHADER_COMBO( FLOWMAP, bHasFlowmap ); + SET_STATIC_PIXEL_SHADER_COMBO( CORECOLORTEXTURE, bHasCoreColorTexture && ( nPass == 0 ) ); + SET_STATIC_PIXEL_SHADER_COMBO( REFRACT, nPass == 0 ); + SET_STATIC_PIXEL_SHADER( sdk_core_ps20 ); + } + + DefaultFog(); + } + DYNAMIC_STATE + { + pShaderAPI->SetDefaultState(); + + if ( params[BASETEXTURE]->IsTexture() ) + { + BindTexture( SHADER_SAMPLER2, BASETEXTURE, FRAME ); + } + else + { + pShaderAPI->BindStandardTexture( SHADER_SAMPLER2, TEXTURE_FRAME_BUFFER_FULL_TEXTURE_0 ); + } + + BindTexture( SHADER_SAMPLER3, NORMALMAP, BUMPFRAME ); + + if( bHasEnvmap ) + { + BindTexture( SHADER_SAMPLER4, ENVMAP, ENVMAPFRAME ); + } + + if( bHasFlowmap ) + { + BindTexture( SHADER_SAMPLER6, FLOWMAP, FLOWMAPFRAME ); + } + + if( bHasCoreColorTexture ) + { + BindTexture( SHADER_SAMPLER7, CORECOLORTEXTURE, CORECOLORTEXTUREFRAME ); + } + + DECLARE_DYNAMIC_VERTEX_SHADER( sdk_core_vs20 ); + SET_DYNAMIC_VERTEX_SHADER_COMBO( SKINNING, pShaderAPI->GetCurrentNumBones() > 0 ); + SET_DYNAMIC_VERTEX_SHADER_COMBO( COMPRESSED_VERTS, (int)vertexCompression ); + SET_DYNAMIC_VERTEX_SHADER( sdk_core_vs20 ); + + if ( g_pHardwareConfig->SupportsPixelShaders_2_b() ) + { + DECLARE_DYNAMIC_PIXEL_SHADER( sdk_core_ps20b ); + SET_DYNAMIC_PIXEL_SHADER_COMBO( PIXELFOGTYPE, pShaderAPI->GetPixelFogCombo() ); + SET_DYNAMIC_PIXEL_SHADER( sdk_core_ps20b ); + } + else + { + DECLARE_DYNAMIC_PIXEL_SHADER( sdk_core_ps20 ); + SET_DYNAMIC_PIXEL_SHADER_COMBO( PIXELFOGTYPE, pShaderAPI->GetPixelFogCombo() ); + SET_DYNAMIC_PIXEL_SHADER( sdk_core_ps20 ); + } + + SetVertexShaderTextureTransform( VERTEX_SHADER_SHADER_SPECIFIC_CONST_1, BUMPTRANSFORM ); + + if( g_pHardwareConfig->GetHDRType() == HDR_TYPE_NONE ) + { + SetPixelShaderConstant( 0, ENVMAPTINT ); + SetPixelShaderConstant( 1, REFRACTTINT ); + } + else + { + SetPixelShaderConstantGammaToLinear( 0, ENVMAPTINT ); + SetPixelShaderConstantGammaToLinear( 1, REFRACTTINT ); + } + SetPixelShaderConstant( 2, ENVMAPCONTRAST ); + SetPixelShaderConstant( 3, ENVMAPSATURATION ); + float c5[4] = { params[REFRACTAMOUNT]->GetFloatValue(), + params[REFRACTAMOUNT]->GetFloatValue(), 0.0f, 0.0f }; + pShaderAPI->SetPixelShaderConstant( 5, c5, 1 ); + + float eyePos[4]; + s_pShaderAPI->GetWorldSpaceCameraPosition( eyePos ); + s_pShaderAPI->SetPixelShaderConstant( 8, eyePos, 1 ); + pShaderAPI->SetPixelShaderFogParams( 11 ); + + + + if( bHasFlowmap ) + { + float curTime = pShaderAPI->CurrentTime(); + float timeVec[4] = { curTime, curTime, curTime, curTime }; + pShaderAPI->SetPixelShaderConstant( 6, timeVec, 1 ); + + SetPixelShaderConstant( 7, FLOWMAPSCROLLRATE ); + + SetPixelShaderConstant( 9, FLOWMAPTEXCOORDOFFSET ); + } + +#ifdef MAPBASE + SetPixelShaderConstant( 12, SPHERECENTER ); + SetPixelShaderConstant( 15, SPHERERADIUS ); +#endif + } + Draw(); + } + + SHADER_DRAW + { + DrawPass( params, pShaderShadow, pShaderAPI, 0, vertexCompression ); + DrawPass( params, pShaderShadow, pShaderAPI, 1, vertexCompression ); + } +END_SHADER + diff --git a/sp/src/materialsystem/stdshaders/fxctmp9/SDK_core_ps20.inc b/sp/src/materialsystem/stdshaders/fxctmp9/SDK_core_ps20.inc new file mode 100644 index 0000000000..d6701fc738 --- /dev/null +++ b/sp/src/materialsystem/stdshaders/fxctmp9/SDK_core_ps20.inc @@ -0,0 +1,162 @@ +#include "shaderlib/cshader.h" +class sdk_core_ps20_Static_Index +{ +private: + int m_nCUBEMAP; +#ifdef _DEBUG + bool m_bCUBEMAP; +#endif +public: + void SetCUBEMAP( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nCUBEMAP = i; +#ifdef _DEBUG + m_bCUBEMAP = true; +#endif + } + void SetCUBEMAP( bool i ) + { + m_nCUBEMAP = i ? 1 : 0; +#ifdef _DEBUG + m_bCUBEMAP = true; +#endif + } +private: + int m_nFLOWMAP; +#ifdef _DEBUG + bool m_bFLOWMAP; +#endif +public: + void SetFLOWMAP( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nFLOWMAP = i; +#ifdef _DEBUG + m_bFLOWMAP = true; +#endif + } + void SetFLOWMAP( bool i ) + { + m_nFLOWMAP = i ? 1 : 0; +#ifdef _DEBUG + m_bFLOWMAP = true; +#endif + } +private: + int m_nCORECOLORTEXTURE; +#ifdef _DEBUG + bool m_bCORECOLORTEXTURE; +#endif +public: + void SetCORECOLORTEXTURE( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nCORECOLORTEXTURE = i; +#ifdef _DEBUG + m_bCORECOLORTEXTURE = true; +#endif + } + void SetCORECOLORTEXTURE( bool i ) + { + m_nCORECOLORTEXTURE = i ? 1 : 0; +#ifdef _DEBUG + m_bCORECOLORTEXTURE = true; +#endif + } +private: + int m_nREFRACT; +#ifdef _DEBUG + bool m_bREFRACT; +#endif +public: + void SetREFRACT( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nREFRACT = i; +#ifdef _DEBUG + m_bREFRACT = true; +#endif + } + void SetREFRACT( bool i ) + { + m_nREFRACT = i ? 1 : 0; +#ifdef _DEBUG + m_bREFRACT = true; +#endif + } +public: + sdk_core_ps20_Static_Index( ) + { +#ifdef _DEBUG + m_bCUBEMAP = false; +#endif // _DEBUG + m_nCUBEMAP = 0; +#ifdef _DEBUG + m_bFLOWMAP = false; +#endif // _DEBUG + m_nFLOWMAP = 0; +#ifdef _DEBUG + m_bCORECOLORTEXTURE = false; +#endif // _DEBUG + m_nCORECOLORTEXTURE = 0; +#ifdef _DEBUG + m_bREFRACT = false; +#endif // _DEBUG + m_nREFRACT = 0; + } + int GetIndex() + { + // Asserts to make sure that we aren't using any skipped combinations. + // Asserts to make sure that we are setting all of the combination vars. +#ifdef _DEBUG + bool bAllStaticVarsDefined = m_bCUBEMAP && m_bFLOWMAP && m_bCORECOLORTEXTURE && m_bREFRACT; + Assert( bAllStaticVarsDefined ); +#endif // _DEBUG + return ( 2 * m_nCUBEMAP ) + ( 4 * m_nFLOWMAP ) + ( 8 * m_nCORECOLORTEXTURE ) + ( 16 * m_nREFRACT ) + 0; + } +}; +#define shaderStaticTest_sdk_core_ps20 psh_forgot_to_set_static_CUBEMAP + psh_forgot_to_set_static_FLOWMAP + psh_forgot_to_set_static_CORECOLORTEXTURE + psh_forgot_to_set_static_REFRACT + 0 +class sdk_core_ps20_Dynamic_Index +{ +private: + int m_nPIXELFOGTYPE; +#ifdef _DEBUG + bool m_bPIXELFOGTYPE; +#endif +public: + void SetPIXELFOGTYPE( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nPIXELFOGTYPE = i; +#ifdef _DEBUG + m_bPIXELFOGTYPE = true; +#endif + } + void SetPIXELFOGTYPE( bool i ) + { + m_nPIXELFOGTYPE = i ? 1 : 0; +#ifdef _DEBUG + m_bPIXELFOGTYPE = true; +#endif + } +public: + sdk_core_ps20_Dynamic_Index() + { +#ifdef _DEBUG + m_bPIXELFOGTYPE = false; +#endif // _DEBUG + m_nPIXELFOGTYPE = 0; + } + int GetIndex() + { + // Asserts to make sure that we aren't using any skipped combinations. + // Asserts to make sure that we are setting all of the combination vars. +#ifdef _DEBUG + bool bAllDynamicVarsDefined = m_bPIXELFOGTYPE; + Assert( bAllDynamicVarsDefined ); +#endif // _DEBUG + return ( 1 * m_nPIXELFOGTYPE ) + 0; + } +}; +#define shaderDynamicTest_sdk_core_ps20 psh_forgot_to_set_dynamic_PIXELFOGTYPE + 0 diff --git a/sp/src/materialsystem/stdshaders/fxctmp9/SDK_core_ps20b.inc b/sp/src/materialsystem/stdshaders/fxctmp9/SDK_core_ps20b.inc new file mode 100644 index 0000000000..9b9a843d09 --- /dev/null +++ b/sp/src/materialsystem/stdshaders/fxctmp9/SDK_core_ps20b.inc @@ -0,0 +1,187 @@ +#include "shaderlib/cshader.h" +class sdk_core_ps20b_Static_Index +{ +private: + int m_nCONVERT_TO_SRGB; +#ifdef _DEBUG + bool m_bCONVERT_TO_SRGB; +#endif +public: + void SetCONVERT_TO_SRGB( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nCONVERT_TO_SRGB = i; +#ifdef _DEBUG + m_bCONVERT_TO_SRGB = true; +#endif + } + void SetCONVERT_TO_SRGB( bool i ) + { + m_nCONVERT_TO_SRGB = i ? 1 : 0; +#ifdef _DEBUG + m_bCONVERT_TO_SRGB = true; +#endif + } +private: + int m_nCUBEMAP; +#ifdef _DEBUG + bool m_bCUBEMAP; +#endif +public: + void SetCUBEMAP( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nCUBEMAP = i; +#ifdef _DEBUG + m_bCUBEMAP = true; +#endif + } + void SetCUBEMAP( bool i ) + { + m_nCUBEMAP = i ? 1 : 0; +#ifdef _DEBUG + m_bCUBEMAP = true; +#endif + } +private: + int m_nFLOWMAP; +#ifdef _DEBUG + bool m_bFLOWMAP; +#endif +public: + void SetFLOWMAP( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nFLOWMAP = i; +#ifdef _DEBUG + m_bFLOWMAP = true; +#endif + } + void SetFLOWMAP( bool i ) + { + m_nFLOWMAP = i ? 1 : 0; +#ifdef _DEBUG + m_bFLOWMAP = true; +#endif + } +private: + int m_nCORECOLORTEXTURE; +#ifdef _DEBUG + bool m_bCORECOLORTEXTURE; +#endif +public: + void SetCORECOLORTEXTURE( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nCORECOLORTEXTURE = i; +#ifdef _DEBUG + m_bCORECOLORTEXTURE = true; +#endif + } + void SetCORECOLORTEXTURE( bool i ) + { + m_nCORECOLORTEXTURE = i ? 1 : 0; +#ifdef _DEBUG + m_bCORECOLORTEXTURE = true; +#endif + } +private: + int m_nREFRACT; +#ifdef _DEBUG + bool m_bREFRACT; +#endif +public: + void SetREFRACT( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nREFRACT = i; +#ifdef _DEBUG + m_bREFRACT = true; +#endif + } + void SetREFRACT( bool i ) + { + m_nREFRACT = i ? 1 : 0; +#ifdef _DEBUG + m_bREFRACT = true; +#endif + } +public: + sdk_core_ps20b_Static_Index( ) + { +#ifdef _DEBUG + m_bCONVERT_TO_SRGB = true; +#endif // _DEBUG + m_nCONVERT_TO_SRGB = g_pHardwareConfig->NeedsShaderSRGBConversion(); +#ifdef _DEBUG + m_bCUBEMAP = false; +#endif // _DEBUG + m_nCUBEMAP = 0; +#ifdef _DEBUG + m_bFLOWMAP = false; +#endif // _DEBUG + m_nFLOWMAP = 0; +#ifdef _DEBUG + m_bCORECOLORTEXTURE = false; +#endif // _DEBUG + m_nCORECOLORTEXTURE = 0; +#ifdef _DEBUG + m_bREFRACT = false; +#endif // _DEBUG + m_nREFRACT = 0; + } + int GetIndex() + { + // Asserts to make sure that we aren't using any skipped combinations. + // Asserts to make sure that we are setting all of the combination vars. +#ifdef _DEBUG + bool bAllStaticVarsDefined = m_bCONVERT_TO_SRGB && m_bCUBEMAP && m_bFLOWMAP && m_bCORECOLORTEXTURE && m_bREFRACT; + Assert( bAllStaticVarsDefined ); +#endif // _DEBUG + return ( 2 * m_nCONVERT_TO_SRGB ) + ( 4 * m_nCUBEMAP ) + ( 8 * m_nFLOWMAP ) + ( 16 * m_nCORECOLORTEXTURE ) + ( 32 * m_nREFRACT ) + 0; + } +}; +#define shaderStaticTest_sdk_core_ps20b psh_forgot_to_set_static_CUBEMAP + psh_forgot_to_set_static_FLOWMAP + psh_forgot_to_set_static_CORECOLORTEXTURE + psh_forgot_to_set_static_REFRACT + 0 +class sdk_core_ps20b_Dynamic_Index +{ +private: + int m_nPIXELFOGTYPE; +#ifdef _DEBUG + bool m_bPIXELFOGTYPE; +#endif +public: + void SetPIXELFOGTYPE( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nPIXELFOGTYPE = i; +#ifdef _DEBUG + m_bPIXELFOGTYPE = true; +#endif + } + void SetPIXELFOGTYPE( bool i ) + { + m_nPIXELFOGTYPE = i ? 1 : 0; +#ifdef _DEBUG + m_bPIXELFOGTYPE = true; +#endif + } +public: + sdk_core_ps20b_Dynamic_Index() + { +#ifdef _DEBUG + m_bPIXELFOGTYPE = false; +#endif // _DEBUG + m_nPIXELFOGTYPE = 0; + } + int GetIndex() + { + // Asserts to make sure that we aren't using any skipped combinations. + // Asserts to make sure that we are setting all of the combination vars. +#ifdef _DEBUG + bool bAllDynamicVarsDefined = m_bPIXELFOGTYPE; + Assert( bAllDynamicVarsDefined ); +#endif // _DEBUG + return ( 1 * m_nPIXELFOGTYPE ) + 0; + } +}; +#define shaderDynamicTest_sdk_core_ps20b psh_forgot_to_set_dynamic_PIXELFOGTYPE + 0 diff --git a/sp/src/materialsystem/stdshaders/fxctmp9/SDK_core_vs20.inc b/sp/src/materialsystem/stdshaders/fxctmp9/SDK_core_vs20.inc new file mode 100644 index 0000000000..ef6a680c63 --- /dev/null +++ b/sp/src/materialsystem/stdshaders/fxctmp9/SDK_core_vs20.inc @@ -0,0 +1,112 @@ +#include "shaderlib/cshader.h" +class sdk_core_vs20_Static_Index +{ +private: + int m_nMODEL; +#ifdef _DEBUG + bool m_bMODEL; +#endif +public: + void SetMODEL( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nMODEL = i; +#ifdef _DEBUG + m_bMODEL = true; +#endif + } + void SetMODEL( bool i ) + { + m_nMODEL = i ? 1 : 0; +#ifdef _DEBUG + m_bMODEL = true; +#endif + } +public: + sdk_core_vs20_Static_Index( ) + { +#ifdef _DEBUG + m_bMODEL = false; +#endif // _DEBUG + m_nMODEL = 0; + } + int GetIndex() + { + // Asserts to make sure that we aren't using any skipped combinations. + // Asserts to make sure that we are setting all of the combination vars. +#ifdef _DEBUG + bool bAllStaticVarsDefined = m_bMODEL; + Assert( bAllStaticVarsDefined ); +#endif // _DEBUG + return ( 4 * m_nMODEL ) + 0; + } +}; +#define shaderStaticTest_sdk_core_vs20 vsh_forgot_to_set_static_MODEL + 0 +class sdk_core_vs20_Dynamic_Index +{ +private: + int m_nCOMPRESSED_VERTS; +#ifdef _DEBUG + bool m_bCOMPRESSED_VERTS; +#endif +public: + void SetCOMPRESSED_VERTS( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nCOMPRESSED_VERTS = i; +#ifdef _DEBUG + m_bCOMPRESSED_VERTS = true; +#endif + } + void SetCOMPRESSED_VERTS( bool i ) + { + m_nCOMPRESSED_VERTS = i ? 1 : 0; +#ifdef _DEBUG + m_bCOMPRESSED_VERTS = true; +#endif + } +private: + int m_nSKINNING; +#ifdef _DEBUG + bool m_bSKINNING; +#endif +public: + void SetSKINNING( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nSKINNING = i; +#ifdef _DEBUG + m_bSKINNING = true; +#endif + } + void SetSKINNING( bool i ) + { + m_nSKINNING = i ? 1 : 0; +#ifdef _DEBUG + m_bSKINNING = true; +#endif + } +public: + sdk_core_vs20_Dynamic_Index() + { +#ifdef _DEBUG + m_bCOMPRESSED_VERTS = false; +#endif // _DEBUG + m_nCOMPRESSED_VERTS = 0; +#ifdef _DEBUG + m_bSKINNING = false; +#endif // _DEBUG + m_nSKINNING = 0; + } + int GetIndex() + { + // Asserts to make sure that we aren't using any skipped combinations. + // Asserts to make sure that we are setting all of the combination vars. +#ifdef _DEBUG + bool bAllDynamicVarsDefined = m_bCOMPRESSED_VERTS && m_bSKINNING; + Assert( bAllDynamicVarsDefined ); +#endif // _DEBUG + return ( 1 * m_nCOMPRESSED_VERTS ) + ( 2 * m_nSKINNING ) + 0; + } +}; +#define shaderDynamicTest_sdk_core_vs20 vsh_forgot_to_set_dynamic_COMPRESSED_VERTS + vsh_forgot_to_set_dynamic_SKINNING + 0 diff --git a/sp/src/materialsystem/stdshaders/fxctmp9_tmp/SDK_core_ps20.inc b/sp/src/materialsystem/stdshaders/fxctmp9_tmp/SDK_core_ps20.inc new file mode 100644 index 0000000000..d6701fc738 --- /dev/null +++ b/sp/src/materialsystem/stdshaders/fxctmp9_tmp/SDK_core_ps20.inc @@ -0,0 +1,162 @@ +#include "shaderlib/cshader.h" +class sdk_core_ps20_Static_Index +{ +private: + int m_nCUBEMAP; +#ifdef _DEBUG + bool m_bCUBEMAP; +#endif +public: + void SetCUBEMAP( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nCUBEMAP = i; +#ifdef _DEBUG + m_bCUBEMAP = true; +#endif + } + void SetCUBEMAP( bool i ) + { + m_nCUBEMAP = i ? 1 : 0; +#ifdef _DEBUG + m_bCUBEMAP = true; +#endif + } +private: + int m_nFLOWMAP; +#ifdef _DEBUG + bool m_bFLOWMAP; +#endif +public: + void SetFLOWMAP( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nFLOWMAP = i; +#ifdef _DEBUG + m_bFLOWMAP = true; +#endif + } + void SetFLOWMAP( bool i ) + { + m_nFLOWMAP = i ? 1 : 0; +#ifdef _DEBUG + m_bFLOWMAP = true; +#endif + } +private: + int m_nCORECOLORTEXTURE; +#ifdef _DEBUG + bool m_bCORECOLORTEXTURE; +#endif +public: + void SetCORECOLORTEXTURE( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nCORECOLORTEXTURE = i; +#ifdef _DEBUG + m_bCORECOLORTEXTURE = true; +#endif + } + void SetCORECOLORTEXTURE( bool i ) + { + m_nCORECOLORTEXTURE = i ? 1 : 0; +#ifdef _DEBUG + m_bCORECOLORTEXTURE = true; +#endif + } +private: + int m_nREFRACT; +#ifdef _DEBUG + bool m_bREFRACT; +#endif +public: + void SetREFRACT( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nREFRACT = i; +#ifdef _DEBUG + m_bREFRACT = true; +#endif + } + void SetREFRACT( bool i ) + { + m_nREFRACT = i ? 1 : 0; +#ifdef _DEBUG + m_bREFRACT = true; +#endif + } +public: + sdk_core_ps20_Static_Index( ) + { +#ifdef _DEBUG + m_bCUBEMAP = false; +#endif // _DEBUG + m_nCUBEMAP = 0; +#ifdef _DEBUG + m_bFLOWMAP = false; +#endif // _DEBUG + m_nFLOWMAP = 0; +#ifdef _DEBUG + m_bCORECOLORTEXTURE = false; +#endif // _DEBUG + m_nCORECOLORTEXTURE = 0; +#ifdef _DEBUG + m_bREFRACT = false; +#endif // _DEBUG + m_nREFRACT = 0; + } + int GetIndex() + { + // Asserts to make sure that we aren't using any skipped combinations. + // Asserts to make sure that we are setting all of the combination vars. +#ifdef _DEBUG + bool bAllStaticVarsDefined = m_bCUBEMAP && m_bFLOWMAP && m_bCORECOLORTEXTURE && m_bREFRACT; + Assert( bAllStaticVarsDefined ); +#endif // _DEBUG + return ( 2 * m_nCUBEMAP ) + ( 4 * m_nFLOWMAP ) + ( 8 * m_nCORECOLORTEXTURE ) + ( 16 * m_nREFRACT ) + 0; + } +}; +#define shaderStaticTest_sdk_core_ps20 psh_forgot_to_set_static_CUBEMAP + psh_forgot_to_set_static_FLOWMAP + psh_forgot_to_set_static_CORECOLORTEXTURE + psh_forgot_to_set_static_REFRACT + 0 +class sdk_core_ps20_Dynamic_Index +{ +private: + int m_nPIXELFOGTYPE; +#ifdef _DEBUG + bool m_bPIXELFOGTYPE; +#endif +public: + void SetPIXELFOGTYPE( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nPIXELFOGTYPE = i; +#ifdef _DEBUG + m_bPIXELFOGTYPE = true; +#endif + } + void SetPIXELFOGTYPE( bool i ) + { + m_nPIXELFOGTYPE = i ? 1 : 0; +#ifdef _DEBUG + m_bPIXELFOGTYPE = true; +#endif + } +public: + sdk_core_ps20_Dynamic_Index() + { +#ifdef _DEBUG + m_bPIXELFOGTYPE = false; +#endif // _DEBUG + m_nPIXELFOGTYPE = 0; + } + int GetIndex() + { + // Asserts to make sure that we aren't using any skipped combinations. + // Asserts to make sure that we are setting all of the combination vars. +#ifdef _DEBUG + bool bAllDynamicVarsDefined = m_bPIXELFOGTYPE; + Assert( bAllDynamicVarsDefined ); +#endif // _DEBUG + return ( 1 * m_nPIXELFOGTYPE ) + 0; + } +}; +#define shaderDynamicTest_sdk_core_ps20 psh_forgot_to_set_dynamic_PIXELFOGTYPE + 0 diff --git a/sp/src/materialsystem/stdshaders/fxctmp9_tmp/SDK_core_ps20b.inc b/sp/src/materialsystem/stdshaders/fxctmp9_tmp/SDK_core_ps20b.inc new file mode 100644 index 0000000000..9b9a843d09 --- /dev/null +++ b/sp/src/materialsystem/stdshaders/fxctmp9_tmp/SDK_core_ps20b.inc @@ -0,0 +1,187 @@ +#include "shaderlib/cshader.h" +class sdk_core_ps20b_Static_Index +{ +private: + int m_nCONVERT_TO_SRGB; +#ifdef _DEBUG + bool m_bCONVERT_TO_SRGB; +#endif +public: + void SetCONVERT_TO_SRGB( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nCONVERT_TO_SRGB = i; +#ifdef _DEBUG + m_bCONVERT_TO_SRGB = true; +#endif + } + void SetCONVERT_TO_SRGB( bool i ) + { + m_nCONVERT_TO_SRGB = i ? 1 : 0; +#ifdef _DEBUG + m_bCONVERT_TO_SRGB = true; +#endif + } +private: + int m_nCUBEMAP; +#ifdef _DEBUG + bool m_bCUBEMAP; +#endif +public: + void SetCUBEMAP( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nCUBEMAP = i; +#ifdef _DEBUG + m_bCUBEMAP = true; +#endif + } + void SetCUBEMAP( bool i ) + { + m_nCUBEMAP = i ? 1 : 0; +#ifdef _DEBUG + m_bCUBEMAP = true; +#endif + } +private: + int m_nFLOWMAP; +#ifdef _DEBUG + bool m_bFLOWMAP; +#endif +public: + void SetFLOWMAP( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nFLOWMAP = i; +#ifdef _DEBUG + m_bFLOWMAP = true; +#endif + } + void SetFLOWMAP( bool i ) + { + m_nFLOWMAP = i ? 1 : 0; +#ifdef _DEBUG + m_bFLOWMAP = true; +#endif + } +private: + int m_nCORECOLORTEXTURE; +#ifdef _DEBUG + bool m_bCORECOLORTEXTURE; +#endif +public: + void SetCORECOLORTEXTURE( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nCORECOLORTEXTURE = i; +#ifdef _DEBUG + m_bCORECOLORTEXTURE = true; +#endif + } + void SetCORECOLORTEXTURE( bool i ) + { + m_nCORECOLORTEXTURE = i ? 1 : 0; +#ifdef _DEBUG + m_bCORECOLORTEXTURE = true; +#endif + } +private: + int m_nREFRACT; +#ifdef _DEBUG + bool m_bREFRACT; +#endif +public: + void SetREFRACT( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nREFRACT = i; +#ifdef _DEBUG + m_bREFRACT = true; +#endif + } + void SetREFRACT( bool i ) + { + m_nREFRACT = i ? 1 : 0; +#ifdef _DEBUG + m_bREFRACT = true; +#endif + } +public: + sdk_core_ps20b_Static_Index( ) + { +#ifdef _DEBUG + m_bCONVERT_TO_SRGB = true; +#endif // _DEBUG + m_nCONVERT_TO_SRGB = g_pHardwareConfig->NeedsShaderSRGBConversion(); +#ifdef _DEBUG + m_bCUBEMAP = false; +#endif // _DEBUG + m_nCUBEMAP = 0; +#ifdef _DEBUG + m_bFLOWMAP = false; +#endif // _DEBUG + m_nFLOWMAP = 0; +#ifdef _DEBUG + m_bCORECOLORTEXTURE = false; +#endif // _DEBUG + m_nCORECOLORTEXTURE = 0; +#ifdef _DEBUG + m_bREFRACT = false; +#endif // _DEBUG + m_nREFRACT = 0; + } + int GetIndex() + { + // Asserts to make sure that we aren't using any skipped combinations. + // Asserts to make sure that we are setting all of the combination vars. +#ifdef _DEBUG + bool bAllStaticVarsDefined = m_bCONVERT_TO_SRGB && m_bCUBEMAP && m_bFLOWMAP && m_bCORECOLORTEXTURE && m_bREFRACT; + Assert( bAllStaticVarsDefined ); +#endif // _DEBUG + return ( 2 * m_nCONVERT_TO_SRGB ) + ( 4 * m_nCUBEMAP ) + ( 8 * m_nFLOWMAP ) + ( 16 * m_nCORECOLORTEXTURE ) + ( 32 * m_nREFRACT ) + 0; + } +}; +#define shaderStaticTest_sdk_core_ps20b psh_forgot_to_set_static_CUBEMAP + psh_forgot_to_set_static_FLOWMAP + psh_forgot_to_set_static_CORECOLORTEXTURE + psh_forgot_to_set_static_REFRACT + 0 +class sdk_core_ps20b_Dynamic_Index +{ +private: + int m_nPIXELFOGTYPE; +#ifdef _DEBUG + bool m_bPIXELFOGTYPE; +#endif +public: + void SetPIXELFOGTYPE( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nPIXELFOGTYPE = i; +#ifdef _DEBUG + m_bPIXELFOGTYPE = true; +#endif + } + void SetPIXELFOGTYPE( bool i ) + { + m_nPIXELFOGTYPE = i ? 1 : 0; +#ifdef _DEBUG + m_bPIXELFOGTYPE = true; +#endif + } +public: + sdk_core_ps20b_Dynamic_Index() + { +#ifdef _DEBUG + m_bPIXELFOGTYPE = false; +#endif // _DEBUG + m_nPIXELFOGTYPE = 0; + } + int GetIndex() + { + // Asserts to make sure that we aren't using any skipped combinations. + // Asserts to make sure that we are setting all of the combination vars. +#ifdef _DEBUG + bool bAllDynamicVarsDefined = m_bPIXELFOGTYPE; + Assert( bAllDynamicVarsDefined ); +#endif // _DEBUG + return ( 1 * m_nPIXELFOGTYPE ) + 0; + } +}; +#define shaderDynamicTest_sdk_core_ps20b psh_forgot_to_set_dynamic_PIXELFOGTYPE + 0 diff --git a/sp/src/materialsystem/stdshaders/fxctmp9_tmp/SDK_core_vs20.inc b/sp/src/materialsystem/stdshaders/fxctmp9_tmp/SDK_core_vs20.inc new file mode 100644 index 0000000000..ef6a680c63 --- /dev/null +++ b/sp/src/materialsystem/stdshaders/fxctmp9_tmp/SDK_core_vs20.inc @@ -0,0 +1,112 @@ +#include "shaderlib/cshader.h" +class sdk_core_vs20_Static_Index +{ +private: + int m_nMODEL; +#ifdef _DEBUG + bool m_bMODEL; +#endif +public: + void SetMODEL( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nMODEL = i; +#ifdef _DEBUG + m_bMODEL = true; +#endif + } + void SetMODEL( bool i ) + { + m_nMODEL = i ? 1 : 0; +#ifdef _DEBUG + m_bMODEL = true; +#endif + } +public: + sdk_core_vs20_Static_Index( ) + { +#ifdef _DEBUG + m_bMODEL = false; +#endif // _DEBUG + m_nMODEL = 0; + } + int GetIndex() + { + // Asserts to make sure that we aren't using any skipped combinations. + // Asserts to make sure that we are setting all of the combination vars. +#ifdef _DEBUG + bool bAllStaticVarsDefined = m_bMODEL; + Assert( bAllStaticVarsDefined ); +#endif // _DEBUG + return ( 4 * m_nMODEL ) + 0; + } +}; +#define shaderStaticTest_sdk_core_vs20 vsh_forgot_to_set_static_MODEL + 0 +class sdk_core_vs20_Dynamic_Index +{ +private: + int m_nCOMPRESSED_VERTS; +#ifdef _DEBUG + bool m_bCOMPRESSED_VERTS; +#endif +public: + void SetCOMPRESSED_VERTS( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nCOMPRESSED_VERTS = i; +#ifdef _DEBUG + m_bCOMPRESSED_VERTS = true; +#endif + } + void SetCOMPRESSED_VERTS( bool i ) + { + m_nCOMPRESSED_VERTS = i ? 1 : 0; +#ifdef _DEBUG + m_bCOMPRESSED_VERTS = true; +#endif + } +private: + int m_nSKINNING; +#ifdef _DEBUG + bool m_bSKINNING; +#endif +public: + void SetSKINNING( int i ) + { + Assert( i >= 0 && i <= 1 ); + m_nSKINNING = i; +#ifdef _DEBUG + m_bSKINNING = true; +#endif + } + void SetSKINNING( bool i ) + { + m_nSKINNING = i ? 1 : 0; +#ifdef _DEBUG + m_bSKINNING = true; +#endif + } +public: + sdk_core_vs20_Dynamic_Index() + { +#ifdef _DEBUG + m_bCOMPRESSED_VERTS = false; +#endif // _DEBUG + m_nCOMPRESSED_VERTS = 0; +#ifdef _DEBUG + m_bSKINNING = false; +#endif // _DEBUG + m_nSKINNING = 0; + } + int GetIndex() + { + // Asserts to make sure that we aren't using any skipped combinations. + // Asserts to make sure that we are setting all of the combination vars. +#ifdef _DEBUG + bool bAllDynamicVarsDefined = m_bCOMPRESSED_VERTS && m_bSKINNING; + Assert( bAllDynamicVarsDefined ); +#endif // _DEBUG + return ( 1 * m_nCOMPRESSED_VERTS ) + ( 2 * m_nSKINNING ) + 0; + } +}; +#define shaderDynamicTest_sdk_core_vs20 vsh_forgot_to_set_dynamic_COMPRESSED_VERTS + vsh_forgot_to_set_dynamic_SKINNING + 0 diff --git a/sp/src/materialsystem/stdshaders/game_shader_dx9_mapbase.vpc b/sp/src/materialsystem/stdshaders/game_shader_dx9_mapbase.vpc index 40d641439e..d6a8480d06 100644 --- a/sp/src/materialsystem/stdshaders/game_shader_dx9_mapbase.vpc +++ b/sp/src/materialsystem/stdshaders/game_shader_dx9_mapbase.vpc @@ -64,6 +64,8 @@ $Project $File "engine_post_dx9.cpp" $File "depthoffield_dx9.cpp" + + $File "core_dx9.cpp" } //$Shaders "mapbase_dx9_20b.txt" diff --git a/sp/src/materialsystem/stdshaders/lightmappedgeneric_dx9_helper.cpp b/sp/src/materialsystem/stdshaders/lightmappedgeneric_dx9_helper.cpp index b2a8ddcc63..6f64a15d83 100644 --- a/sp/src/materialsystem/stdshaders/lightmappedgeneric_dx9_helper.cpp +++ b/sp/src/materialsystem/stdshaders/lightmappedgeneric_dx9_helper.cpp @@ -1566,9 +1566,10 @@ void DrawLightmappedGeneric_DX9_Internal(CBaseVSShader *pShader, IMaterialVar** // Doing it here in the shader itself allows us to retain other properties, like FANCY_BLENDING. else { - // m_SemiStaticCmdsOut wasn't being sent correctly, so we have to assign this to the API directly - float editorBlend = bEditorBlend ? 1.0f : 0.0f; - pContextData->m_SemiStaticCmdsOut.SetPixelShaderConstant( 21, &editorBlend, 1 ); + // TODO: This is inefficient use of a constant; Something should be done about this in the future + static const float editorBlend[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + static const float regularBlend[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + pContextData->m_SemiStaticCmdsOut.SetPixelShaderConstant( 21, (bEditorBlend ? editorBlend : regularBlend), 1 ); /* if (bEditorBlend) { diff --git a/sp/src/materialsystem/stdshaders/windowimposter_dx90.cpp b/sp/src/materialsystem/stdshaders/windowimposter_dx90.cpp index 99ac791488..d24ac04b0e 100644 --- a/sp/src/materialsystem/stdshaders/windowimposter_dx90.cpp +++ b/sp/src/materialsystem/stdshaders/windowimposter_dx90.cpp @@ -7,9 +7,9 @@ #include "BaseVSShader.h" #include "cpp_shader_constant_register_map.h" -#include "sdk_windowimposter_vs20.inc" -#include "sdk_windowimposter_ps20.inc" -#include "sdk_windowimposter_ps20b.inc" +#include "SDK_windowimposter_vs20.inc" +#include "SDK_windowimposter_ps20.inc" +#include "SDK_windowimposter_ps20b.inc" diff --git a/sp/src/mathlib/polyhedron.cpp b/sp/src/mathlib/polyhedron.cpp index 5a858f194f..5c57671a49 100644 --- a/sp/src/mathlib/polyhedron.cpp +++ b/sp/src/mathlib/polyhedron.cpp @@ -1015,12 +1015,12 @@ CPolyhedron *ClipLinkedGeometry( GeneratePolyhedronFromPlanes_UnorderedPolygonLL //Scan for onplane points connected to only other onplane/dead points, these points get downgraded to dead status. { - GeneratePolyhedronFromPlanes_UnorderedPointLL *pActivePointWalk = pAllPoints; + GeneratePolyhedronFromPlanes_UnorderedPointLL *pActivePointWalkLocl = pAllPoints; do { - if( pActivePointWalk->pPoint->planarity == POINT_ONPLANE ) + if( pActivePointWalkLocl->pPoint->planarity == POINT_ONPLANE ) { - GeneratePolyhedronFromPlanes_LineLL *pOnPlaneLineWalk = pActivePointWalk->pPoint->pConnectedLines; + GeneratePolyhedronFromPlanes_LineLL *pOnPlaneLineWalk = pActivePointWalkLocl->pPoint->pConnectedLines; GeneratePolyhedronFromPlanes_LineLL *pStartLineWalk = pOnPlaneLineWalk; bool bDead = true; //assume it's dead and disprove do @@ -1047,7 +1047,7 @@ CPolyhedron *ClipLinkedGeometry( GeneratePolyhedronFromPlanes_UnorderedPolygonLL if( bDead ) { - pActivePointWalk->pPoint->planarity = POINT_DEAD; + pActivePointWalkLocl->pPoint->planarity = POINT_DEAD; pOnPlaneLineWalk = pStartLineWalk; @@ -1059,8 +1059,8 @@ CPolyhedron *ClipLinkedGeometry( GeneratePolyhedronFromPlanes_UnorderedPolygonLL } while( pOnPlaneLineWalk != pStartLineWalk ); } } - pActivePointWalk = pActivePointWalk->pNext; - } while( pActivePointWalk ); + pActivePointWalkLocl = pActivePointWalkLocl->pNext; + } while( pActivePointWalkLocl ); } #ifdef _DEBUG PlaneCutHistory.AddToTail( &pOutwardFacingPlanes[iCurrentPlane * 4] ); @@ -1337,17 +1337,17 @@ CPolyhedron *ClipLinkedGeometry( GeneratePolyhedronFromPlanes_UnorderedPolygonLL //verify that the new point isn't sitting on top of another { - GeneratePolyhedronFromPlanes_UnorderedPointLL *pActivePointWalk = pAllPoints; + GeneratePolyhedronFromPlanes_UnorderedPointLL *pActivePointWalkLocl = pAllPoints; do { - if( pActivePointWalk->pPoint != pNewPoint ) + if( pActivePointWalkLocl->pPoint != pNewPoint ) { - Vector vDiff = pActivePointWalk->pPoint->ptPosition - pNewPoint->ptPosition; + Vector vDiff = pActivePointWalkLocl->pPoint->ptPosition - pNewPoint->ptPosition; AssertMsg_DumpPolyhedron( vDiff.Length() > fOnPlaneEpsilon, "Generated a point on top of another" ); } - pActivePointWalk = pActivePointWalk->pNext; - } while( pActivePointWalk ); + pActivePointWalkLocl = pActivePointWalk->pNext; + } while( pActivePointWalkLocl ); } #endif diff --git a/sp/src/public/ScratchPadUtils.cpp b/sp/src/public/ScratchPadUtils.cpp index dfb93b0d4a..6e020aaf75 100644 --- a/sp/src/public/ScratchPadUtils.cpp +++ b/sp/src/public/ScratchPadUtils.cpp @@ -141,11 +141,11 @@ void CScratchPadGraph::UpdateTicksAndStuff( float flTime, float flValue ) // Extend the lines attached to the time labels. for ( int i=0; i < m_nTimeLabelsDrawn; i++ ) { - float flTime = m_flTimeOrigin + m_nTimeLabelsDrawn * m_flTimeLabelEveryNSeconds; + float flTimeLocl = m_flTimeOrigin + m_nTimeLabelsDrawn * m_flTimeLabelEveryNSeconds; m_pPad->DrawLine( - CSPVert((const Vector&) GetSamplePosition( flTime, m_flHighestValue )), - CSPVert((const Vector&) GetSamplePosition( flTime, flValue ) ) + CSPVert((const Vector&) GetSamplePosition( flTimeLocl, m_flHighestValue )), + CSPVert((const Vector&) GetSamplePosition( flTimeLocl, flValue ) ) ); } @@ -158,21 +158,21 @@ void CScratchPadGraph::UpdateTicksAndStuff( float flTime, float flValue ) { CTextParams params; - float flTime = m_flTimeOrigin + m_nTimeLabelsDrawn * m_flTimeLabelEveryNSeconds; + float flTimeLocl = m_flTimeOrigin + m_nTimeLabelsDrawn * m_flTimeLabelEveryNSeconds; params.m_bSolidBackground = true; - params.m_vPos = GetSamplePosition( flTime, m_flValueOrigin-5 ); + params.m_vPos = GetSamplePosition( flTimeLocl, m_flValueOrigin-5 ); params.m_bTwoSided = true; char str[512]; - Q_snprintf( str, sizeof( str ), "time: %.2f", flTime ); + Q_snprintf( str, sizeof( str ), "time: %.2f", flTimeLocl ); m_pPad->DrawText( str, params ); // Now draw the vertical line for the value.. m_pPad->DrawLine( - CSPVert( (const Vector&)GetSamplePosition( flTime, m_flValueOrigin ) ), - CSPVert( (const Vector&)GetSamplePosition( flTime, m_flHighestValue ) ) + CSPVert( (const Vector&)GetSamplePosition( flTimeLocl, m_flValueOrigin ) ), + CSPVert( (const Vector&)GetSamplePosition( flTimeLocl, m_flHighestValue ) ) ); diff --git a/sp/src/public/bitmap/imageformat.h b/sp/src/public/bitmap/imageformat.h index 1a35cab158..4a6a8ce007 100644 --- a/sp/src/public/bitmap/imageformat.h +++ b/sp/src/public/bitmap/imageformat.h @@ -21,7 +21,7 @@ enum NormalDecodeMode_t }; // Forward declaration -#ifdef _WIN32 +#if defined(_WIN32) && _MSC_VER < 1900 typedef enum _D3DFORMAT D3DFORMAT; #endif @@ -103,7 +103,7 @@ enum ImageFormat NUM_IMAGE_FORMATS }; -#if defined( POSIX ) || defined( DX_TO_GL_ABSTRACTION ) +#if defined( POSIX ) || defined( DX_TO_GL_ABSTRACTION ) || _MSC_VER >= 1900 typedef enum _D3DFORMAT { D3DFMT_INDEX16, diff --git a/sp/src/public/bone_setup.cpp b/sp/src/public/bone_setup.cpp index 250133d5c0..0f1f5c47ff 100644 --- a/sp/src/public/bone_setup.cpp +++ b/sp/src/public/bone_setup.cpp @@ -933,8 +933,8 @@ static void CalcVirtualAnimation( virtualmodel_t *pVModel, const CStudioHdr *pSt { if (pStudioHdr->boneFlags(i) & boneMask) { - int j = pSeqGroup->boneMap[i]; - if (j >= 0 && pweight[j] > 0.0f) + int l = pSeqGroup->boneMap[i]; + if (l >= 0 && pweight[l] > 0.0f) { if (animdesc.flags & STUDIO_DELTA) { @@ -943,13 +943,13 @@ static void CalcVirtualAnimation( virtualmodel_t *pVModel, const CStudioHdr *pSt } else if (pSeqLinearBones) { - q[i] = pSeqLinearBones->quat(j); - pos[i] = pSeqLinearBones->pos(j); + q[i] = pSeqLinearBones->quat(l); + pos[i] = pSeqLinearBones->pos(l); } else { - q[i] = pSeqbone[j].quat; - pos[i] = pSeqbone[j].pos; + q[i] = pSeqbone[l].quat; + pos[i] = pSeqbone[l].pos; } #ifdef STUDIO_ENABLE_PERF_COUNTERS pStudioHdr->m_nPerfUsedBones++; @@ -997,10 +997,9 @@ static void CalcVirtualAnimation( virtualmodel_t *pVModel, const CStudioHdr *pSt matrix3x4_t *boneToWorld = g_MatrixPool.Alloc(); CBoneBitList boneComputed; - int i; - for (i = 0; i < animdesc.numlocalhierarchy; i++) + for (int l = 0; i < animdesc.numlocalhierarchy; i++) { - mstudiolocalhierarchy_t *pHierarchy = animdesc.pHierarchy( i ); + mstudiolocalhierarchy_t *pHierarchy = animdesc.pHierarchy( l ); if ( !pHierarchy ) break; @@ -1141,10 +1140,9 @@ static void CalcAnimation( const CStudioHdr *pStudioHdr, Vector *pos, Quaternion matrix3x4_t *boneToWorld = g_MatrixPool.Alloc(); CBoneBitList boneComputed; - int i; - for (i = 0; i < animdesc.numlocalhierarchy; i++) + for (int j = 0; j < animdesc.numlocalhierarchy; j++) { - mstudiolocalhierarchy_t *pHierarchy = animdesc.pHierarchy( i ); + mstudiolocalhierarchy_t *pHierarchy = animdesc.pHierarchy( j ); if ( !pHierarchy ) break; @@ -5610,9 +5608,9 @@ bool Studio_AnimPosition( mstudioanimdesc_t *panim, float flCycle, Vector &vecPo vecAngle.y = vecAngle.y * (1 - f) + pmove->angle * f; if (iLoops != 0) { - mstudiomovement_t *pmove = panim->pMovement( panim->nummovements - 1 ); - vecPos = vecPos + iLoops * pmove->position; - vecAngle.y = vecAngle.y + iLoops * pmove->angle; + mstudiomovement_t *pmoveLocl = panim->pMovement( panim->nummovements - 1 ); + vecPos = vecPos + iLoops * pmoveLocl->position; + vecAngle.y = vecAngle.y + iLoops * pmoveLocl->angle; } return true; } diff --git a/sp/src/public/datamap.h b/sp/src/public/datamap.h index 34a1caf898..b25bb1724a 100644 --- a/sp/src/public/datamap.h +++ b/sp/src/public/datamap.h @@ -313,6 +313,12 @@ struct datamap_t static datamap_t *GetBaseMap(); \ template friend void DataMapAccess(T *, datamap_t **p); \ template friend datamap_t *DataMapInit(T *); + +#define DECLARE_SIMPLE_DATADESC_INSIDE_NAMESPACE() \ + static datamap_t m_DataMap; \ + static datamap_t *GetBaseMap(); \ + template friend void ::DataMapAccess(T *, datamap_t **p); \ + template friend datamap_t *::DataMapInit(T *); #define DECLARE_DATADESC() \ DECLARE_SIMPLE_DATADESC() \ @@ -414,6 +420,8 @@ inline void DataMapAccess(T *ignored, datamap_t **p) *p = &T::m_DataMap; } +template datamap_t* DataMapInit(T*); + //----------------------------------------------------------------------------- class CDatadescGeneratedNameHolder diff --git a/sp/src/public/dt_utlvector_send.cpp b/sp/src/public/dt_utlvector_send.cpp index b10170ded0..06f78c64cc 100644 --- a/sp/src/public/dt_utlvector_send.cpp +++ b/sp/src/public/dt_utlvector_send.cpp @@ -134,7 +134,7 @@ void* SendProxy_LengthTable( const SendProp *pProp, const void *pStructBase, con // Note: you have to be DILIGENT about calling NetworkStateChanged whenever an element in your CUtlVector changes // since CUtlVector doesn't do this automatically. SendProp SendPropUtlVector( - char *pVarName, // Use SENDINFO_UTLVECTOR to generate these 4. + const char *pVarName, // Use SENDINFO_UTLVECTOR to generate these 4. int offset, // Used to generate pData in the function specified in varProxy. int sizeofVar, // The size of each element in the utlvector. EnsureCapacityFn ensureFn, // This is the value returned for elements out of the array's current range. diff --git a/sp/src/public/dt_utlvector_send.h b/sp/src/public/dt_utlvector_send.h index f2ba68c360..3684d5b500 100644 --- a/sp/src/public/dt_utlvector_send.h +++ b/sp/src/public/dt_utlvector_send.h @@ -43,7 +43,7 @@ // ) // SendProp SendPropUtlVector( - char *pVarName, // Use SENDINFO_UTLVECTOR to generate these first 4 parameters. + const char *pVarName, // Use SENDINFO_UTLVECTOR to generate these first 4 parameters. int offset, int sizeofVar, EnsureCapacityFn ensureFn, diff --git a/sp/src/public/haptics/haptic_utils.cpp b/sp/src/public/haptics/haptic_utils.cpp index b9c72b2fb7..e6d3288a5c 100644 --- a/sp/src/public/haptics/haptic_utils.cpp +++ b/sp/src/public/haptics/haptic_utils.cpp @@ -138,6 +138,12 @@ void ConnectHaptics(CreateInterfaceFn appFactory) HookHapticMessages(); } +#if _MSC_VER >= 1925 +// deleting haptics results in a warning about deleting something with a non-virtual destructor +// big yikes but we can't do anything about it as it's accessed via interface +#pragma warning (disable: 5205) +#endif + void DisconnectHaptics() { haptics->ShutdownHaptics(); diff --git a/sp/src/public/keyframe/keyframe.cpp b/sp/src/public/keyframe/keyframe.cpp index 1e08b0a305..425a172ece 100644 --- a/sp/src/public/keyframe/keyframe.cpp +++ b/sp/src/public/keyframe/keyframe.cpp @@ -154,7 +154,7 @@ class CPositionInterpolator_Linear : public IPositionInterpolator { public: virtual void Release(); - virtual void GetDetails( char **outName, int *outMinKeyReq, int *outMaxKeyReq ); + virtual void GetDetails( const char **outName, int *outMinKeyReq, int *outMaxKeyReq ); virtual void SetKeyPosition( int keyNum, Vector const &vPos ); virtual void InterpolatePosition( float time, Vector &vOut ); virtual bool ProcessKey( char const *pName, char const *pValue ) { return false; } @@ -171,7 +171,7 @@ void CPositionInterpolator_Linear::Release() { } -void CPositionInterpolator_Linear::GetDetails( char **outName, int *outMinKeyReq, int *outMaxKeyReq ) +void CPositionInterpolator_Linear::GetDetails( const char **outName, int *outMinKeyReq, int *outMaxKeyReq ) { *outName = "Linear"; *outMinKeyReq = 0; @@ -201,7 +201,7 @@ class CPositionInterpolator_CatmullRom : public IPositionInterpolator { public: virtual void Release(); - virtual void GetDetails( char **outName, int *outMinKeyReq, int *outMaxKeyReq ); + virtual void GetDetails( const char **outName, int *outMinKeyReq, int *outMaxKeyReq ); virtual void SetKeyPosition( int keyNum, Vector const &vPos ); virtual void InterpolatePosition( float time, Vector &vOut ); virtual bool ProcessKey( char const *pName, char const *pValue ) { return false; } @@ -218,7 +218,7 @@ void CPositionInterpolator_CatmullRom::Release() { } -void CPositionInterpolator_CatmullRom::GetDetails( char **outName, int *outMinKeyReq, int *outMaxKeyReq ) +void CPositionInterpolator_CatmullRom::GetDetails( const char **outName, int *outMinKeyReq, int *outMaxKeyReq ) { *outName = "Catmull-Rom Spline"; *outMinKeyReq = -1; @@ -282,7 +282,7 @@ class CPositionInterpolator_Rope : public IPositionInterpolator CPositionInterpolator_Rope(); virtual void Release(); - virtual void GetDetails( char **outName, int *outMinKeyReq, int *outMaxKeyReq ); + virtual void GetDetails( const char **outName, int *outMinKeyReq, int *outMaxKeyReq ); virtual void SetKeyPosition( int keyNum, Vector const &vPos ); virtual void InterpolatePosition( float time, Vector &vOut ); virtual bool ProcessKey( char const *pName, char const *pValue ); @@ -319,7 +319,7 @@ void CPositionInterpolator_Rope::Release() delete this; } -void CPositionInterpolator_Rope::GetDetails( char **outName, int *outMinKeyReq, int *outMaxKeyReq ) +void CPositionInterpolator_Rope::GetDetails( const char **outName, int *outMinKeyReq, int *outMaxKeyReq ) { *outName = "Rope"; *outMinKeyReq = 0; @@ -433,7 +433,7 @@ typedef void (*RotationInterpolatorFunc_t)(float time, Quaternion &outRot); typedef struct { - char *szName; + const char *szName; RotationInterpolatorFunc_t pFunc; // defines the range of keys this interpolator needs to function @@ -458,7 +458,7 @@ int Motion_GetNumberOfRotationInterpolators( void ) return ARRAYSIZE(g_RotationInterpolators); } -bool Motion_GetRotationInterpolatorDetails( int rotInterpNum, char **outName, int *outMinKeyReq, int *outMaxKeyReq ) +bool Motion_GetRotationInterpolatorDetails( int rotInterpNum, const char **outName, int *outMinKeyReq, int *outMaxKeyReq ) { if ( rotInterpNum < 0 || rotInterpNum >= Motion_GetNumberOfRotationInterpolators() ) { diff --git a/sp/src/public/keyframe/keyframe.h b/sp/src/public/keyframe/keyframe.h index b2cfd27c4c..4ee0482439 100644 --- a/sp/src/public/keyframe/keyframe.h +++ b/sp/src/public/keyframe/keyframe.h @@ -14,7 +14,7 @@ class IPositionInterpolator public: virtual void Release() = 0; - virtual void GetDetails( char **outName, int *outMinKeyReq, int *outMaxKeyReq ) = 0; + virtual void GetDetails( const char **outName, int *outMinKeyReq, int *outMaxKeyReq ) = 0; virtual void SetKeyPosition( int keyNum, Vector const &vPos ) = 0; virtual void InterpolatePosition( float time, Vector &vOut ) = 0; @@ -34,7 +34,7 @@ IPositionInterpolator* Motion_GetPositionInterpolator( int interpNum ); // Rotation interpolators. int Motion_GetNumberOfRotationInterpolators( void ); -bool Motion_GetRotationInterpolatorDetails( int rotInterpNum, char **outName, int *outMinKeyReq, int *outMaxKeyReq ); +bool Motion_GetRotationInterpolatorDetails( int rotInterpNum, const char **outName, int *outMinKeyReq, int *outMaxKeyReq ); bool Motion_InterpolateRotation( float time, int interpFuncNum, Quaternion &outQuatRotation ); bool Motion_SetKeyAngles( int keyNum, Quaternion &quatAngles ); diff --git a/sp/src/public/materialsystem/MaterialSystemUtil.h b/sp/src/public/materialsystem/MaterialSystemUtil.h index 3f22e918c7..ddabcd220b 100644 --- a/sp/src/public/materialsystem/MaterialSystemUtil.h +++ b/sp/src/public/materialsystem/MaterialSystemUtil.h @@ -72,6 +72,10 @@ class CTextureReference void Init( char const* pTexture, const char *pTextureGroupName, bool bComplain = true ); void InitProceduralTexture( const char *pTextureName, const char *pTextureGroupName, int w, int h, ImageFormat fmt, int nFlags ); void InitRenderTarget( int w, int h, RenderTargetSizeMode_t sizeMode, ImageFormat fmt, MaterialRenderTargetDepth_t depth, bool bHDR, char *pStrOptionalName = NULL ); + void InitRenderTarget(int w, int h, RenderTargetSizeMode_t sizeMode, ImageFormat fmt, MaterialRenderTargetDepth_t depth, bool bHDR, const char* pStrOptionalName = NULL) + { + InitRenderTarget(w, h, sizeMode, fmt, depth, bHDR, const_cast(pStrOptionalName)); + } #if defined( _X360 ) // used when RT coupling is disparate (texture is DDR based, surface is EDRAM based) void InitRenderTargetTexture( int width, int height, RenderTargetSizeMode_t sizeMode, ImageFormat fmt, MaterialRenderTargetDepth_t depth, bool bHDR, char *pStrOptionalName = NULL ); diff --git a/sp/src/public/networkvar.h b/sp/src/public/networkvar.h index 142b35e9e3..46a92de081 100644 --- a/sp/src/public/networkvar.h +++ b/sp/src/public/networkvar.h @@ -744,9 +744,9 @@ class CNetworkQuaternionBase : public CNetworkVarBase< Type, Changer > const type* Base() const { return m_Value; } \ int Count() const { return count; } \ protected: \ - inline void NetworkStateChanged( int index ) \ + inline void NetworkStateChanged( int _index ) \ { \ - CHECK_USENETWORKVARS ((ThisClass*)(((char*)this) - MyOffsetOf(ThisClass,name)))->stateChangedFn( &m_Value[index] ); \ + CHECK_USENETWORKVARS ((ThisClass*)(((char*)this) - MyOffsetOf(ThisClass,name)))->stateChangedFn( &m_Value[_index] ); \ } \ type m_Value[count]; \ }; \ diff --git a/sp/src/public/responserules/response_host_interface.h b/sp/src/public/responserules/response_host_interface.h new file mode 100644 index 0000000000..fa39bd88cc --- /dev/null +++ b/sp/src/public/responserules/response_host_interface.h @@ -0,0 +1,66 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Core types for the response rules -- criteria, responses, rules, and matchers. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef RESPONSE_HOST_INTERFACE_H +#define RESPONSE_HOST_INTERFACE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "filesystem.h" +class IUniformRandomStream; +class ICommandLine; + +namespace ResponseRules +{ + // FUNCTIONS YOU MUST IMPLEMENT IN THE HOST EXECUTABLE: + // These are functions that are mentioned in the header, but need their bodies implemented + // in the .dll that links against this lib. + // This is to wrap functions that previously came from the engine interface + // back when the response rules were inside the server.dll . Now that the rules + // are included into a standalone editor, we don't necessarily have an engine around, + // so there needs to be some other implementation. + abstract_class IEngineEmulator + { + public: + /// Given an input text buffer data pointer, parses a single token into the variable token and returns the new + /// reading position + virtual const char *ParseFile( const char *data, char *token, int maxlen ) = 0; + +#ifdef MAPBASE + /// (Optional) Same as ParseFile, but with casing preserved and escaped quotes supported + virtual const char *ParseFilePreserve( const char *data, char *token, int maxlen ) { return ParseFile( data, token, maxlen ); } +#endif + + /// Return a pointer to an IFileSystem we can use to read and process scripts. + virtual IFileSystem *GetFilesystem() = 0; + + /// Return a pointer to an instance of an IUniformRandomStream + virtual IUniformRandomStream *GetRandomStream() = 0 ; + + /// Return a pointer to a tier0 ICommandLine + virtual ICommandLine *GetCommandLine() = 0; + + /// Emulates the server's UTIL_LoadFileForMe + virtual byte *LoadFileForMe( const char *filename, int *pLength ) = 0; + + /// Emulates the server's UTIL_FreeFile + virtual void FreeFile( byte *buffer ) = 0; + + + /// Somewhere in the host executable you should define this symbol and + /// point it at a singleton instance. + static IEngineEmulator *s_pSingleton; + + // this is just a function that returns the pointer above -- just in + // case we need to define it differently. And I get asserts this way. + static IEngineEmulator *Get(); + }; +}; + + +#endif \ No newline at end of file diff --git a/sp/src/public/responserules/response_types.h b/sp/src/public/responserules/response_types.h new file mode 100644 index 0000000000..2e80cc5aae --- /dev/null +++ b/sp/src/public/responserules/response_types.h @@ -0,0 +1,480 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Core types for the response rules -- criteria, responses, rules, and matchers. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef RESPONSE_TYPES_H +#define RESPONSE_TYPES_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tier1/utlrbtree.h" +#include "tier1/utlsymbol.h" +#include "tier1/interval.h" +#include "mathlib/compressed_vector.h" +#include "datamap.h" +#include "soundflags.h" +#include "tier1/utlsymbol.h" + +namespace ResponseRules +{ + /// Custom symbol table for the response rules. + extern CUtlSymbolTable g_RS; +}; + +#ifdef _MANAGED +// forward declare some editor types just so we can friend them. +namespace ResponseRulesCLI +{ + ref class ResponseQueryResult; +} +#endif + +namespace ResponseRules +{ + using ::DataMapAccess; + // using ::DataMapInit; + class CResponseSystem; + +#pragma pack(push,1) + template + struct response_interval_t + { + T start; + T range; + + interval_t &ToInterval( interval_t &dest ) const { dest.start = start; dest.range = range; return dest; } + void FromInterval( const interval_t &from ) { start = from.start; range = from.range; } + float Random() const { interval_t temp = { start, range }; return RandomInterval( temp ); } + }; + + typedef response_interval_t responseparams_interval_t; +#pragma pack(pop) + +#pragma pack(push,1) + struct AI_ResponseFollowup + { + + + // TODO: make less wasteful of memory, by using a symbol table. + const char *followup_concept; // 12 -- next response + const char *followup_contexts; // 16 + float followup_delay; // 20 + const char *followup_target; // 24 -- to whom is this despatched? + // AIConceptHandle_t hConcept; + const char *followup_entityiotarget; //< if this rule involves firing entity io + const char *followup_entityioinput; //< if this rule involves firing entity io + float followup_entityiodelay; + bool bFired; + + inline bool IsValid( void ) const { return (followup_concept && followup_contexts); } + inline void Invalidate() { followup_concept = NULL; followup_contexts = NULL; } + inline void SetFired( bool fired ) { bFired = fired; } + inline bool HasBeenFired() { return bFired; } + + AI_ResponseFollowup( void ) : followup_concept(NULL), followup_contexts(NULL), followup_delay(0), followup_target(NULL), followup_entityiotarget(NULL), followup_entityioinput(NULL), followup_entityiodelay(0), bFired(false) + {}; + AI_ResponseFollowup( char *_followup_concept, char *_followup_contexts, float _followup_delay, char *_followup_target, + char *_followup_entityiotarget, char *_followup_entityioinput, float _followup_entityiodelay ) : + followup_concept(_followup_concept), followup_contexts(_followup_contexts), followup_delay(_followup_delay), followup_target(_followup_target), + followup_entityiotarget(_followup_entityiotarget), followup_entityioinput(_followup_entityioinput), followup_entityiodelay(_followup_entityiodelay), + bFired(false) + {}; + }; +#pragma pack(pop) + + + enum ResponseType_t + { + RESPONSE_NONE = 0, + RESPONSE_SPEAK, + RESPONSE_SENTENCE, + RESPONSE_SCENE, + RESPONSE_RESPONSE, // A reference to another response by name + RESPONSE_PRINT, + RESPONSE_ENTITYIO, // poke an input on an entity +#ifdef MAPBASE + RESPONSE_VSCRIPT, // Run VScript code + RESPONSE_VSCRIPT_FILE, // Run a VScript file (bypasses ugliness and character limits when just using IncludeScript() with RESPONSE_VSCRIPT) +#endif + + NUM_RESPONSES, + }; + +#ifdef MAPBASE + // The "apply to world" context option has been replaced with a flag-based integer which can apply contexts to more things. + // + // New ones should be implemented in: + // CResponseSystem::BuildDispatchTables() - AI_ResponseSystem.cpp (with their own funcs for m_RuleDispatch) + // CRR_Response::Describe() - rr_response.cpp + // CAI_Expresser::SpeakDispatchResponse() - ai_speech.cpp + // + // Also mind that this is 8-bit + enum : uint8 + { + APPLYCONTEXT_SELF = (1 << 0), // Included for contexts that apply to both self and something else + APPLYCONTEXT_WORLD = (1 << 1), // Apply to world + + APPLYCONTEXT_SQUAD = (1 << 2), // Apply to squad + APPLYCONTEXT_ENEMY = (1 << 3), // Apply to enemy + }; +#endif + + +#pragma pack(push,1) + struct ResponseParams + { + DECLARE_SIMPLE_DATADESC_INSIDE_NAMESPACE(); + + enum + { + RG_DELAYAFTERSPEAK = (1<<0), + RG_SPEAKONCE = (1<<1), + RG_ODDS = (1<<2), + RG_RESPEAKDELAY = (1<<3), + RG_SOUNDLEVEL = (1<<4), + RG_DONT_USE_SCENE = (1<<5), + RG_STOP_ON_NONIDLE = (1<<6), + RG_WEAPONDELAY = (1<<7), + RG_DELAYBEFORESPEAK = (1<<8), + }; + + ResponseParams() + { + flags = 0; + odds = 100; + delay.start = 0; + delay.range = 0; + respeakdelay.start = 0; + respeakdelay.range = 0; + weapondelay.start = 0; + weapondelay.range = 0; + soundlevel = 0; + predelay.start = 0; + predelay.range = 0; + } + responseparams_interval_t delay; //4 + responseparams_interval_t respeakdelay; //8 + responseparams_interval_t weapondelay; //12 + + short odds; //14 + + short flags; //16 + byte soundlevel; //17 + + responseparams_interval_t predelay; //21 + + ALIGN32 AI_ResponseFollowup *m_pFollowup; + + }; +#pragma pack(pop) + + class CriteriaSet + { + public: + typedef CUtlSymbol CritSymbol_t; ///< just to make it clear that some symbols come out of our special static table + public: + CriteriaSet(); + CriteriaSet( const CriteriaSet& src ); + CriteriaSet( const char *criteria, const char *value ) ; // construct initialized with a key/value pair (convenience) + ~CriteriaSet(); + + static CritSymbol_t ComputeCriteriaSymbol( const char *criteria ); + void AppendCriteria( CritSymbol_t criteria, const char *value = "", float weight = 1.0f ); + void AppendCriteria( const char *criteria, const char *value = "", float weight = 1.0f ); + void AppendCriteria( const char *criteria, float value, float weight = 1.0f ); + void RemoveCriteria( const char *criteria ); + + void Describe() const; + + int GetCount() const; + int FindCriterionIndex( CritSymbol_t criteria ) const; + int FindCriterionIndex( const char *name ) const; + inline bool IsValidIndex( int index ) const; + + CritSymbol_t GetNameSymbol( int nIndex ) const; + inline static const char *SymbolToStr( const CritSymbol_t &symbol ); + const char *GetName( int index ) const; + const char *GetValue( int index ) const; + float GetWeight( int index ) const; + + /// Merge another CriteriaSet into this one. + void Merge( const CriteriaSet *otherCriteria ); + void Merge( const char *modifiers ); // add criteria parsed from a text string + + /// add all of the contexts herein onto an entity. all durations are infinite. + void WriteToEntity( CBaseEntity *pEntity ); + + // Accessors to things that need only be done under unusual circumstances. + inline void EnsureCapacity( int num ); + void Reset(); // clear out this criteria (should not be necessary) + + /// When this is true, calls to AppendCriteria on a criteria that already exists + /// will override the existing value. (This is the default behavior). Can be temporarily + /// set false to prevent such overrides. + inline void OverrideOnAppend( bool bOverride ) { m_bOverrideOnAppend = bOverride; } + + // For iteration from beginning to end (also should not be necessary except in + // save/load) + inline int Head() const; + inline int Next( int i ) const; // use with IsValidIndex above + + const static char kAPPLYTOWORLDPREFIX = '$'; + + /// A last minute l4d2 change: deferred contexts prefixed with a '$' + /// character are actually applied to the world. This matches the + /// related hack in CBaseEntity::AppplyContext. + /// This function works IN-PLACE on the "from" parameter. + /// any $-prefixed criteria in pFrom become prefixed by "world", + /// and are also written into pSetOnWorld. + /// *IF* a response matches using the modified criteria, then and only + /// then should you write back the criteria in pSetOnWorld to the world + /// entity, subsequent to the match but BEFORE the dispatch. + /// Returns the number of contexts modified. If it returns 0, then + /// pSetOnWorld is empty. + static int InterceptWorldSetContexts( CriteriaSet * RESTRICT pFrom, + CriteriaSet * RESTRICT pSetOnWorld ); + + private: + void RemoveCriteria( int idx, bool bTestForPrefix ); + + struct CritEntry_t + { + CritEntry_t() : + criterianame( UTL_INVAL_SYMBOL ), + weight( 0.0f ) + { + value[ 0 ] = 0; + } + + CritEntry_t( const CritEntry_t& src ) + { + criterianame = src.criterianame; + value[ 0 ] = 0; + weight = src.weight; + SetValue( src.value ); + } + + CritEntry_t& operator=( const CritEntry_t& src ) + { + if ( this == &src ) + return *this; + + criterianame = src.criterianame; + weight = src.weight; + SetValue( src.value ); + + return *this; + } + + static bool LessFunc( const CritEntry_t& lhs, const CritEntry_t& rhs ) + { + return lhs.criterianame < rhs.criterianame; + } + + void SetValue( char const *str ) + { + if ( !str ) + { + value[ 0 ] = 0; + } + else + { + Q_strncpy( value, str, sizeof( value ) ); + } + } + + CritSymbol_t criterianame; + char value[ 64 ]; + float weight; + }; + + static CUtlSymbolTable sm_CriteriaSymbols; + typedef CUtlRBTree< CritEntry_t, short > Dict_t; + Dict_t m_Lookup; + int m_nNumPrefixedContexts; // number of contexts prefixed with kAPPLYTOWORLDPREFIX + bool m_bOverrideOnAppend; + }; + + inline void CriteriaSet::EnsureCapacity( int num ) + { + m_Lookup.EnsureCapacity(num); + } + + //----------------------------------------------------------------------------- + // Purpose: Generic container for a response to a match to a criteria set + // This is what searching for a response returns + //----------------------------------------------------------------------------- + + class CRR_Response + { + public: + DECLARE_SIMPLE_DATADESC_INSIDE_NAMESPACE(); + + CRR_Response(); + CRR_Response( const CRR_Response &from ); + CRR_Response &operator=( const CRR_Response &from ); + ~CRR_Response(); + private: + void operator delete(void* p); // please do not new or delete CRR_Responses. + public: + + // void Release(); // we no longer encourage new and delete on these things + + void GetName( char *buf, size_t buflen ) const; + void GetResponse( char *buf, size_t buflen ) const; +#ifdef MAPBASE + void GetRule( char *buf, size_t buflen ) const; +#endif + const char* GetNamePtr() const; + const char* GetResponsePtr() const; + const ResponseParams *GetParams() const { return &m_Params; } + ResponseType_t GetType() const { return (ResponseType_t)m_Type; } + soundlevel_t GetSoundLevel() const; + float GetRespeakDelay() const; + float GetWeaponDelay() const; + bool GetSpeakOnce() const; + bool ShouldntUseScene( ) const; + bool ShouldBreakOnNonIdle( void ) const; + int GetOdds() const; + float GetDelay() const; + float GetPreDelay() const; + + inline bool IsEmpty() const; // true iff my response name is empty + void Invalidate() ; // wipe out my contents, mark me invalid + + // Get/set the contexts we apply to character and world after execution + void SetContext( const char *context ); + const char * GetContext( void ) const { return m_szContext; } + + // Get/set the score I matched with (under certain circumstances) + inline float GetMatchScore( void ) { return m_fMatchScore; } + inline void SetMatchScore( float f ) { m_fMatchScore = f; } + +#ifdef MAPBASE + int GetContextFlags() { return m_iContextFlags; } + bool IsApplyContextToWorld( void ) { return (m_iContextFlags & APPLYCONTEXT_WORLD) != 0; } + + inline short *GetInternalIndices() { return m_InternalIndices; } + inline void SetInternalIndices( short iGroup, short iWithinGroup ) { m_InternalIndices[0] = iGroup; m_InternalIndices[1] = iWithinGroup; } +#else + bool IsApplyContextToWorld( void ) { return m_bApplyContextToWorld; } +#endif + + void Describe( const CriteriaSet *pDebugCriteria = NULL ); + + void Init( ResponseType_t type, + const char *responseName, + const ResponseParams& responseparams, + const char *matchingRule, + const char *applyContext, + bool bApplyContextToWorld ); + +#ifdef MAPBASE + void Init( ResponseType_t type, + const char *responseName, + const ResponseParams& responseparams, + const char *matchingRule, + const char *applyContext, + int iContextFlags ); +#endif + + static const char *DescribeResponse( ResponseType_t type ); + + enum + { + MAX_RESPONSE_NAME = 64, + MAX_RULE_NAME = 64 + }; + + + private: + byte m_Type; + char m_szResponseName[ MAX_RESPONSE_NAME ]; + char m_szMatchingRule[ MAX_RULE_NAME ]; + + ResponseParams m_Params; + float m_fMatchScore; // when instantiated dynamically in SpeakFindResponse, the score of the rule that matched it. + + char * m_szContext; // context data we apply to character after running +#ifdef MAPBASE + int m_iContextFlags; + + // The response's original indices in the system. [0] is the group's index, [1] is the index within the group. + // For now, this is only set in prospecctive mode. It's used to call back to the ParserResponse and mark a prospectively chosen response as used. + short m_InternalIndices[2]; +#else + bool m_bApplyContextToWorld; +#endif + +#ifdef _MANAGED + friend ref class ResponseRulesCLI::ResponseQueryResult; +#endif + }; + + + + abstract_class IResponseFilter + { + public: + virtual bool IsValidResponse( ResponseType_t type, const char *pszValue ) = 0; + }; + + abstract_class IResponseSystem + { + public: + virtual ~IResponseSystem() {} + + virtual bool FindBestResponse( const CriteriaSet& set, CRR_Response& response, IResponseFilter *pFilter = NULL ) = 0; + virtual void GetAllResponses( CUtlVector *pResponses ) = 0; + virtual void PrecacheResponses( bool bEnable ) = 0; + +#ifdef MAPBASE + // (Optional) Call this before and after using FindBestResponse() for a prospective lookup, e.g. a response that might not actually be used + // and should not trigger displayfirst, etc. + virtual void SetProspective( bool bToggle ) {}; + + // (Optional) Marks a prospective response as used + virtual void MarkResponseAsUsed( short iGroup, short iWithinGroup ) {}; +#endif + }; + + + + // INLINE FUNCTIONS + + // Used as a failsafe in finding responses. + bool CRR_Response::IsEmpty() const + { + return m_szResponseName[0] == 0; + } + + inline bool CriteriaSet::IsValidIndex( int index ) const + { + return ( index >= 0 && index < ((int)(m_Lookup.Count())) ); + } + + inline int CriteriaSet::Head() const + { + return m_Lookup.FirstInorder(); + } + + inline int CriteriaSet::Next( int i ) const + { + return m_Lookup.NextInorder(i); + } + + inline const char *CriteriaSet::SymbolToStr( const CritSymbol_t &symbol ) + { + return sm_CriteriaSymbols.String(symbol); + } + +} + +#include "rr_speechconcept.h" +#include "response_host_interface.h" + +#endif diff --git a/sp/src/public/responserules/rr_speechconcept.h b/sp/src/public/responserules/rr_speechconcept.h new file mode 100644 index 0000000000..65b1bb6e60 --- /dev/null +++ b/sp/src/public/responserules/rr_speechconcept.h @@ -0,0 +1,57 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Class data for an AI Concept, an atom of response-driven dialog. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef RR_SPEECHCONCEPT_H +#define RR_SPEECHCONCEPT_H + +#if defined( _WIN32 ) +#pragma once +#endif + +#include "utlsymbol.h" + +#define RR_CONCEPTS_ARE_STRINGS 0 + + +typedef CUtlSymbolTable CRR_ConceptSymbolTable; + +namespace ResponseRules +{ +class CRR_Concept +{ +public: // local typedefs + typedef CUtlSymbol tGenericId; // an int-like type that can be used to refer to all concepts of this type + tGenericId m_iConcept; + +public: + CRR_Concept() {}; + // construct concept from a string. + CRR_Concept(const char *fromString); + + // Return as a string + const char *GetStringConcept() const; + static const char *GetStringForGenericId(tGenericId genericId); + + operator tGenericId() const { return m_iConcept; } + operator const char *() const { return GetStringConcept(); } + inline bool operator==(const CRR_Concept &other) // default is compare by concept ids + { + return m_iConcept == other.m_iConcept; + } + bool operator==(const char *pszConcept); + +protected: + +private: + // dupe a concept + // CRR_Concept& operator=(CRR_Concept &other); + CRR_Concept& operator=(const char *fromString); +}; +}; + + +#endif diff --git a/sp/src/public/saverestoretypes.h b/sp/src/public/saverestoretypes.h index b92a77c72a..96e5cf5d28 100644 --- a/sp/src/public/saverestoretypes.h +++ b/sp/src/public/saverestoretypes.h @@ -181,7 +181,7 @@ class CGameSaveRestoreInfo { public: CGameSaveRestoreInfo() - : tableCount( 0 ), pTable( 0 ), m_pCurrentEntity( 0 ), m_EntityToIndex( 1024 ) + : m_iTableCount( 0 ), m_pTable( 0 ), m_pCurrentEntity( 0 ), m_EntityToIndex( 1024 ) { memset( &levelInfo, 0, sizeof( levelInfo ) ); modelSpaceOffset.Init( 0, 0, 0 ); @@ -189,8 +189,8 @@ class CGameSaveRestoreInfo void InitEntityTable( entitytable_t *pNewTable = NULL, int size = 0 ) { - pTable = pNewTable; - tableCount = size; + m_pTable = pNewTable; + m_iTableCount = size; for ( int i = 0; i < NumEntities(); i++ ) { @@ -200,17 +200,17 @@ class CGameSaveRestoreInfo entitytable_t *DetachEntityTable() { - entitytable_t *pReturn = pTable; - pTable = NULL; - tableCount = 0; + entitytable_t *pReturn = m_pTable; + m_pTable = NULL; + m_iTableCount = 0; return pReturn; } CBaseEntity *GetCurrentEntityContext() { return m_pCurrentEntity; } void SetCurrentEntityContext(CBaseEntity *pEntity) { m_pCurrentEntity = pEntity; } - int NumEntities() { return tableCount; } - entitytable_t *GetEntityInfo( int i ) { return (pTable + i); } + int NumEntities() { return m_iTableCount; } + entitytable_t *GetEntityInfo( int i ) { return (m_pTable + i); } float GetBaseTime() const { return levelInfo.time; } Vector GetLandmark() const { return ( levelInfo.fUseLandmark ) ? levelInfo.vecLandmarkOffset : vec3_origin; } @@ -218,13 +218,13 @@ class CGameSaveRestoreInfo { #ifdef GAME_DLL int i; - entitytable_t *pTable; + entitytable_t *m_pTable; int nEntities = NumEntities(); for ( i = 0; i < nEntities; i++ ) { - pTable = GetEntityInfo( i ); - m_EntityToIndex.Insert( CHashElement( pTable->hEnt.Get(), i ) ); + m_pTable = GetEntityInfo( i ); + m_EntityToIndex.Insert( CHashElement( m_pTable->hEnt.Get(), i ) ); } #endif } @@ -269,8 +269,8 @@ class CGameSaveRestoreInfo Vector modelSpaceOffset; // used only for globaly entity brushes modelled in different coordinate systems. private: - int tableCount; // Number of elements in the entity table - entitytable_t *pTable; // Array of entitytable_t elements (1 for each entity) + int m_iTableCount; // Number of elements in the entity table + entitytable_t *m_pTable; // Array of entitytable_t elements (1 for each entity) CBaseEntity *m_pCurrentEntity; // only valid during the save functions of this entity, NULL otherwise diff --git a/sp/src/public/scratchpad3d.h b/sp/src/public/scratchpad3d.h index 7e9aad66ea..30b2455cf5 100644 --- a/sp/src/public/scratchpad3d.h +++ b/sp/src/public/scratchpad3d.h @@ -51,7 +51,7 @@ class CScratchPad3D : public IScratchPad3D m_pCachedRenderData = NULL; } - ~CBaseCommand() + virtual ~CBaseCommand() { ReleaseCachedRenderData(); } diff --git a/sp/src/public/sentence.cpp b/sp/src/public/sentence.cpp index 49c24c3c54..1b8efd646a 100644 --- a/sp/src/public/sentence.cpp +++ b/sp/src/public/sentence.cpp @@ -508,7 +508,7 @@ void CSentence::ParseWords( CUtlBuffer& buf ) // Parse phoneme int code; char phonemename[ 256 ]; - float start, end; + float startLocl, endLocl; float volume; code = atoi( token ); @@ -516,9 +516,9 @@ void CSentence::ParseWords( CUtlBuffer& buf ) buf.GetString( token ); Q_strncpy( phonemename, token, sizeof( phonemename ) ); buf.GetString( token ); - start = atof( token ); + startLocl = atof( token ); buf.GetString( token ); - end = atof( token ); + endLocl = atof( token ); buf.GetString( token ); volume = atof( token ); @@ -526,8 +526,8 @@ void CSentence::ParseWords( CUtlBuffer& buf ) assert( pt ); pt->SetPhonemeCode( code ); pt->SetTag( phonemename ); - pt->SetStartTime( start ); - pt->SetEndTime( end ); + pt->SetStartTime( startLocl ); + pt->SetEndTime( endLocl ); AddPhonemeTag( wt, pt ); } @@ -1304,9 +1304,9 @@ void CSentence::Append( float starttime, const CSentence& src ) // Offset times int c = newWord->m_Phonemes.Count(); - for ( int i = 0; i < c; ++i ) + for ( int j = 0; j < c; ++j ) { - CPhonemeTag *tag = newWord->m_Phonemes[ i ]; + CPhonemeTag *tag = newWord->m_Phonemes[ j ]; tag->AddStartTime( starttime ); tag->AddEndTime( starttime ); } diff --git a/sp/src/public/studio.cpp b/sp/src/public/studio.cpp index 9bf97018d2..127840dcbf 100644 --- a/sp/src/public/studio.cpp +++ b/sp/src/public/studio.cpp @@ -516,7 +516,7 @@ void studiohdr_t::SetAttachmentBone( int iAttachment, int iBone ) // Purpose: //----------------------------------------------------------------------------- -char *studiohdr_t::pszNodeName( int iNode ) +const char *studiohdr_t::pszNodeName( int iNode ) { if (numincludemodels == 0) { @@ -565,7 +565,7 @@ int studiohdr_t::GetActivityListVersion( void ) virtualmodel_t *pVModel = (virtualmodel_t *)GetVirtualModel(); Assert( pVModel ); - int version = activitylistversion; + int versionLocl = activitylistversion; int i; for (i = 1; i < pVModel->m_group.Count(); i++) @@ -575,15 +575,15 @@ int studiohdr_t::GetActivityListVersion( void ) Assert( pStudioHdr ); - version = min( version, pStudioHdr->activitylistversion ); + versionLocl = min( versionLocl, pStudioHdr->activitylistversion ); } - return version; + return versionLocl; } -void studiohdr_t::SetActivityListVersion( int version ) const +void studiohdr_t::SetActivityListVersion( int iVersion ) const { - activitylistversion = version; + activitylistversion = iVersion; if (numincludemodels == 0) { @@ -601,7 +601,7 @@ void studiohdr_t::SetActivityListVersion( int version ) const Assert( pStudioHdr ); - pStudioHdr->SetActivityListVersion( version ); + pStudioHdr->SetActivityListVersion( iVersion ); } } @@ -1152,7 +1152,7 @@ void CStudioHdr::SetAttachmentBone( int iAttachment, int iBone ) // Purpose: //----------------------------------------------------------------------------- -char *CStudioHdr::pszNodeName( int iNode ) +const char *CStudioHdr::pszNodeName( int iNode ) { if (m_pVModel == NULL) { @@ -1433,9 +1433,9 @@ void CStudioHdr::RunFlexRules( const float *src, float *dest ) { int m = pops->d.index; int km = k - m; - for ( int i = km + 1; i < k; ++i ) + for ( int l = km + 1; l < k; ++l ) { - stack[ km ] *= stack[ i ]; + stack[ km ] *= stack[ l ]; } k = k - m + 1; } @@ -1445,9 +1445,9 @@ void CStudioHdr::RunFlexRules( const float *src, float *dest ) int m = pops->d.index; int km = k - m; float dv = stack[ km ]; - for ( int i = km + 1; i < k; ++i ) + for ( int l = km + 1; l < k; ++l ) { - dv *= stack[ i ]; + dv *= stack[ l ]; } stack[ km - 1 ] *= 1.0f - dv; k -= m; @@ -1701,7 +1701,7 @@ void CStudioHdr::CActivityToSequenceMapping::Initialize( CStudioHdr * __restrict // This stack may potentially grow very large; so if you have problems with it, // go to a utlmap or similar structure. unsigned int allocsize = (topActivity + 1) * sizeof(int); -#define ALIGN_VALUE( val, alignment ) ( ( val + alignment - 1 ) & ~( alignment - 1 ) ) // need macro for constant expression +//#define ALIGN_VALUE( val, alignment ) ( ( val + alignment - 1 ) & ~( alignment - 1 ) ) // need macro for constant expression allocsize = ALIGN_VALUE(allocsize,16); int * __restrict seqsPerAct = static_cast(stackalloc(allocsize)); memset(seqsPerAct, 0, allocsize); diff --git a/sp/src/public/studio.h b/sp/src/public/studio.h index bcc38d5d52..384f4732c3 100644 --- a/sp/src/public/studio.h +++ b/sp/src/public/studio.h @@ -2137,7 +2137,7 @@ struct studiohdr_t int GetSequenceActivity( int iSequence ); void SetSequenceActivity( int iSequence, int iActivity ); int GetActivityListVersion( void ); - void SetActivityListVersion( int version ) const; + void SetActivityListVersion( int iVersion ) const; int GetEventListVersion( void ); void SetEventListVersion( int version ); @@ -2185,7 +2185,7 @@ struct studiohdr_t //public: int EntryNode( int iSequence ); int ExitNode( int iSequence ); - char *pszNodeName( int iNode ); + const char *pszNodeName( int iNode ); int GetTransition( int iFrom, int iTo ) const; int numflexdesc; @@ -2395,7 +2395,7 @@ class CStudioHdr int EntryNode( int iSequence ); int ExitNode( int iSequence ); - char *pszNodeName( int iNode ); + const char *pszNodeName( int iNode ); // FIXME: where should this one be? int GetTransition( int iFrom, int iTo ) const; diff --git a/sp/src/public/tier0/basetypes.h b/sp/src/public/tier0/basetypes.h index e8387b56b8..9bcaf90eca 100644 --- a/sp/src/public/tier0/basetypes.h +++ b/sp/src/public/tier0/basetypes.h @@ -131,6 +131,70 @@ T Max( T const &val1, T const &val2 ) #define TRUE (!FALSE) #endif +//----------------------------------------------------------------------------- +// fsel +//----------------------------------------------------------------------------- +#ifndef _X360 + +#define fsel(c,x,y) ( (c) >= 0 ? (x) : (y) ) + +// integer conditional move +// if a >= 0, return x, else y +#define isel(a,x,y) ( ((a) >= 0) ? (x) : (y) ) + +// if x = y, return a, else b +#define ieqsel(x,y,a,b) (( (x) == (y) ) ? (a) : (b)) + +// if the nth bit of a is set (counting with 0 = LSB), +// return x, else y +// this is fast if nbit is a compile-time immediate +#define ibitsel(a, nbit, x, y) ( ( ((a) & (1 << (nbit))) != 0 ) ? (x) : (y) ) + +#else + +// __fsel(double fComparand, double fValGE, double fLT) == fComparand >= 0 ? fValGE : fLT +// this is much faster than if ( aFloat > 0 ) { x = .. } +// the XDK defines two intrinsics, one for floats and one for doubles -- it's the same +// opcode, but the __fself version tells the compiler not to do a wasteful unnecessary +// rounding op after each sel. +// #define fsel __fsel +FORCEINLINE double fsel(double fComparand, double fValGE, double fLT) { return __fsel( fComparand, fValGE, fLT ); } +FORCEINLINE float fsel(float fComparand, float fValGE, float fLT) { return __fself( fComparand, fValGE, fLT ); } + +// if a >= 0, return x, else y +FORCEINLINE int isel( int a, int x, int y ) +{ + int mask = a >> 31; // arithmetic shift right, splat out the sign bit + return x + ((y - x) & mask); +}; + +// if a >= 0, return x, else y +FORCEINLINE unsigned isel( int a, unsigned x, unsigned y ) +{ + int mask = a >> 31; // arithmetic shift right, splat out the sign bit + return x + ((y - x) & mask); +}; + +// ( x == y ) ? a : b +FORCEINLINE unsigned ieqsel( unsigned x, unsigned y, unsigned a, unsigned b ) +{ + unsigned mask = (x == y) ? 0 : -1; + return a + ((b - a) & mask); +}; + +// ( x == y ) ? a : b +FORCEINLINE int ieqsel( int x, int y, int a, int b ) +{ + int mask = (x == y) ? 0 : -1; + return a + ((b - a) & mask); +}; + +// if the nth bit of a is set (counting with 0 = LSB), +// return x, else y +// this is fast if nbit is a compile-time immediate +#define ibitsel(a, nbit, x, y) ( (x) + (((y) - (x)) & (((a) & (1 << (nbit))) ? 0 : -1)) ) + +#endif #ifndef DONT_DEFINE_BOOL // Needed for Cocoa stuff to compile. typedef int BOOL; @@ -148,7 +212,6 @@ typedef unsigned short ucs2; #endif #ifdef MAPBASE -// I'm using ThreeState_t a lot more now and I'm tired of typing this out so much. #define TO_THREESTATE(num) static_cast(num) #endif diff --git a/sp/src/public/tier0/dbg.h b/sp/src/public/tier0/dbg.h index 9fd38d0a9b..fab7165090 100644 --- a/sp/src/public/tier0/dbg.h +++ b/sp/src/public/tier0/dbg.h @@ -15,7 +15,9 @@ #include "basetypes.h" #include "dbgflag.h" #include "platform.h" +#if _MSC_VER < 1900 #include +#endif #include #include @@ -247,10 +249,10 @@ DBG_INTERFACE struct SDL_Window * GetAssertDialogParent(); if (!(_exp)) \ { \ _SpewInfo( SPEW_ASSERT, __TFILE__, __LINE__ ); \ - SpewRetval_t ret = _SpewMessage("%s", static_cast( _msg )); \ + SpewRetval_t _ret = _SpewMessage("%s", static_cast( _msg )); \ CallAssertFailedNotifyFunc( __TFILE__, __LINE__, _msg ); \ _executeExp; \ - if ( ret == SPEW_DEBUGGER) \ + if ( _ret == SPEW_DEBUGGER) \ { \ if ( !ShouldUseNewAssertDialog() || DoNewAssertDialog( __TFILE__, __LINE__, _msg ) ) \ { \ diff --git a/sp/src/public/tier0/memalloc.h b/sp/src/public/tier0/memalloc.h index e0f9c16ab9..34e1ccb91e 100644 --- a/sp/src/public/tier0/memalloc.h +++ b/sp/src/public/tier0/memalloc.h @@ -382,7 +382,12 @@ class CMemAllocAttributeAlloction #pragma warning(disable:4290) #pragma warning(push) + +#if _MSC_VER < 1900 #include +#else + #include +#endif // MEM_DEBUG_CLASSNAME is opt-in. // Note: typeid().name() is not threadsafe, so if the project needs to access it in multiple threads diff --git a/sp/src/public/tier0/memdbgon.h b/sp/src/public/tier0/memdbgon.h index 7fd6e9ca18..4e46839fc2 100644 --- a/sp/src/public/tier0/memdbgon.h +++ b/sp/src/public/tier0/memdbgon.h @@ -37,7 +37,7 @@ #include "commonmacros.h" #include "memalloc.h" -#if defined(USE_MEM_DEBUG) +#if USE_MEM_DEBUG #if defined( POSIX ) #define _NORMAL_BLOCK 1 @@ -91,7 +91,7 @@ inline void *MemAlloc_InlineCallocMemset( void *pMem, size_t nCount, size_t nEle } #endif -#define calloc(c, s) MemAlloc_InlineCallocMemset(malloc(c*s), c, s) +#define calloc(c, s) MemAlloc_InlineCallocMemset(malloc((c)*(s)), (c), (s)) #define free(p) g_pMemAlloc->Free( p ) #define _msize(p) g_pMemAlloc->GetSize( p ) #define _expand(p, s) _expand_NoLongerSupported(p, s) @@ -99,7 +99,7 @@ inline void *MemAlloc_InlineCallocMemset( void *pMem, size_t nCount, size_t nEle // -------------------------------------------------------- // Debug path -#if defined(USE_MEM_DEBUG) +#if USE_MEM_DEBUG #define malloc(s) g_pMemAlloc->Alloc( s, __FILE__, __LINE__) #define realloc(p, s) g_pMemAlloc->Realloc( p, s, __FILE__, __LINE__ ) @@ -231,7 +231,7 @@ inline wchar_t *MemAlloc_WcStrDup(const wchar_t *pString) #else -#if defined(USE_MEM_DEBUG) +#if USE_MEM_DEBUG #ifndef _STATIC_LINKED #pragma message ("Note: file includes crtdbg.h directly, therefore will cannot use memdbgon.h in non-debug build") #else diff --git a/sp/src/public/tier0/memoverride.cpp b/sp/src/public/tier0/memoverride.cpp index 5c679e4571..5ff4dbb425 100644 --- a/sp/src/public/tier0/memoverride.cpp +++ b/sp/src/public/tier0/memoverride.cpp @@ -40,9 +40,17 @@ #define __cdecl #endif +#undef _malloc_dbg +#undef _calloc_dbg +#undef _free_dbg +#undef _CrtSetCheckCount +#undef _CrtGetCheckCount +#undef _CrtSetDebugFillThreshold + #if defined( _WIN32 ) && !defined( _X360 ) const char *MakeModuleFileName() { +#if _MSC_VER < 1900 if ( g_pMemAlloc->IsDebugHeap() ) { char *pszModuleName = (char *)HeapAlloc( GetProcessHeap(), 0, MAX_PATH ); // small leak, debug only @@ -65,27 +73,36 @@ const char *MakeModuleFileName() return pszModuleName; } +#endif return NULL; } static void *AllocUnattributed( size_t nSize ) { +#if _MSC_VER < 1900 static const char *pszOwner = MakeModuleFileName(); if ( !pszOwner ) return g_pMemAlloc->Alloc(nSize); else return g_pMemAlloc->Alloc(nSize, pszOwner, 0); +#else + return g_pMemAlloc->Alloc(nSize); +#endif } static void *ReallocUnattributed( void *pMem, size_t nSize ) { +#if _MSC_VER < 1900 static const char *pszOwner = MakeModuleFileName(); if ( !pszOwner ) return g_pMemAlloc->Realloc(pMem, nSize); else return g_pMemAlloc->Realloc(pMem, nSize, pszOwner, 0); +#else + return g_pMemAlloc->Realloc(pMem, nSize); +#endif } #else @@ -108,6 +125,9 @@ inline void *ReallocUnattributed( void *pMem, size_t nSize ) // this magic only works under win32 // under linux this malloc() overrides the libc malloc() and so we // end up in a recursion (as g_pMemAlloc->Alloc() calls malloc) +#if _MSC_VER >= 1900 && !defined(_CRTNOALIAS) +#define _CRTNOALIAS +#endif #if _MSC_VER >= 1400 #define ALLOC_CALL _CRTNOALIAS _CRTRESTRICT #define FREE_CALL _CRTNOALIAS @@ -155,6 +175,11 @@ void* __cdecl _malloc_base( size_t nSize ) { return AllocUnattributed( nSize ); } +#elif _MSC_VER >= 1900 + __declspec(restrict) void* _malloc_base(size_t nSize) + { + return AllocUnattributed(nSize); + } #else void *_malloc_base( size_t nSize ) { @@ -162,24 +187,47 @@ void *_malloc_base( size_t nSize ) } #endif +#if _MSC_VER >= 1900 +__declspec(restrict) void* _calloc_base(size_t count, size_t nSize) +{ + void* pMem = AllocUnattributed(count * nSize); + memset(pMem, 0, count * nSize); + return pMem; +} +#else void *_calloc_base( size_t nSize ) { void *pMem = AllocUnattributed( nSize ); memset(pMem, 0, nSize); return pMem; } +#endif +#if _MSC_VER >= 1900 +__declspec(restrict) void* _realloc_base(void* pMem, size_t nSize) +{ + return ReallocUnattributed(pMem, nSize); +} +#else void *_realloc_base( void *pMem, size_t nSize ) { return ReallocUnattributed( pMem, nSize ); } +#endif +#if _MSC_VER >= 1900 +__declspec(restrict) void* _recalloc_base(void* pMem, size_t count, size_t nSize) +{ + return _recalloc(pMem, count, nSize); +} +#else void *_recalloc_base( void *pMem, size_t nSize ) { void *pMemOut = ReallocUnattributed( pMem, nSize ); memset(pMemOut, 0, nSize); return pMemOut; } +#endif void _free_base( void *pMem ) { @@ -200,7 +248,11 @@ void * __cdecl _malloc_crt(size_t size) void * __cdecl _calloc_crt(size_t count, size_t size) { +#if _MSC_VER >= 1900 + return _calloc_base(count, size); +#else return _calloc_base( count * size ); +#endif } void * __cdecl _realloc_crt(void *ptr, size_t size) @@ -210,17 +262,29 @@ void * __cdecl _realloc_crt(void *ptr, size_t size) void * __cdecl _recalloc_crt(void *ptr, size_t count, size_t size) { +#if _MSC_VER >= 1900 + return _recalloc_base(ptr, count, size); +#else return _recalloc_base( ptr, size * count ); +#endif } ALLOC_CALL void * __cdecl _recalloc ( void * memblock, size_t count, size_t size ) { - void *pMem = ReallocUnattributed( memblock, size * count ); - memset( pMem, 0, size * count ); - return pMem; + const size_t oldSize = _msize(memblock); + const size_t newSize = count * size; + void* pMemOut = ReallocUnattributed(memblock, newSize); + + if (newSize > oldSize) + memset(((char*)pMemOut) + oldSize, 0, newSize - oldSize); + + return pMemOut; } size_t _msize_base( void *pMem ) +#if _MSC_VER >= 1925 //VS2019+ + throw() +#endif { return g_pMemAlloc->GetSize(pMem); } @@ -485,6 +549,7 @@ void *__cdecl _calloc_dbg_impl( size_t nNum, size_t nSize, int nBlockUse, return _calloc_dbg( nNum, nSize, nBlockUse, szFileName, nLine ); } +#ifdef DEBUG void *__cdecl _realloc_dbg( void *pMem, size_t nNewSize, int nBlockUse, const char *pFileName, int nLine ) { @@ -498,6 +563,7 @@ void *__cdecl _expand_dbg( void *pMem, size_t nNewSize, int nBlockUse, Assert( 0 ); return NULL; } +#endif void __cdecl _free_dbg( void *pMem, int nBlockUse ) { @@ -505,6 +571,7 @@ void __cdecl _free_dbg( void *pMem, int nBlockUse ) g_pMemAlloc->Free(pMem); } +#ifdef DEBUG size_t __cdecl _msize_dbg( void *pMem, int nBlockUse ) { #ifdef _WIN32 @@ -514,6 +581,7 @@ size_t __cdecl _msize_dbg( void *pMem, int nBlockUse ) return 0; #endif } +#endif #ifdef _WIN32 @@ -614,6 +682,7 @@ ALLOC_CALL void * __cdecl _aligned_offset_recalloc( void * memblock, size_t coun extern "C" { +#ifdef DEBUG int _CrtDumpMemoryLeaks(void) { return 0; @@ -628,11 +697,25 @@ int _CrtSetDbgFlag( int nNewFlag ) { return g_pMemAlloc->CrtSetDbgFlag( nNewFlag ); } +#endif // 64-bit port. #define AFNAME(var) __p_ ## var #define AFRET(var) &var +#if _MSC_VER >= 1900 +int* __cdecl __p__crtDbgFlag(void) +{ + static int dummy = _CRTDBG_ALLOC_MEM_DF | _CRTDBG_CHECK_ALWAYS_DF; + return &dummy; +} + +long* __cdecl __p__crtBreakAlloc(void) +{ + static long dummy = 0; + return &dummy; +} +#else int _crtDbgFlag = _CRTDBG_ALLOC_MEM_DF; int* AFNAME(_crtDbgFlag)(void) { @@ -644,12 +727,14 @@ long* AFNAME(_crtBreakAlloc) (void) { return AFRET(_crtBreakAlloc); } +#endif void __cdecl _CrtSetDbgBlockType( void *pMem, int nBlockUse ) { DebuggerBreak(); } +#ifdef DEBUG _CRT_ALLOC_HOOK __cdecl _CrtSetAllocHook( _CRT_ALLOC_HOOK pfnNewHook ) { DebuggerBreak(); @@ -710,13 +795,14 @@ void __cdecl _CrtDoForAllClientObjects( void (*pfn)(void *, void *), void * pCon { DebuggerBreak(); } - +#endif //----------------------------------------------------------------------------- // Methods in dbgrpt.cpp //----------------------------------------------------------------------------- long _crtAssertBusy = -1; +#ifdef DEBUG int __cdecl _CrtSetReportMode( int nReportType, int nReportMode ) { return g_pMemAlloc->CrtSetReportMode( nReportType, nReportMode ); @@ -731,6 +817,7 @@ _CRT_REPORT_HOOK __cdecl _CrtSetReportHook( _CRT_REPORT_HOOK pfnNewHook ) { return (_CRT_REPORT_HOOK)g_pMemAlloc->CrtSetReportHook( pfnNewHook ); } +#endif int __cdecl _CrtDbgReport( int nRptType, const char * szFile, int nLine, const char * szModule, const char * szFormat, ... ) @@ -863,7 +950,7 @@ ErrorHandlerRegistrar::ErrorHandlerRegistrar() _set_invalid_parameter_handler( VInvalidParameterHandler ); } -#if defined( _DEBUG ) +#if 0 // defined( _DEBUG ) // wrapper which passes no debug info; not available in debug #ifndef SUPPRESS_INVALID_PARAMETER_NO_INFO @@ -887,21 +974,41 @@ int __cdecl __crtMessageWindowW( int nRptType, const wchar_t * szFile, const wch int __cdecl _CrtDbgReportV( int nRptType, const wchar_t *szFile, int nLine, const wchar_t *szModule, const wchar_t *szFormat, va_list arglist ) { - Assert(0); + wchar_t buffer[256]; + vswprintf(buffer, 256, szFormat, arglist); + DevWarning("%ls", buffer); return 0; } int __cdecl _CrtDbgReportW( int nRptType, const wchar_t *szFile, int nLine, const wchar_t *szModule, const wchar_t *szFormat, ...) { - Assert(0); + wchar_t buffer[256]; + va_list args; + va_start(args, szFormat); + vswprintf(buffer, 256, szFormat, args); + va_end(args); + DevWarning("%ls", buffer); return 0; } +#if _MSC_VER >= 1900 +int __cdecl _VCrtDbgReportA(int nRptType, void* returnAddress, const char* szFile, int nLine, + const char* szModule, const char* szFormat, va_list arglist) +#else int __cdecl _VCrtDbgReportA( int nRptType, const wchar_t * szFile, int nLine, const wchar_t * szModule, const wchar_t * szFormat, va_list arglist ) +#endif { - Assert(0); +#if _MSC_VER >= 1900 + char buffer[256]; + vsnprintf(buffer, 256, szFormat, arglist); + DevWarning("%s", buffer); +#else + wchar_t buffer[256]; + vswprintf(buffer, 256, szFormat, arglist); + DevWarning("%ls", buffer); +#endif // _MSC_VER >= 1900 return 0; } @@ -927,13 +1034,12 @@ extern "C" int __cdecl _CrtGetCheckCount( void ) return __crtDebugCheckCount; } +#ifdef DEBUG // aligned offset debug extern "C" void * __cdecl _aligned_offset_recalloc_dbg( void * memblock, size_t count, size_t size, size_t align, size_t offset, const char * f_name, int line_n ) { Assert( IsPC() || 0 ); - void *pMem = ReallocUnattributed( memblock, size * count ); - memset( pMem, 0, size * count ); - return pMem; + return ReallocUnattributed(memblock, size * count); } extern "C" void * __cdecl _aligned_recalloc_dbg( void *memblock, size_t count, size_t size, size_t align, const char * f_name, int line_n ) @@ -950,12 +1056,16 @@ _CRT_REPORT_HOOK __cdecl _CrtGetReportHook( void ) { return NULL; } +#endif // DEBUG #endif + +#ifdef DEBUG int __cdecl _CrtReportBlockType(const void * pUserData) { return 0; } +#endif } // end extern "C" @@ -995,14 +1105,14 @@ void * __cdecl _heap_alloc_dbg( size_t nSize, int nBlockUse, const char * szFile static void * __cdecl realloc_help( void * pUserData, size_t * pnNewSize, int nBlockUse,const char * szFileName, int nLine, int fRealloc ) { - assert(0); // Shouldn't be needed + Assert(0); // Shouldn't be needed return NULL; } #else static void * __cdecl realloc_help( void * pUserData, size_t nNewSize, int nBlockUse, const char * szFileName, int nLine, int fRealloc) { - assert(0); // Shouldn't be needed + Assert(0); // Shouldn't be needed return NULL; } #endif @@ -1018,11 +1128,13 @@ void __cdecl _free_dbg_nolock( void * pUserData, int nBlockUse) _free_dbg(pUserData, 0); } +#ifdef DEBUG _CRT_ALLOC_HOOK __cdecl _CrtGetAllocHook ( void) { - assert(0); + Assert(0); return NULL; } +#endif static int __cdecl CheckBytes( unsigned char * pb, unsigned char bCheck, size_t nSize) { @@ -1030,12 +1142,13 @@ static int __cdecl CheckBytes( unsigned char * pb, unsigned char bCheck, size_t return bOkay; } - +#ifdef DEBUG _CRT_DUMP_CLIENT __cdecl _CrtGetDumpClient ( void) { - assert(0); + Assert(0); return NULL; } +#endif #if _MSC_VER >= 1400 static void __cdecl _printMemBlockData( _locale_t plocinfo, _CrtMemBlockHeader * pHead) @@ -1046,6 +1159,8 @@ static void __cdecl _CrtMemDumpAllObjectsSince_stat( const _CrtMemState * state, { } #endif + +#if defined(DEBUG) && _MSC_VER >= 1900 void * __cdecl _aligned_malloc_dbg( size_t size, size_t align, const char * f_name, int line_n) { return _aligned_malloc(size, align); @@ -1073,16 +1188,19 @@ void __cdecl _aligned_free_dbg( void * memblock) { _aligned_free(memblock); } +#endif // DEBUG +#if _MSC_VER < 1900 size_t __cdecl _CrtSetDebugFillThreshold( size_t _NewDebugFillThreshold) { assert(0); return 0; } +#endif //=========================================== // NEW!!! 64-bit - +#ifndef PROTECTED_THINGS_DISABLE char * __cdecl _strdup ( const char * string ) { int nSize = (int)strlen(string) + 1; @@ -1094,6 +1212,7 @@ char * __cdecl _strdup ( const char * string ) memcpy( pCopy, string, nSize ); return pCopy; } +#endif #if 0 _TSCHAR * __cdecl _tfullpath_dbg ( _TSCHAR *UserBuf, const _TSCHAR *path, size_t maxlen, int nBlockUse, const char * szFileName, int nLine ) @@ -1143,7 +1262,6 @@ _TSCHAR * __cdecl _ttempnam ( const _TSCHAR *dir, const _TSCHAR *pfx ) Assert(0); return 0; } -#endif wchar_t * __cdecl _wcsdup_dbg ( const wchar_t * string, int nBlockUse, const char * szFileName, int nLine ) { @@ -1156,6 +1274,7 @@ wchar_t * __cdecl _wcsdup ( const wchar_t * string ) Assert(0); return 0; } +#endif } // end extern "C" @@ -1344,6 +1463,12 @@ _CRTIMP extern uintptr_t __cdecl __threadhandle(void); /* Structure for each thread's data */ +#if _MSC_VER >= 1900 +typedef __crt_multibyte_data* pthreadmbcinfo; +typedef __crt_locale_data* pthreadlocinfo; +typedef __crt_locale_pointers _locale_tstruct; +#endif + struct _tiddata { unsigned long _tid; /* thread ID */ @@ -1387,7 +1512,7 @@ struct _tiddata { * the thread */ pthreadmbcinfo ptmbcinfo; - /* pointer to the copy of the locale informaton used by the thead */ + /* pointer to the copy of the locale information used by the thread */ pthreadlocinfo ptlocinfo; int _ownlocale; /* if 1, this thread owns its own locale */ @@ -1502,7 +1627,7 @@ struct _tiddata { * the thread */ pthreadmbcinfo ptmbcinfo; - /* pointer to the copy of the locale informaton used by the thead */ + /* pointer to the copy of the locale information used by the thread */ pthreadlocinfo ptlocinfo; int _ownlocale; /* if 1, this thread owns its own locale */ diff --git a/sp/src/public/tier0/platform.h b/sp/src/public/tier0/platform.h index 0e5a34284e..7ed6154590 100644 --- a/sp/src/public/tier0/platform.h +++ b/sp/src/public/tier0/platform.h @@ -367,7 +367,7 @@ typedef void * HINSTANCE; #define MAX_UNICODE_PATH MAX_PATH #endif -#define MAX_UNICODE_PATH_IN_UTF8 MAX_UNICODE_PATH*4 +#define MAX_UNICODE_PATH_IN_UTF8 (MAX_UNICODE_PATH*4) #ifdef GNUC #undef offsetof @@ -379,7 +379,7 @@ typedef void * HINSTANCE; #endif -#define ALIGN_VALUE( val, alignment ) ( ( val + alignment - 1 ) & ~( alignment - 1 ) ) // need macro for constant expression +#define ALIGN_VALUE( val, alignment ) ( ( (val) + (alignment) - 1 ) & ~( (alignment) - 1 ) ) // need macro for constant expression // Used to step into the debugger #if defined( _WIN32 ) && !defined( _X360 ) @@ -572,7 +572,16 @@ typedef void * HINSTANCE; #endif // GCC 3.4.1 has a bug in supporting forced inline of templated functions // this macro lets us not force inlining in that case - #define FORCEINLINE_TEMPLATE inline +#if __GNUC__ < 4 +#define FORCEINLINE_TEMPLATE inline +#else +#define FORCEINLINE_TEMPLATE inline __attribute__((always_inline)) +#endif +#if __cpp_constexpr >= 201304 +#define CONSTEXPR_FUNC constexpr +#else +#define CONSTEXPR_FUNC +#endif // #define __stdcall __attribute__ ((__stdcall__)) #endif @@ -672,6 +681,40 @@ typedef void * HINSTANCE; #pragma warning( disable : 4312 ) // conversion from 'unsigned int' to 'memhandle_t' of greater size #endif +// Detect C++11 support for "rvalue references" / "move semantics" / other C++11 (and up) stuff +#if defined(_MSC_VER) +#if _MSC_VER >= 1600 +#define VALVE_RVALUE_REFS 1 +#endif +#if _MSC_VER >= 1800 +#define VALVE_INITIALIZER_LIST_SUPPORT 1 +#define VALVE_EXPLICIT_CONVERSION_OP 1 +#endif +#elif defined(__clang__) +#if __has_extension(cxx_rvalue_references) +#define VALVE_RVALUE_REFS 1 +#endif +#if __has_feature(cxx_generalized_initializers) +#define VALVE_INITIALIZER_LIST_SUPPORT 1 +#endif +#if __has_feature(cxx_explicit_conversions) +#define VALVE_EXPLICIT_CONVERSION_OP 1 +#endif +#elif defined(__GNUC__) +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6 ) +#if defined(__GXX_EXPERIMENTAL_CXX0X__) +#define VALVE_RVALUE_REFS 1 +#define VALVE_INITIALIZER_LIST_SUPPORT 1 +#define VALVE_EXPLICIT_CONVERSION_OP 1 +#endif +#endif +#endif + +#ifdef VALVE_RVALUE_REFS +#include "tier0/valve_minmax_off.h" +#include +#include "tier0/valve_minmax_on.h" +#endif #ifdef POSIX #define _stricmp stricmp @@ -704,29 +747,6 @@ typedef uint32 HMODULE; typedef void *HANDLE; #endif -//----------------------------------------------------------------------------- -// fsel -//----------------------------------------------------------------------------- -#ifndef _X360 - -static FORCEINLINE float fsel(float fComparand, float fValGE, float fLT) -{ - return fComparand >= 0 ? fValGE : fLT; -} -static FORCEINLINE double fsel(double fComparand, double fValGE, double fLT) -{ - return fComparand >= 0 ? fValGE : fLT; -} - -#else - -// __fsel(double fComparand, double fValGE, double fLT) == fComparand >= 0 ? fValGE : fLT -// this is much faster than if ( aFloat > 0 ) { x = .. } -#define fsel __fsel - -#endif - - //----------------------------------------------------------------------------- // FP exception handling //----------------------------------------------------------------------------- @@ -1257,6 +1277,15 @@ inline bool Plat_IsInDebugSession( bool bForceRecheck = false ) { return false; PLATFORM_INTERFACE bool Is64BitOS(); +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// General Mapbase version constants compiled into projects for versioning purposes +//----------------------------------------------------------------------------- +#define MAPBASE_VERSION "7.0" +#define MAPBASE_VER_INT 7000 // For use in #if in a similar fashion to macros like _MSC_VER +#endif + + //----------------------------------------------------------------------------- // XBOX Components valid in PC compilation space //----------------------------------------------------------------------------- @@ -1328,62 +1357,81 @@ inline const char *GetPlatformExt( void ) template inline T* Construct( T* pMemory ) { + HINT(pMemory != 0); return ::new( pMemory ) T; } template inline T* Construct( T* pMemory, ARG1 a1 ) { + HINT(pMemory != 0); return ::new( pMemory ) T( a1 ); } template inline T* Construct( T* pMemory, ARG1 a1, ARG2 a2 ) { + HINT(pMemory != 0); return ::new( pMemory ) T( a1, a2 ); } template inline T* Construct( T* pMemory, ARG1 a1, ARG2 a2, ARG3 a3 ) { + HINT(pMemory != 0); return ::new( pMemory ) T( a1, a2, a3 ); } template inline T* Construct( T* pMemory, ARG1 a1, ARG2 a2, ARG3 a3, ARG4 a4 ) { + HINT(pMemory != 0); return ::new( pMemory ) T( a1, a2, a3, a4 ); } template inline T* Construct( T* pMemory, ARG1 a1, ARG2 a2, ARG3 a3, ARG4 a4, ARG5 a5 ) { + HINT(pMemory != 0); return ::new( pMemory ) T( a1, a2, a3, a4, a5 ); } template inline void ConstructOneArg( T* pMemory, P const& arg) { + HINT(pMemory != 0); ::new( pMemory ) T(arg); } template inline void ConstructTwoArg( T* pMemory, P1 const& arg1, P2 const& arg2) { + HINT(pMemory != 0); ::new( pMemory ) T(arg1, arg2); } template inline void ConstructThreeArg( T* pMemory, P1 const& arg1, P2 const& arg2, P3 const& arg3) { + HINT(pMemory != 0); ::new( pMemory ) T(arg1, arg2, arg3); } template inline T* CopyConstruct( T* pMemory, T const& src ) { + HINT(pMemory != 0); return ::new( pMemory ) T(src); } + +#ifdef VALVE_RVALUE_REFS +template +inline void CopyConstruct(T* pMemory, T&& src) +{ + HINT(pMemory != 0); + ::new(pMemory)T(std::forward(src)); +} +#endif template inline void Destruct( T* pMemory ) diff --git a/sp/src/public/tier1/UtlSortVector.h b/sp/src/public/tier1/UtlSortVector.h index b5bfef5338..53989db497 100644 --- a/sp/src/public/tier1/UtlSortVector.h +++ b/sp/src/public/tier1/UtlSortVector.h @@ -245,7 +245,7 @@ void CUtlSortVector::QuickSort( LessFunc& less, int nLo ctx.m_pLessContext = m_pLessContext; ctx.m_pLessFunc = &less; - qsort_s( Base(), Count(), sizeof(T), (QSortCompareFunc_t)&CUtlSortVector::CompareHelper, &ctx ); + qsort_s( this->Base(), this->Count(), sizeof(T), (QSortCompareFunc_t)&CUtlSortVector::CompareHelper, &ctx ); } #else typedef int (__cdecl *QSortCompareFunc_t)( const void *, const void *); diff --git a/sp/src/public/tier1/byteswap.h b/sp/src/public/tier1/byteswap.h index 9b082541be..9c866639cb 100644 --- a/sp/src/public/tier1/byteswap.h +++ b/sp/src/public/tier1/byteswap.h @@ -186,7 +186,7 @@ class CByteswap if( !m_bSwapBytes || ( sizeof(T) == 1 ) ) { // If we were just going to swap in place then return. - if( !inputBuffer ) + if( inputBuffer == outputBuffer ) return; // Otherwise copy the inputBuffer to the outputBuffer: diff --git a/sp/src/public/tier1/convar.h b/sp/src/public/tier1/convar.h index 314ee011c2..37268cc474 100644 --- a/sp/src/public/tier1/convar.h +++ b/sp/src/public/tier1/convar.h @@ -21,7 +21,7 @@ #include "tier1/utlvector.h" #include "tier1/utlstring.h" #include "icvar.h" -#include "color.h" +#include "Color.h" #ifdef _WIN32 #define FORCEINLINE_CVAR FORCEINLINE diff --git a/sp/src/game/shared/interval.h b/sp/src/public/tier1/interval.h similarity index 100% rename from sp/src/game/shared/interval.h rename to sp/src/public/tier1/interval.h diff --git a/sp/src/public/tier1/mapbase_con_groups.h b/sp/src/public/tier1/mapbase_con_groups.h index cdd955c970..7346092e8d 100644 --- a/sp/src/public/tier1/mapbase_con_groups.h +++ b/sp/src/public/tier1/mapbase_con_groups.h @@ -16,25 +16,48 @@ //static const Color CON_COLOR_DEV_VERBOSE( 192, 128, 192, 255 ); -// General -#define CON_GROUP_MAPBASE_MISC 0 // "Mapbase Misc." -#define CON_GROUP_PHYSICS 1 // "Physics" +enum ConGroupID_t +{ + // General + CON_GROUP_MAPBASE_MISC, // "Mapbase misc." + CON_GROUP_PHYSICS, // "Physics" + CON_GROUP_IO_SYSTEM, // "Entity I/O" + CON_GROUP_RESPONSE_SYSTEM, // "Response System" -// Server -#define CON_GROUP_IO_SYSTEM 2 // "Entity I/O" -#define CON_GROUP_NPC_AI 3 // "NPC AI" -#define CON_GROUP_NPC_SCRIPTS 4 // "NPC Scripts" -#define CON_GROUP_CHOREO 5 // "Choreo" + // Game + CON_GROUP_NPC_AI, // "NPC AI" + CON_GROUP_NPC_SCRIPTS, // "NPC scripts" + CON_GROUP_SPEECH_AI, // "Speech AI" + CON_GROUP_CHOREO, // "Choreo" -// VScript -#define CON_GROUP_VSCRIPT 6 // "VScript" -#define CON_GROUP_VSCRIPT_PRINT 7 // "VScript Print" + // VScript + CON_GROUP_VSCRIPT, // "VScript" + CON_GROUP_VSCRIPT_PRINT, // "VScript print" -#define CON_GROUP_MAX 8 // must always be at the end + //-------------------------- + + // + // Mod groups can be inserted here + // + + //-------------------------- + + CON_GROUP_MAX, // Keep this at the end +}; // Mapbase console group message. -void CGMsg( int level, int nGroup, PRINTF_FORMAT_STRING const tchar* pMsg, ... ) FMTFUNCTION( 3, 4 ); +void CGMsg( int level, ConGroupID_t nGroup, PRINTF_FORMAT_STRING const tchar* pMsg, ... ) FMTFUNCTION( 3, 4 ); #define CGWarning CGMsg +//----------------------------------------------------------------------------- + +class IBaseFileSystem; + +void InitConsoleGroups( IBaseFileSystem *filesystem ); + +void PrintAllConsoleGroups(); +void ToggleConsoleGroups( const char *pszQuery ); +void SetConsoleGroupIncludeNames( bool bToggle ); + #endif diff --git a/sp/src/public/tier1/mapbase_matchers_base.h b/sp/src/public/tier1/mapbase_matchers_base.h new file mode 100644 index 0000000000..74b2cbc919 --- /dev/null +++ b/sp/src/public/tier1/mapbase_matchers_base.h @@ -0,0 +1,58 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ================= +// +// Purpose: General matching functions for things like wildcards and !=. +// +// $NoKeywords: $ +//============================================================================= + +#ifndef MAPBASE_MATCHERS_BASE_H +#define MAPBASE_MATCHERS_BASE_H +#ifdef _WIN32 +#pragma once +#endif + +#include + +#define MAPBASE_MATCHERS 1 + +// Regular expressions based off of the std library. +// pszQuery = The regex text. +// szValue = The value that should be matched. +bool Matcher_Regex( const char *pszQuery, const char *szValue ); + +// Compares two strings with support for wildcards or regex. This code is an expanded version of baseentity.cpp's NamesMatch(). +// pszQuery = The value that should have the wildcard. +// szValue = The value tested against the query. +// Use Matcher_Match if you want <, !=, etc. as well. +bool Matcher_NamesMatch( const char *pszQuery, const char *szValue ); + +// Identical to baseentity.cpp's original NamesMatch(). +// pszQuery = The value that should have the wildcard. +// szValue = The value tested against the query. +bool Matcher_NamesMatch_Classic( const char *pszQuery, const char *szValue ); + +// Identical to Matcher_NamesMatch_Classic(), but either value could use a wildcard. +// pszQuery = The value that serves as the query. This value can use wildcards. +// szValue = The value tested against the query. This value can use wildcards as well. +bool Matcher_NamesMatch_MutualWildcard( const char *pszQuery, const char *szValue ); + +// Taken from the Response System. +// Checks if the specified string appears to be a number of some sort. +static bool AppearsToBeANumber( char const *token ) +{ + if ( atof( token ) != 0.0f ) + return true; + + char const *p = token; + while ( *p ) + { + if ( *p != '0' ) + return false; + + p++; + } + + return true; +} + +#endif diff --git a/sp/src/public/tier1/strtools.h b/sp/src/public/tier1/strtools.h index d3f1c65ba4..0872539dc3 100644 --- a/sp/src/public/tier1/strtools.h +++ b/sp/src/public/tier1/strtools.h @@ -470,6 +470,16 @@ inline void V_wcscat( INOUT_Z_CAP(cchDest) wchar_t *dest, const wchar_t *src, in V_wcsncat( dest, src, cchDest, COPY_ALL_CHARACTERS ); } +// Reentrant strtok +inline static char* V_strtok_s( char *str, const char *delimiters, char **context ) +{ +#ifdef _MSC_VER + return strtok_s( str, delimiters, context ); +#elif POSIX + return strtok_r( str, delimiters, context ); +#endif +} + //----------------------------------------------------------------------------- // generic unique name helper functions //----------------------------------------------------------------------------- diff --git a/sp/src/public/vgui/MouseCode.h b/sp/src/public/vgui/MouseCode.h index 7ba162147a..9b13fc8142 100644 --- a/sp/src/public/vgui/MouseCode.h +++ b/sp/src/public/vgui/MouseCode.h @@ -18,6 +18,15 @@ namespace vgui { typedef ButtonCode_t MouseCode; + +static inline int MouseButtonBit(MouseCode code) +{ + if (code < MOUSE_FIRST || code > MOUSE_LAST) { + Assert(false); + return 0; + } + return 1 << (code - MOUSE_FIRST); +} } #endif // MOUSECODE_H diff --git a/sp/src/public/vgui_controls/Panel.h b/sp/src/public/vgui_controls/Panel.h index 07b63d8de9..99f8e22390 100644 --- a/sp/src/public/vgui_controls/Panel.h +++ b/sp/src/public/vgui_controls/Panel.h @@ -139,7 +139,7 @@ class IForceVirtualInheritancePanel // This is designed as an easy-access to the vgui-functionality; for more // low-level access to vgui functions use the IPanel/IClientPanel interfaces directly //----------------------------------------------------------------------------- -class Panel : public IClientPanel, virtual IForceVirtualInheritancePanel +class Panel : public IClientPanel, public virtual IForceVirtualInheritancePanel { DECLARE_CLASS_SIMPLE_NOBASE( Panel ); @@ -1011,7 +1011,7 @@ class CSortedPanelYLess void VguiPanelGetSortedChildPanelList( Panel *pParentPanel, void *pSortedPanels ); -void VguiPanelGetSortedChildButtonList( Panel *pParentPanel, void *pSortedPanels, char *pchFilter = NULL, int nFilterType = 0 ); +void VguiPanelGetSortedChildButtonList( Panel *pParentPanel, void *pSortedPanels, const char *pchFilter = NULL, int nFilterType = 0 ); int VguiPanelNavigateSortedChildButtonList( void *pSortedPanels, int nDir ); diff --git a/sp/src/public/vphysics_interface.h b/sp/src/public/vphysics_interface.h index d6e619353f..1852ab8ccc 100644 --- a/sp/src/public/vphysics_interface.h +++ b/sp/src/public/vphysics_interface.h @@ -961,30 +961,6 @@ struct surfacedata_t surfacegameprops_t game; // Game data / properties surfacesoundhandles_t soundhandles; - -#ifdef MAPBASE_VSCRIPT - // These functions are for the VScript class description. - - float GetFriction() { return physics.friction; } - float GetThickness() { return physics.thickness; } - - float GetJumpFactor() { return game.jumpFactor; } - char GetMaterialChar() { return game.material; } - -#if defined(CLIENT_DLL) || defined(GAME_DLL) - const char* GetSoundStepLeft(); - const char* GetSoundStepRight(); - const char* GetSoundImpactSoft(); - const char* GetSoundImpactHard(); - const char* GetSoundScrapeSmooth(); - const char* GetSoundScrapeRough(); - const char* GetSoundBulletImpact(); - const char* GetSoundRolling(); - const char* GetSoundBreak(); - const char* GetSoundStrain(); -#endif - -#endif }; #define VPHYSICS_SURFACEPROPS_INTERFACE_VERSION "VPhysicsSurfaceProps001" diff --git a/sp/src/public/vscript/ivscript.h b/sp/src/public/vscript/ivscript.h index 4b3388df76..001e1c24dd 100644 --- a/sp/src/public/vscript/ivscript.h +++ b/sp/src/public/vscript/ivscript.h @@ -95,6 +95,9 @@ #ifndef IVSCRIPT_H #define IVSCRIPT_H +#include +#include + #include "platform.h" #include "datamap.h" #include "appframework/IAppSystem.h" @@ -163,20 +166,6 @@ class IScriptManager : public IAppSystem // //----------------------------------------------------------------------------- -#ifdef MAPBASE_VSCRIPT -template T *HScriptToClass( HSCRIPT hObj ) -{ - return (hObj) ? (T*)g_pScriptVM->GetInstanceValue( hObj, GetScriptDesc( (T*)NULL ) ) : NULL; -} -#else -DECLARE_POINTER_HANDLE( HSCRIPT ); -#define INVALID_HSCRIPT ((HSCRIPT)-1) -#endif - -//----------------------------------------------------------------------------- -// -//----------------------------------------------------------------------------- - enum ExtendedFieldType { FIELD_TYPEUNKNOWN = FIELD_TYPECOUNT, @@ -645,8 +634,21 @@ struct ScriptEnumDesc_t // //----------------------------------------------------------------------------- +// forwards T (and T&) if T is neither enum or an unsigned integer +// the overload for int below captures enums and unsigned integers and "bends" them to int +template +static inline typename std::enable_if::type>::value && !std::is_unsigned::type>::value, T&&>::type ToConstantVariant(T &&value) +{ + return std::forward(value); +} + +static inline int ToConstantVariant(int value) +{ + return value; +} + #define ScriptRegisterConstant( pVM, constant, description ) ScriptRegisterConstantNamed( pVM, constant, #constant, description ) -#define ScriptRegisterConstantNamed( pVM, constant, scriptName, description ) do { static ScriptConstantBinding_t binding; binding.m_pszScriptName = scriptName; binding.m_pszDescription = description; binding.m_data = constant; pVM->RegisterConstant( &binding ); } while (0) +#define ScriptRegisterConstantNamed( pVM, constant, scriptName, description ) do { static ScriptConstantBinding_t binding; binding.m_pszScriptName = scriptName; binding.m_pszDescription = description; binding.m_data = ToConstantVariant(constant); pVM->RegisterConstant( &binding ); } while (0) // Could probably use a better name. // This is used for registering variants (particularly vectors) not tied to existing variables. @@ -753,6 +755,23 @@ struct ScriptEnumDesc_t pDesc->m_Hooks.AddToTail(pHook); \ } +// Static hooks (or "global" hooks) are not tied to specific classes +#define END_SCRIPTHOOK_STATIC( pVM ) \ + pVM->RegisterHook( pHook ); \ + } + +#define ScriptRegisterSimpleHook( pVM, hook, hookName, returnType, description ) \ + if (!hook.m_bDefined) \ + { \ + ScriptHook_t *pHook = &hook; \ + pHook->m_desc.m_pszScriptName = hookName; pHook->m_desc.m_pszFunction = #hook; pHook->m_desc.m_ReturnType = returnType; pHook->m_desc.m_pszDescription = description; \ + pVM->RegisterHook( pHook ); \ + } + +#define ScriptRegisterConstant( pVM, constant, description ) ScriptRegisterConstantNamed( pVM, constant, #constant, description ) +#define ScriptRegisterConstantNamed( pVM, constant, scriptName, description ) do { static ScriptConstantBinding_t binding; binding.m_pszScriptName = scriptName; binding.m_pszDescription = description; binding.m_data = ToConstantVariant(constant); pVM->RegisterConstant( &binding ); } while (0) + + #define DEFINE_MEMBERVAR( varName, returnType, description ) \ do { ScriptMemberDesc_t *pBinding = &((pDesc)->m_Members[(pDesc)->m_Members.AddToTail()]); pBinding->m_pszScriptName = varName; pBinding->m_pszDescription = description; pBinding->m_ReturnType = returnType; } while (0); #endif @@ -809,6 +828,8 @@ enum ScriptStatus_t class IScriptVM { public: + virtual ~IScriptVM() {} + virtual bool Init() = 0; virtual void Shutdown() = 0; @@ -860,6 +881,15 @@ class IScriptVM //-------------------------------------------------------- virtual ScriptStatus_t ExecuteFunction( HSCRIPT hFunction, ScriptVariant_t *pArgs, int nArgs, ScriptVariant_t *pReturn, HSCRIPT hScope, bool bWait ) = 0; +#ifdef MAPBASE_VSCRIPT + //-------------------------------------------------------- + // Hooks + //-------------------------------------------------------- + virtual bool ScopeIsHooked( HSCRIPT hScope, const char *pszEventName ) = 0; + virtual HSCRIPT LookupHookFunction( const char *pszEventName, HSCRIPT hScope, bool &bLegacy ) = 0; + virtual ScriptStatus_t ExecuteHookFunction( const char *pszEventName, HSCRIPT hFunction, ScriptVariant_t *pArgs, int nArgs, ScriptVariant_t *pReturn, HSCRIPT hScope, bool bWait ) = 0; +#endif + //-------------------------------------------------------- // External functions //-------------------------------------------------------- @@ -880,6 +910,11 @@ class IScriptVM // External enums //-------------------------------------------------------- virtual void RegisterEnum( ScriptEnumDesc_t *pEnumDesc ) = 0; + + //-------------------------------------------------------- + // External hooks + //-------------------------------------------------------- + virtual void RegisterHook( ScriptHook_t *pHookDesc ) = 0; #endif //-------------------------------------------------------- @@ -1090,6 +1125,20 @@ class IScriptVM #endif }; +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +#ifdef MAPBASE_VSCRIPT +template T *HScriptToClass( HSCRIPT hObj ) +{ + extern IScriptVM *g_pScriptVM; + return (hObj) ? (T*)g_pScriptVM->GetInstanceValue( hObj, GetScriptDesc( (T*)NULL ) ) : NULL; +} +#else +DECLARE_POINTER_HANDLE( HSCRIPT ); +#define INVALID_HSCRIPT ((HSCRIPT)-1) +#endif //----------------------------------------------------------------------------- // Script scope helper class @@ -1573,12 +1622,13 @@ struct ScriptHook_t // Cached for when CanRunInScope() is called before Call() HSCRIPT m_hFunc; + bool m_bLegacy; // Checks if there's a function of this name which would run in this scope HSCRIPT CanRunInScope( HSCRIPT hScope ) { extern IScriptVM *g_pScriptVM; - m_hFunc = g_pScriptVM->LookupFunction( m_desc.m_pszScriptName, hScope ); + m_hFunc = g_pScriptVM->LookupHookFunction( m_desc.m_pszScriptName, hScope, m_bLegacy ); return m_hFunc; } @@ -1602,7 +1652,8 @@ struct ScriptHook_t // Make sure we have a function in this scope if (!m_hFunc && !CanRunInScope(hScope)) return false; - else + // Legacy + else if (m_bLegacy) { for (int i = 0; i < m_desc.m_Parameters.Count(); i++) { @@ -1623,6 +1674,15 @@ struct ScriptHook_t return true; } + // New Hook System + else + { + g_pScriptVM->ExecuteHookFunction( m_desc.m_pszScriptName, m_hFunc, pArgs, m_desc.m_Parameters.Count(), pReturn, hScope, true ); + if (bRelease) + g_pScriptVM->ReleaseFunction( m_hFunc ); + m_hFunc = NULL; + return true; + } return false; } diff --git a/sp/src/public/vscript/vscript_templates.h b/sp/src/public/vscript/vscript_templates.h index e23a9fe9d5..2d7058a3dd 100644 --- a/sp/src/public/vscript/vscript_templates.h +++ b/sp/src/public/vscript/vscript_templates.h @@ -137,29 +137,25 @@ inline void *ScriptConvertFuncPtrToVoid( FUNCPTR_TYPE pFunc ) #elif defined( GNUC ) else if ( ( sizeof( FUNCPTR_TYPE ) == sizeof( void * ) + sizeof( int ) ) ) { - AssertMsg( 0, "Note: This path has not been verified yet. See comments below in #else case." ); - struct GnuMFP { union { void *funcadr; // If vtable_index_2 is even, then this is the function pointer. - int vtable_index_2; // If vtable_index_2 is odd, then this = vindex*2+1. + int vtable_index_2; // If vtable_index_2 is odd, then (vtable_index_2 - 1) * 2 is the index into the vtable. }; - int delta; + int delta; // Offset from this-ptr to vtable }; - + GnuMFP *p = (GnuMFP*)&pFunc; - if ( p->vtable_index_2 & 1 ) - { - char **delta = (char**)p->delta; - char *pCur = *delta + (p->vtable_index_2+1)/2; - return (void*)( pCur + 4 ); - } - else + if ( p->delta == 0 ) { + // No need to check whether this is a direct function pointer or not, + // this gets converted back to a "proper" member-function pointer in + // ScriptConvertFuncPtrFromVoid() to get invoked return p->funcadr; } + AssertMsg( 0, "Function pointer must be from primary vtable" ); } #else #error "Need to implement code to crack non-offset member function pointer case" @@ -257,8 +253,30 @@ inline FUNCPTR_TYPE ScriptConvertFuncPtrFromVoid( void *p ) convert.mfp.m_delta = 0; return convert.pFunc; } -#elif defined( POSIX ) - AssertMsg( 0, "Note: This path has not been implemented yet." ); +#elif defined( GNUC ) + if ( ( sizeof( FUNCPTR_TYPE ) == sizeof( void * ) + sizeof( int ) ) ) + { + struct GnuMFP + { + union + { + void *funcadr; // If vtable_index_2 is even, then this is the function pointer. + int vtable_index_2; // If vtable_index_2 is odd, then (vtable_index_2 - 1) * 2 is the index into the vtable. + }; + int delta; // Offset from this-ptr to vtable + }; + + union FuncPtrConvertGnu + { + GnuMFP mfp; + FUNCPTR_TYPE pFunc; + }; + + FuncPtrConvertGnu convert; + convert.mfp.funcadr = p; + convert.mfp.delta = 0; + return convert.pFunc; + } #else #error "Need to implement code to crack non-offset member function pointer case" #endif diff --git a/sp/src/raytrace/raytrace.cpp b/sp/src/raytrace/raytrace.cpp index 9816560dbc..6def3bab93 100644 --- a/sp/src/raytrace/raytrace.cpp +++ b/sp/src/raytrace/raytrace.cpp @@ -425,11 +425,11 @@ void RayTracingEnvironment::Trace4Rays(const FourRays &rays, fltx4 TMin, fltx4 T MulSIMD( SubSIMD(ReplicateX4(CurNode->SplittingPlaneValue), rays.origin[split_plane_number]),OneOverRayDir[split_plane_number]); - fltx4 active=CmpLeSIMD(TMin,TMax); // mask of which rays are active + fltx4 activeLocl=CmpLeSIMD(TMin,TMax); // mask of which rays are active // now, decide how to traverse children. can either do front,back, or do front and push // back. - fltx4 hits_front=AndSIMD(active,CmpGeSIMD(dist_to_sep_plane,TMin)); + fltx4 hits_front=AndSIMD(activeLocl,CmpGeSIMD(dist_to_sep_plane,TMin)); if (! IsAnyNegative(hits_front)) { // missed the front. only traverse back @@ -440,7 +440,7 @@ void RayTracingEnvironment::Trace4Rays(const FourRays &rays, fltx4 TMin, fltx4 T } else { - fltx4 hits_back=AndSIMD(active,CmpLeSIMD(dist_to_sep_plane,TMax)); + fltx4 hits_back=AndSIMD(activeLocl,CmpLeSIMD(dist_to_sep_plane,TMax)); if (! IsAnyNegative(hits_back) ) { // missed the back - only need to traverse front node diff --git a/sp/src/raytrace/trace2.cpp b/sp/src/raytrace/trace2.cpp index 4b1d19f613..d2e50557dd 100644 --- a/sp/src/raytrace/trace2.cpp +++ b/sp/src/raytrace/trace2.cpp @@ -155,14 +155,14 @@ void RayTracingEnvironment::RenderScene( fltx4 MaxT=ldir.length(); ldir.VectorNormalizeFast(); // now, compute shadow flag - FourRays myrays; - myrays.origin=surface_pos; + FourRays myraysLocl; + myraysLocl.origin=surface_pos; FourVectors epsilon=ldir; epsilon*=0.01; - myrays.origin+=epsilon; - myrays.direction=ldir; + myraysLocl.origin+=epsilon; + myraysLocl.direction=ldir; RayTracingResult shadowtest; - Trace4Rays(myrays,Four_Zeros,MaxT, &shadowtest); + Trace4Rays(myraysLocl,Four_Zeros,MaxT, &shadowtest); fltx4 unshadowed=CmpGtSIMD(shadowtest.HitDistance,MaxT); if (! (IsAllZeros(unshadowed))) { diff --git a/sp/src/responserules/runtime/criteriaset.cpp b/sp/src/responserules/runtime/criteriaset.cpp new file mode 100644 index 0000000000..6d53e95455 --- /dev/null +++ b/sp/src/responserules/runtime/criteriaset.cpp @@ -0,0 +1,479 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// +#include "rrbase.h" + +#include "utlmap.h" + +#include "tier1/mapbase_con_groups.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include + +using namespace ResponseRules; + +//----------------------------------------------------------------------------- +// Case-insensitive criteria symbol table +//----------------------------------------------------------------------------- +CUtlSymbolTable CriteriaSet::sm_CriteriaSymbols( 1024, 1024, true ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *raw - +// *key - +// keylen - +// *value - +// valuelen - +// *duration - +// Output : static bool +//----------------------------------------------------------------------------- +const char *SplitContext( const char *raw, char *key, int keylen, char *value, int valuelen, float *duration, const char *entireContext ) +{ + char *colon1 = Q_strstr( raw, ":" ); + if ( !colon1 ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "SplitContext: warning, ignoring context '%s', missing colon separator!\n", raw ); + *key = *value = 0; + return NULL; + } + + int len = colon1 - raw; + Q_strncpy( key, raw, MIN( len + 1, keylen ) ); + key[ MIN( len, keylen - 1 ) ] = 0; + + bool last = false; + char *end = Q_strstr( colon1 + 1, "," ); + if ( !end ) + { + int remaining = Q_strlen( colon1 + 1 ); + end = colon1 + 1 + remaining; + last = true; + } + + char *colon2 = Q_strstr( colon1 + 1, ":" ); + if ( colon2 && ( colon2 < end ) ) + { + if ( duration ) + *duration = atof( colon2 + 1 ); + + char durationStartChar = *(colon2 + 1); + if ( durationStartChar < '0' || durationStartChar > '9' ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "SplitContext: warning, ignoring context '%s', missing comma separator! Entire context was '%s'.\n", raw, entireContext ); + *key = *value = 0; + return NULL; + } + + len = MIN( colon2 - ( colon1 + 1 ), valuelen - 1 ); + Q_strncpy( value, colon1 + 1, len + 1 ); + value[ len ] = 0; + } + else + { + if ( duration ) + *duration = 0.0; + + len = MIN( end - ( colon1 + 1 ), valuelen - 1 ); + Q_strncpy( value, colon1 + 1, len + 1 ); + value[ len ] = 0; + } + + return last ? NULL : end + 1; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CriteriaSet::CriteriaSet() : m_Lookup( 0, 0, CritEntry_t::LessFunc ), m_bOverrideOnAppend(true), + m_nNumPrefixedContexts(0) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CriteriaSet::CriteriaSet( const CriteriaSet& src ) : m_Lookup( 0, 0, CritEntry_t::LessFunc ), m_nNumPrefixedContexts(src.m_nNumPrefixedContexts) +{ + m_Lookup.EnsureCapacity( src.m_Lookup.Count() ); + for ( short i = src.m_Lookup.FirstInorder(); + i != src.m_Lookup.InvalidIndex(); + i = src.m_Lookup.NextInorder( i ) ) + { + m_Lookup.Insert( src.m_Lookup[ i ] ); + } +} + +CriteriaSet::CriteriaSet( const char *criteria, const char *value ) : m_Lookup( 0, 0, CritEntry_t::LessFunc ), m_bOverrideOnAppend(true) +{ + AppendCriteria(criteria,value); +} + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CriteriaSet::~CriteriaSet() +{ +} + +//----------------------------------------------------------------------------- +// Computes a symbol for the criteria +//----------------------------------------------------------------------------- +CriteriaSet::CritSymbol_t CriteriaSet::ComputeCriteriaSymbol( const char *criteria ) +{ + return sm_CriteriaSymbols.AddString( criteria ); +} + + +//----------------------------------------------------------------------------- +// Computes a symbol for the criteria +//----------------------------------------------------------------------------- +void CriteriaSet::AppendCriteria( CriteriaSet::CritSymbol_t criteria, const char *value, float weight ) +{ + int idx = FindCriterionIndex( criteria ); + if ( idx == -1 ) + { + CritEntry_t entry; + entry.criterianame = criteria; + MEM_ALLOC_CREDIT(); + idx = m_Lookup.Insert( entry ); + if ( sm_CriteriaSymbols.String(criteria)[0] == kAPPLYTOWORLDPREFIX ) + { + m_nNumPrefixedContexts += 1; + } + } + else // criteria already existed + { + // bail out if override existing criteria is not allowed + if ( !m_bOverrideOnAppend ) + return; + } + + CritEntry_t *entry = &m_Lookup[ idx ]; + entry->SetValue( value ); + entry->weight = weight; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *criteria - +// "" - +// 1.0f - +//----------------------------------------------------------------------------- +void CriteriaSet::AppendCriteria( const char *pCriteriaName, const char *value /*= ""*/, float weight /*= 1.0f*/ ) +{ + CUtlSymbol criteria = ComputeCriteriaSymbol( pCriteriaName ); + AppendCriteria( criteria, value, weight ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *criteria - +// "" - +// 1.0f - +//----------------------------------------------------------------------------- +void CriteriaSet::AppendCriteria( const char *criteria, float value, float weight /*= 1.0f*/ ) +{ + char buf[32]; + V_snprintf( buf, 32, "%f", value ); + AppendCriteria( criteria, buf, weight ); +} + + +//----------------------------------------------------------------------------- +// Removes criteria in a set +//----------------------------------------------------------------------------- +void CriteriaSet::RemoveCriteria( const char *criteria ) +{ + const int idx = FindCriterionIndex( criteria ); + if ( idx == -1 ) + return; + + if ( criteria[0] == kAPPLYTOWORLDPREFIX ) + { + Assert( m_nNumPrefixedContexts > 0 ); + m_nNumPrefixedContexts = isel( m_nNumPrefixedContexts - 1, m_nNumPrefixedContexts - 1, 0 ); + } + RemoveCriteria( idx, false ); +} + +// bTestForIndex tells us whether the calling function has already checked for a +// $ prefix and decremented m_nNumPrefixedContexts appropriately (false), +// or if this function should do that (true). +void CriteriaSet::RemoveCriteria( int idx, bool bTestForPrefix ) +{ + Assert( m_Lookup.IsValidIndex(idx) ); + if ( bTestForPrefix ) + { + if ( sm_CriteriaSymbols.String( m_Lookup[idx].criterianame )[0] == kAPPLYTOWORLDPREFIX ) + { + Assert( m_nNumPrefixedContexts > 0 ); + m_nNumPrefixedContexts = isel( m_nNumPrefixedContexts - 1, m_nNumPrefixedContexts - 1, 0 ); + } + } + m_Lookup.RemoveAt( idx ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CriteriaSet::GetCount() const +{ + return m_Lookup.Count(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +// Output : int +//----------------------------------------------------------------------------- +int CriteriaSet::FindCriterionIndex( CritSymbol_t criteria ) const +{ + CritEntry_t search; + search.criterianame = criteria; + int idx = m_Lookup.Find( search ); + return ( idx == m_Lookup.InvalidIndex() ) ? -1 : idx; +} + +int CriteriaSet::FindCriterionIndex( const char *name ) const +{ + CUtlSymbol criteria = ComputeCriteriaSymbol( name ); + return FindCriterionIndex( criteria ); +} + + +//----------------------------------------------------------------------------- +// Returns the name symbol +//----------------------------------------------------------------------------- +CriteriaSet::CritSymbol_t CriteriaSet::GetNameSymbol( int nIndex ) const +{ + if ( nIndex < 0 || nIndex >= (int)m_Lookup.Count() ) + return UTL_INVAL_SYMBOL; + + const CritEntry_t *entry = &m_Lookup[ nIndex ]; + return entry->criterianame; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : index - +// Output : char const +//----------------------------------------------------------------------------- +const char *CriteriaSet::GetName( int index ) const +{ + if ( index < 0 || index >= (int)m_Lookup.Count() ) + return ""; + else + { + const char *pCriteriaName = sm_CriteriaSymbols.String( m_Lookup[ index ].criterianame ); + return pCriteriaName ? pCriteriaName : ""; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : index - +// Output : char const +//----------------------------------------------------------------------------- +const char *CriteriaSet::GetValue( int index ) const +{ + if ( index < 0 || index >= (int)m_Lookup.Count() ) + return ""; + + const CritEntry_t *entry = &m_Lookup[ index ]; + return entry->value ? entry->value : ""; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : index - +// Output : float +//----------------------------------------------------------------------------- +float CriteriaSet::GetWeight( int index ) const +{ + if ( index < 0 || index >= (int)m_Lookup.Count() ) + return 1.0f; + + const CritEntry_t *entry = &m_Lookup[ index ]; + return entry->weight; +} + + +//----------------------------------------------------------------------------- +// Purpose: Merge another criteria set into this one. +//----------------------------------------------------------------------------- +void CriteriaSet::Merge( const CriteriaSet * RESTRICT otherCriteria ) +{ + Assert(otherCriteria); + if (!otherCriteria) + return; + + // for now, just duplicate everything. + int count = otherCriteria->GetCount(); + EnsureCapacity( count + GetCount() ); + for ( int i = 0 ; i < count ; ++i ) + { + AppendCriteria( otherCriteria->GetNameSymbol(i), otherCriteria->GetValue(i), otherCriteria->GetWeight(i) ); + } +} + +void CriteriaSet::Merge( const char *modifiers ) // add criteria parsed from a text string +{ + // Always include any optional modifiers + if ( modifiers == NULL ) + return; + + char copy_modifiers[ 255 ]; + const char *pCopy; + char key[ 128 ] = { 0 }; + char value[ 128 ] = { 0 }; + + Q_strncpy( copy_modifiers, modifiers, sizeof( copy_modifiers ) ); + pCopy = copy_modifiers; + + while( pCopy ) + { + pCopy = SplitContext( pCopy, key, sizeof( key ), value, sizeof( value ), NULL, modifiers ); + + if( *key && *value ) + { + AppendCriteria( key, value, 1 ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CriteriaSet::Describe() const +{ + // build an alphabetized representation of the set for printing + typedef CUtlMap tMap; + tMap m_TempMap( 0, m_Lookup.Count(), CaselessStringLessThan ); + + for ( short i = m_Lookup.FirstInorder(); i != m_Lookup.InvalidIndex(); i = m_Lookup.NextInorder( i ) ) + { + const CritEntry_t *entry = &m_Lookup[ i ]; + + m_TempMap.Insert( sm_CriteriaSymbols.String( entry->criterianame ), entry ); + } + + for ( tMap::IndexType_t i = m_TempMap.FirstInorder(); i != m_TempMap.InvalidIndex(); i = m_TempMap.NextInorder( i ) ) + { + // const CritEntry_t *entry = &m_TempMap[ i ]; + // const char *pCriteriaName = sm_CriteriaSymbols.String( entry->criterianame ); + + const char *name = m_TempMap.Key( i ); + const CritEntry_t *entry = m_TempMap.Element( i ); + if ( entry->weight != 1.0f ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, " %20s = '%s' (weight %f)\n", name, entry->value ? entry->value : "", entry->weight ); + } + else + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, " %20s = '%s'\n", name, entry->value ? entry->value : "" ); + } + } + + /* + for ( short i = m_Lookup.FirstInorder(); i != m_Lookup.InvalidIndex(); i = m_Lookup.NextInorder( i ) ) + { + const CritEntry_t *entry = &m_Lookup[ i ]; + + const char *pCriteriaName = sm_CriteriaSymbols.String( entry->criterianame ); + if ( entry->weight != 1.0f ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, " %20s = '%s' (weight %f)\n", pCriteriaName, entry->value ? entry->value : "", entry->weight ); + } + else + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, " %20s = '%s'\n", pCriteriaName, entry->value ? entry->value : "" ); + } + } + */ +} + + +void CriteriaSet::Reset() +{ + m_Lookup.Purge(); +} + +void CriteriaSet::WriteToEntity( CBaseEntity *pEntity ) +{ +#if 0 + if ( GetCount() < 1 ) + return; + + for ( int i = Head() ; IsValidIndex(i); i = Next(i) ) + { + pEntity->AddContext( GetName(i), GetValue(i), 0 ); + } +#else + AssertMsg( false, "CriteriaSet::WriteToEntity has not been ported from l4d2.\n" ); +#endif +} + +int CriteriaSet::InterceptWorldSetContexts( CriteriaSet * RESTRICT pFrom, CriteriaSet * RESTRICT pSetOnWorld ) +{ + // Assert( pFrom ); Assert( pTo ); Assert( pSetOnWorld ); + Assert( pSetOnWorld != pFrom ); + Assert( pSetOnWorld->GetCount() == 0 ); + + if ( pFrom->m_nNumPrefixedContexts == 0 ) + { + // nothing needs to be done to it. + return 0; + } + +#ifdef DEBUG + // save this off for later error checking. + const int nPrefixedContexts = pFrom->m_nNumPrefixedContexts; +#endif + + // make enough space for the expected output quantity. + pSetOnWorld->EnsureCapacity( pFrom->m_nNumPrefixedContexts ); + + // initialize a buffer with the "world" prefix (so we can use strncpy instead of snprintf and be much faster) + char buf[80] = { 'w', 'o', 'r', 'l', 'd', '\0' }; + const unsigned int PREFIXLEN = 5; // strlen("world") + + // create a second tree that has the appropriately renamed criteria, + // then swap it into pFrom + CriteriaSet rewrite; + rewrite.EnsureCapacity( pFrom->GetCount() + 1 ); + + for ( int i = pFrom->Head(); pFrom->IsValidIndex(i); i = pFrom->Next(i) ) + { + const char *pszName = pFrom->GetName( i ); + if ( pszName[0] == CriteriaSet::kAPPLYTOWORLDPREFIX ) + { // redirect to the world contexts + V_strncpy( buf+PREFIXLEN, pszName+1, sizeof(buf) - PREFIXLEN ); + rewrite.AppendCriteria( buf, pFrom->GetValue(i), pFrom->GetWeight(i) ); + pSetOnWorld->AppendCriteria( pszName+1, pFrom->GetValue(i), pFrom->GetWeight(i) ); + buf[PREFIXLEN] = 0; + } + else + { // does not need to be fiddled; do not write back to world + rewrite.AppendCriteria( pFrom->GetNameSymbol(i), pFrom->GetValue(i), pFrom->GetWeight(i) ); + } + } + AssertMsg2( pSetOnWorld->GetCount() == nPrefixedContexts, "Count of $ persistent RR contexts is inconsistent (%d vs %d)! Call Elan.", + pSetOnWorld->GetCount(), nPrefixedContexts ); + + pFrom->m_nNumPrefixedContexts = 0; + pFrom->m_Lookup.Swap(rewrite.m_Lookup); + return pSetOnWorld->GetCount(); +} diff --git a/sp/src/responserules/runtime/response_rules.vpc b/sp/src/responserules/runtime/response_rules.vpc new file mode 100644 index 0000000000..9ab73afaa1 --- /dev/null +++ b/sp/src/responserules/runtime/response_rules.vpc @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------------- +// response_rules.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$macro SRCDIR "..\.." +$include "$SRCDIR\vpc_scripts\source_lib_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE;..\public\responserules" + $PreprocessorDefinitions "$BASE;RR_RUNTIME" + } +} + +$Project "responserules_runtime" +{ + $Folder "Source Files" + { + $File "criteriaset.cpp" + $File "response_system.cpp" + $File "response_system.h" + $File "response_types.cpp" + $File "response_types_internal.cpp" + $File "response_types_internal.h" + $File "rr_convars.cpp" + $File "rr_response.cpp" + $File "rr_speechconcept.cpp" + $File "rrrlib.cpp" + } + + $Folder "Public Header Files" + { + $File "$SRCDIR\public\responserules\response_host_interface.h" + $File "$SRCDIR\public\responserules\response_types.h" + $File "$SRCDIR\public\responserules\rr_speechconcept.h" + } +} \ No newline at end of file diff --git a/sp/src/responserules/runtime/response_system.cpp b/sp/src/responserules/runtime/response_system.cpp new file mode 100644 index 0000000000..d294316d19 --- /dev/null +++ b/sp/src/responserules/runtime/response_system.cpp @@ -0,0 +1,2944 @@ +//========= Copyright © 1996-2010, Valve Corporation, All rights reserved. ============// +// +// Purpose: Core types for the response rules -- criteria, responses, rules, and matchers. +// +// $NoKeywords: $ +//=============================================================================// + +#include "rrbase.h" +#include "vstdlib/random.h" +#include "utlbuffer.h" +#include "tier1/interval.h" +#include "convar.h" +#include "fmtstr.h" +#include "generichash.h" +#include "tier1/mapbase_con_groups.h" +#ifdef MAPBASE +#include "tier1/mapbase_matchers_base.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// #pragma optimize( "", off ) + +using namespace ResponseRules; +static void CC_RR_Debug_ResponseConcept_Exclude( const CCommand &args ); +static ConCommand rr_debug_responseconcept_exclude( "rr_debugresponseconcept_exclude", CC_RR_Debug_ResponseConcept_Exclude, "Set a list of concepts to exclude from rr_debugresponseconcept. Separate multiple concepts with spaces. Call with no arguments to see current list. Call 'rr_debug_responseconcept_exclude !' to reset."); + +namespace ResponseRules +{ + // ick + // Wrap string lookup with a hash on the string so that all of the repetitive playerxxx type strings get bucketed out better + #define STRING_BUCKETS_COUNT 64 // Must be power of two + #define STRING_BUCKETS_MASK ( STRING_BUCKETS_COUNT - 1 ) + + CUtlRBTree g_ResponseStrings[ STRING_BUCKETS_COUNT ]; + class CResponseStringBuckets + { + public: + CResponseStringBuckets() + { + for ( int i = 0; i < ARRAYSIZE( g_ResponseStrings ); ++i ) + { + g_ResponseStrings[ i ].SetLessFunc( &StringLessThan ); + } + } + } g_ReponseStringBucketInitializer; + + const char *ResponseCopyString( const char *in ) + { + if ( !in ) + return NULL; + if ( !*in ) + return ""; + + int bucket = ( RR_HASH( in ) & STRING_BUCKETS_MASK ); + + CUtlRBTree &list = g_ResponseStrings[ bucket ]; + + int i = list.Find( in ); + if ( i != list.InvalidIndex() ) +{ + return list[i]; + } + + int len = Q_strlen( in ); + char *out = new char[ len + 1 ]; + Q_memcpy( out, in, len ); + out[ len ] = 0; + list.Insert( out ); + return out; + } +} + +IEngineEmulator *IEngineEmulator::Get() +{ + AssertMsg( IEngineEmulator::s_pSingleton, "Response rules fail: no IEngineEmulator" ); + return IEngineEmulator::s_pSingleton; +} + + +//----------------------------------------------------------------------------- +// Convars +//----------------------------------------------------------------------------- + + +ConVar rr_debugresponses( "rr_debugresponses", "0", FCVAR_NONE, "Show verbose matching output (1 for simple, 2 for rule scoring, 3 for noisy). If set to 4, it will only show response success/failure for npc_selected NPCs." ); +ConVar rr_debugrule( "rr_debugrule", "", FCVAR_NONE, "If set to the name of the rule, that rule's score will be shown whenever a concept is passed into the response rules system."); +ConVar rr_dumpresponses( "rr_dumpresponses", "0", FCVAR_NONE, "Dump all response_rules.txt and rules (requires restart)" ); +ConVar rr_debugresponseconcept( "rr_debugresponseconcept", "", FCVAR_NONE, "If set, rr_debugresponses will print only responses testing for the specified concept" ); +#define RR_DEBUGRESPONSES_SPECIALCASE 4 + +#ifdef MAPBASE +ConVar rr_disableemptyrules( "rr_disableemptyrules", "0", FCVAR_NONE, "Disables rules with no remaining responses, e.g. rules which use norepeat responses." ); +#endif + + + +//----------------------------------------------------------------------------- +// Copied from SoundParametersInternal.cpp +//----------------------------------------------------------------------------- + +#define SNDLVL_PREFIX "SNDLVL_" + +struct SoundLevelLookup +{ + soundlevel_t level; + char const *name; +}; + +// NOTE: Needs to reflect the soundlevel_t enum defined in soundflags.h +static SoundLevelLookup g_pSoundLevels[] = +{ + { SNDLVL_NONE, "SNDLVL_NONE" }, + { SNDLVL_20dB, "SNDLVL_20dB" }, + { SNDLVL_25dB, "SNDLVL_25dB" }, + { SNDLVL_30dB, "SNDLVL_30dB" }, + { SNDLVL_35dB, "SNDLVL_35dB" }, + { SNDLVL_40dB, "SNDLVL_40dB" }, + { SNDLVL_45dB, "SNDLVL_45dB" }, + { SNDLVL_50dB, "SNDLVL_50dB" }, + { SNDLVL_55dB, "SNDLVL_55dB" }, + { SNDLVL_IDLE, "SNDLVL_IDLE" }, + { SNDLVL_TALKING, "SNDLVL_TALKING" }, + { SNDLVL_60dB, "SNDLVL_60dB" }, + { SNDLVL_65dB, "SNDLVL_65dB" }, + { SNDLVL_STATIC, "SNDLVL_STATIC" }, + { SNDLVL_70dB, "SNDLVL_70dB" }, + { SNDLVL_NORM, "SNDLVL_NORM" }, + { SNDLVL_75dB, "SNDLVL_75dB" }, + { SNDLVL_80dB, "SNDLVL_80dB" }, + { SNDLVL_85dB, "SNDLVL_85dB" }, + { SNDLVL_90dB, "SNDLVL_90dB" }, + { SNDLVL_95dB, "SNDLVL_95dB" }, + { SNDLVL_100dB, "SNDLVL_100dB" }, + { SNDLVL_105dB, "SNDLVL_105dB" }, + { SNDLVL_110dB, "SNDLVL_110dB" }, + { SNDLVL_120dB, "SNDLVL_120dB" }, + { SNDLVL_130dB, "SNDLVL_130dB" }, + { SNDLVL_GUNFIRE, "SNDLVL_GUNFIRE" }, + { SNDLVL_140dB, "SNDLVL_140dB" }, + { SNDLVL_150dB, "SNDLVL_150dB" }, + { SNDLVL_180dB, "SNDLVL_180dB" }, +}; + +static soundlevel_t TextToSoundLevel( const char *key ) +{ + if ( !key ) + { + Assert( 0 ); + return SNDLVL_NORM; + } + + int c = ARRAYSIZE( g_pSoundLevels ); + + int i; + + for ( i = 0; i < c; i++ ) + { + SoundLevelLookup *entry = &g_pSoundLevels[ i ]; + if ( !Q_strcasecmp( key, entry->name ) ) + return entry->level; + } + + if ( !Q_strnicmp( key, SNDLVL_PREFIX, Q_strlen( SNDLVL_PREFIX ) ) ) + { + char const *val = key + Q_strlen( SNDLVL_PREFIX ); + int sndlvl = atoi( val ); + if ( sndlvl > 0 && sndlvl <= 180 ) + { + return ( soundlevel_t )sndlvl; + } + } + + DevMsg( "CSoundEmitterSystem: Unknown sound level %s\n", key ); + + return SNDLVL_NORM; +} + +CResponseSystem::ExcludeList_t CResponseSystem::m_DebugExcludeList( 4, 0 ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CResponseSystem::CResponseSystem() : + m_RootCommandHashes( 0, 0, DefLessFunc( unsigned int ) ), + m_FileDispatch( 0, 0, DefLessFunc( unsigned int ) ), + m_RuleDispatch( 0, 0, DefLessFunc( unsigned int ) ), + m_ResponseDispatch( 0, 0, DefLessFunc( unsigned int ) ), + m_ResponseGroupDispatch( 0, 0, DefLessFunc( unsigned int ) ) +{ + token[0] = 0; + m_bUnget = false; + m_bCustomManagable = false; +#ifdef MAPBASE + m_bInProspective = false; +#endif + + BuildDispatchTables(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CResponseSystem::~CResponseSystem() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : char const +//----------------------------------------------------------------------------- +void CResponseSystem::GetCurrentScript( char *buf, size_t buflen ) +{ + Assert( buf ); + buf[ 0 ] = 0; + if ( m_ScriptStack.Count() <= 0 ) + return; + + if ( IEngineEmulator::Get()->GetFilesystem()->String( m_ScriptStack[ 0 ].name, buf, buflen ) ) + { + return; + } + buf[ 0 ] = 0; +} + +void CResponseSystem::PushScript( const char *scriptfile, unsigned char *buffer ) +{ + ScriptEntry e; + e.name = IEngineEmulator::Get()->GetFilesystem()->FindOrAddFileName( scriptfile ); + e.buffer = buffer; + e.currenttoken = (char *)e.buffer; + e.tokencount = 0; + m_ScriptStack.AddToHead( e ); +} + +void CResponseSystem::PopScript(void) +{ + Assert( m_ScriptStack.Count() >= 1 ); + if ( m_ScriptStack.Count() <= 0 ) + return; + + m_ScriptStack.Remove( 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::Clear() +{ + m_Responses.RemoveAll(); + m_Criteria.RemoveAll(); +#ifdef MAPBASE + // Must purge to avoid issues with reloading the system + m_RulePartitions.PurgeAndDeleteElements(); +#else + m_RulePartitions.RemoveAll(); +#endif + m_Enumerations.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +// found - +// Output : float +//----------------------------------------------------------------------------- +float CResponseSystem::LookupEnumeration( const char *name, bool& found ) +{ + int idx = m_Enumerations.Find( name ); + if ( idx == m_Enumerations.InvalidIndex() ) + { + found = false; + return 0.0f; + } + + + found = true; + return m_Enumerations[ idx ].value; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : matcher - +//----------------------------------------------------------------------------- +void CResponseSystem::ResolveToken( Matcher& matcher, char *token, size_t bufsize, char const *rawtoken ) +{ + if ( rawtoken[0] != '[' ) + { + Q_strncpy( token, rawtoken, bufsize ); + return; + } + + // Now lookup enumeration + bool found = false; + float f = LookupEnumeration( rawtoken, found ); + if ( !found ) + { + Q_strncpy( token, rawtoken, bufsize ); + ResponseWarning( "No such enumeration '%s'\n", token ); + return; + } + + Q_snprintf( token, bufsize, "%f", f ); +} + + +#ifndef MAPBASE // Already in mapbase_matchers_base +static bool AppearsToBeANumber( char const *token ) +{ + if ( atof( token ) != 0.0f ) + return true; + + char const *p = token; + while ( *p ) + { + if ( *p != '0' ) + return false; + + p++; + } + + return true; +} +#endif + +void CResponseSystem::ComputeMatcher( Criteria *c, Matcher& matcher ) +{ + const char *s = c->value; + if ( !s ) + { + matcher.valid = false; + return; + } + + const char *in = s; + + char token[ 128 ]; + char rawtoken[ 128 ]; + + token[ 0 ] = 0; + rawtoken[ 0 ] = 0; + + int n = 0; + + bool gt = false; + bool lt = false; + bool eq = false; + bool nt = false; +#ifdef MAPBASE + bool bit = false; +#endif + + bool done = false; + while ( !done ) + { + switch( *in ) + { + case '>': + { + gt = true; + Assert( !lt ); // Can't be both + } + break; + case '<': + { + lt = true; + Assert( !gt ); // Can't be both + } + break; + case '=': + { + eq = true; + } + break; + case ',': + case '\0': + { + rawtoken[ n ] = 0; + n = 0; + + // Convert raw token to real token in case token is an enumerated type specifier + ResolveToken( matcher, token, sizeof( token ), rawtoken ); + +#ifdef MAPBASE + // Bits are an entirely different and independent story + if (bit) + { + matcher.isbit = true; + matcher.notequal = nt; + + matcher.isnumeric = true; + } + else +#endif + // Fill in first data set + if ( gt ) + { + matcher.usemin = true; + matcher.minequals = eq; + matcher.minval = (float)atof( token ); + + matcher.isnumeric = true; + } + else if ( lt ) + { + matcher.usemax = true; + matcher.maxequals = eq; + matcher.maxval = (float)atof( token ); + + matcher.isnumeric = true; + } + else + { + if ( *in == ',' ) + { + // If there's a comma, this better have been a less than or a gt key + Assert( 0 ); + } + + matcher.notequal = nt; + + matcher.isnumeric = AppearsToBeANumber( token ); + } + + gt = lt = eq = nt = false; + + if ( !(*in) ) + { + done = true; + } + } + break; + case '!': + nt = true; + break; +#ifdef MAPBASE + case '~': + nt = true; + case '&': + bit = true; + break; +#endif + default: + rawtoken[ n++ ] = *in; + break; + } + + in++; + } + + matcher.SetToken( token ); + matcher.SetRaw( rawtoken ); + matcher.valid = true; +} + +bool CResponseSystem::CompareUsingMatcher( const char *setValue, Matcher& m, bool verbose /*=false*/ ) +{ + if ( !m.valid ) + return false; + + float v = (float)atof( setValue ); + if ( setValue[0] == '[' ) + { + bool found = false; + v = LookupEnumeration( setValue, found ); + } + +#ifdef MAPBASE + // Bits are always a different story + if (m.isbit) + { + int v1 = v; + int v2 = atoi( m.GetToken() ); + if (m.notequal) + return (v1 & v2) == 0; + else + return (v1 & v2) != 0; + } +#endif + + int minmaxcount = 0; + + if ( m.usemin ) + { + if ( m.minequals ) + { + if ( v < m.minval ) + return false; + } + else + { + if ( v <= m.minval ) + return false; + } + + ++minmaxcount; + } + + if ( m.usemax ) + { + if ( m.maxequals ) + { + if ( v > m.maxval ) + return false; + } + else + { + if ( v >= m.maxval ) + return false; + } + + ++minmaxcount; + } + + // Had one or both criteria and met them + if ( minmaxcount >= 1 ) + { + return true; + } + + if ( m.notequal ) + { + if ( m.isnumeric ) + { + if ( v == (float)atof( m.GetToken() ) ) + return false; + } + else + { +#ifdef MAPBASE + if ( Matcher_NamesMatch( m.GetToken(), setValue ) ) +#else + if ( !Q_stricmp( setValue, m.GetToken() ) ) +#endif + return false; + } + + return true; + } + + if ( m.isnumeric ) + { + // If the setValue is "", the NPC doesn't have the key at all, + // in which case we shouldn't match "0". + if ( !setValue || !setValue[0] ) + return false; + + return v == (float)atof( m.GetToken() ); + } + +#ifdef MAPBASE + return Matcher_NamesMatch( m.GetToken(), setValue ); +#else + return !Q_stricmp( setValue, m.GetToken() ) ? true : false; +#endif +} + +bool CResponseSystem::Compare( const char *setValue, Criteria *c, bool verbose /*= false*/ ) +{ + Assert( c ); + Assert( setValue ); + + bool bret = CompareUsingMatcher( setValue, c->matcher, verbose ); + + if ( verbose ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "'%20s' vs. '%20s' = ", setValue, c->value ); + + { + //DevMsg( "\n" ); + //m.Describe(); + } + } + return bret; +} + +float CResponseSystem::RecursiveScoreSubcriteriaAgainstRule( const CriteriaSet& set, Criteria *parent, bool& exclude, bool verbose /*=false*/ ) +{ + float score = 0.0f; + int subcount = parent->subcriteria.Count(); + for ( int i = 0; i < subcount; i++ ) + { + int icriterion = parent->subcriteria[ i ]; + + bool excludesubrule = false; + if (verbose) + { + DevMsg( "\n" ); + } + score += ScoreCriteriaAgainstRuleCriteria( set, icriterion, excludesubrule, verbose ); + } + + exclude = ( parent->required && score == 0.0f ) ? true : false; + + return score * parent->weight.GetFloat(); +} + +float CResponseSystem::RecursiveLookForCriteria( const CriteriaSet &criteriaSet, Criteria *pParent ) +{ + float flScore = 0.0f; + int nSubCount = pParent->subcriteria.Count(); + for ( int iSub = 0; iSub < nSubCount; ++iSub ) + { + int iCriteria = pParent->subcriteria[iSub]; + flScore += LookForCriteria( criteriaSet, iCriteria ); + } + + return flScore; +} + +float CResponseSystem::LookForCriteria( const CriteriaSet &criteriaSet, int iCriteria ) +{ + Criteria *pCriteria = &m_Criteria[iCriteria]; + if ( pCriteria->IsSubCriteriaType() ) + { + return RecursiveLookForCriteria( criteriaSet, pCriteria ); + } + + int iIndex = criteriaSet.FindCriterionIndex( pCriteria->nameSym ); + if ( iIndex == -1 ) + return 0.0f; + + Assert( criteriaSet.GetValue( iIndex ) ); + if ( Q_stricmp( criteriaSet.GetValue( iIndex ), pCriteria->value ) ) + return 0.0f; + + return 1.0f; +} + +float CResponseSystem::ScoreCriteriaAgainstRuleCriteria( const CriteriaSet& set, int icriterion, bool& exclude, bool verbose /*=false*/ ) +{ + Criteria *c = &m_Criteria[ icriterion ]; + + if ( c->IsSubCriteriaType() ) + { + return RecursiveScoreSubcriteriaAgainstRule( set, c, exclude, verbose ); + } + + if ( verbose ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, " criterion '%25s':'%15s' ", m_Criteria.GetElementName( icriterion ), CriteriaSet::SymbolToStr(c->nameSym) ); + } + + exclude = false; + + float score = 0.0f; + + const char *actualValue = ""; + + /* + const char * RESTRICT critname = c->name; + CUtlSymbol sym(critname); + const char * nameDoubleCheck = sym.String(); + */ + int found = set.FindCriterionIndex( c->nameSym ); + if ( found != -1 ) + { + actualValue = set.GetValue( found ); + if ( !actualValue ) + { + Assert( 0 ); + return score; + } + } + + Assert( actualValue ); + + if ( Compare( actualValue, c, verbose ) ) + { + float w = set.GetWeight( found ); + score = w * c->weight.GetFloat(); + + if ( verbose ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "matched, weight %4.2f (s %4.2f x c %4.2f)", + score, w, c->weight.GetFloat() ); + } + } + else + { + if ( c->required ) + { + exclude = true; + if ( verbose ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "failed (+exclude rule)" ); + } + } + else + { + if ( verbose ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "failed" ); + } + } + } + + return score; +} + +float CResponseSystem::ScoreCriteriaAgainstRule( const CriteriaSet& set, ResponseRulePartition::tRuleDict &dict, int irule, bool verbose /*=false*/ ) +{ + Rule * RESTRICT rule = dict[ irule ]; + float score = 0.0f; + + bool bBeingWatched = false; + + // See if we're trying to debug this rule + const char *pszText = rr_debugrule.GetString(); + if ( pszText && pszText[0] && !Q_stricmp( pszText, dict.GetElementName( irule ) ) ) + { + bBeingWatched = true; + } + + if ( !rule->IsEnabled() ) + { + if ( bBeingWatched ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "Rule is disabled.\n" ); + } + return 0.0f; + } + + if ( bBeingWatched ) + { + verbose = true; + } + + if ( verbose ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "Scoring rule '%s' (%i)\n{\n", dict.GetElementName( irule ), irule+1 ); + } + + // Iterate set criteria + int count = rule->m_Criteria.Count(); + int i; + for ( i = 0; i < count; i++ ) + { + int icriterion = rule->m_Criteria[ i ]; + + bool exclude = false; + score += ScoreCriteriaAgainstRuleCriteria( set, icriterion, exclude, verbose ); + + if ( verbose ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, ", score %4.2f\n", score ); + } + + if ( exclude ) + { + score = 0.0f; + break; + } + } + + if ( verbose ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "}\n" ); + } + + if ( rule->m_nForceWeight > 0 ) + { // this means override the cumulative weight of criteria and just force the rule's total score, + // assuming it matched at all. + return fsel( score - FLT_MIN, rule->m_nForceWeight, 0 ); + } + else + { + return score; +} +} + +void CResponseSystem::DebugPrint( int depth, const char *fmt, ... ) +{ + int indentchars = 3 * depth; + char *indent = (char *) stackalloc( indentchars + 1); + indent[ indentchars ] = 0; + while ( --indentchars >= 0 ) + { + indent[ indentchars ] = ' '; + } + + // Dump text to debugging console. + va_list argptr; + char szText[1024]; + + va_start (argptr, fmt); + Q_vsnprintf (szText, sizeof( szText ), fmt, argptr); + va_end (argptr); + + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "%s%s", indent, szText ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::ResetResponseGroups() +{ + int i; + int c = m_Responses.Count(); + for ( i = 0; i < c; i++ ) + { + m_Responses[ i ].Reset(); + } + +#ifdef MAPBASE + for ( ResponseRulePartition::tIndex idx = m_RulePartitions.First() ; + m_RulePartitions.IsValid(idx) ; + idx = m_RulePartitions.Next(idx) ) + { + m_RulePartitions[ idx ].m_bEnabled = true; + } +#endif +} + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::DisableEmptyRules() +{ + if (rr_disableemptyrules.GetBool() == false) + return; + + for ( ResponseRulePartition::tIndex idx = m_RulePartitions.First() ; + m_RulePartitions.IsValid(idx) ; + idx = m_RulePartitions.Next(idx) ) + { + Rule &rule = m_RulePartitions[ idx ]; + + // Set it as disabled in advance + rule.m_bEnabled = false; + + int c2 = rule.m_Responses.Count(); + for (int s = 0; s < c2; s++) + { + if (m_Responses[rule.m_Responses[s]].IsEnabled()) + { + // Re-enable it if there's any valid responses + rule.m_bEnabled = true; + break; + } + } + } +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: Make certain responses unavailable by marking them as depleted +//----------------------------------------------------------------------------- +void CResponseSystem::FakeDepletes( ResponseGroup *g, IResponseFilter *pFilter ) +{ + m_FakedDepletes.RemoveAll(); + + // Fake depletion of unavailable choices + int c = g->group.Count(); + if ( pFilter && g->ShouldCheckRepeats() ) + { + for ( int i = 0; i < c; i++ ) + { + ParserResponse *r = &g->group[ i ]; + if ( r->depletioncount != g->GetDepletionCount() && !pFilter->IsValidResponse( r->GetType(), r->value ) ) + { + m_FakedDepletes.AddToTail( i ); + g->MarkResponseUsed( i ); + } + } + } + + // Fake depletion of choices that fail the odds check + for ( int i = 0; i < c; i++ ) + { + ParserResponse *r = &g->group[ i ]; + if ( RandomInt( 1, 100 ) > r->params.odds ) + { + m_FakedDepletes.AddToTail( i ); + g->MarkResponseUsed( i ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Restore responses that were faked as being depleted +//----------------------------------------------------------------------------- +void CResponseSystem::RevertFakedDepletes( ResponseGroup *g ) +{ + for ( int i = 0; i < m_FakedDepletes.Count(); i++ ) + { + g->group[ m_FakedDepletes[ i ] ].depletioncount = 0; + } + m_FakedDepletes.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *g - +// Output : int +//----------------------------------------------------------------------------- +int CResponseSystem::SelectWeightedResponseFromResponseGroup( ResponseGroup *g, IResponseFilter *pFilter ) +{ + int c = g->group.Count(); + if ( !c ) + { + Assert( !"Expecting response group with >= 1 elements" ); + return -1; + } + + FakeDepletes( g, pFilter ); + + if ( !g->HasUndepletedChoices() ) + { + g->ResetDepletionCount(); + + FakeDepletes( g, pFilter ); + + if ( !g->HasUndepletedChoices() ) + return -1; + + // Disable the group if we looped through all the way + if ( g->IsNoRepeat() ) + { + g->SetEnabled( false ); +#ifdef MAPBASE + DisableEmptyRules(); +#endif + return -1; + } + } + + bool checkrepeats = g->ShouldCheckRepeats(); + int depletioncount = g->GetDepletionCount(); + + float totalweight = 0.0f; + int slot = -1; + + if ( checkrepeats ) + { + int check= -1; + // Snag the first slot right away + if ( g->HasUndepletedFirst( check ) && check != -1 ) + { + slot = check; + } + + if ( slot == -1 && g->HasUndepletedLast( check ) && check != -1 ) + { + // If this is the only undepleted one, use it now + int i; + for ( i = 0; i < c; i++ ) + { + ParserResponse *r = &g->group[ i ]; + if ( checkrepeats && + ( r->depletioncount == depletioncount ) ) + { + continue; + } + + if ( r->last ) + { + Assert( i == check ); + continue; + } + + // There's still another undepleted entry + break; + } + + // No more undepleted so use the r->last slot + if ( i >= c ) + { + slot = check; + } + } + } + + if ( slot == -1 ) + { + for ( int i = 0; i < c; i++ ) + { + ParserResponse *r = &g->group[ i ]; + if ( checkrepeats && + ( r->depletioncount == depletioncount ) ) + { + continue; + } + + // Always skip last entry here since we will deal with it above + if ( checkrepeats && r->last ) + continue; + + int prevSlot = slot; + + if ( !totalweight ) + { + slot = i; + } + + // Always assume very first slot will match + totalweight += r->weight.GetFloat(); + if ( !totalweight || IEngineEmulator::Get()->GetRandomStream()->RandomFloat(0,totalweight) < r->weight.GetFloat() ) + { + slot = i; + } + + if ( !checkrepeats && slot != prevSlot && pFilter && !pFilter->IsValidResponse( r->GetType(), r->value ) ) + { + slot = prevSlot; + totalweight -= r->weight.GetFloat(); + } + } + } + + if ( slot != -1 ) + { +#ifdef MAPBASE + // Don't mark responses as used in prospective mode + if (m_bInProspective == false) +#endif + g->MarkResponseUsed( slot ); + } + + // Revert fake depletion of unavailable choices + RevertFakedDepletes( g ); + + return slot; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : searchResult - +// depth - +// *name - +// verbose - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CResponseSystem::ResolveResponse( ResponseSearchResult& searchResult, int depth, const char *name, bool verbose /*= false*/, IResponseFilter *pFilter ) +{ + int responseIndex = m_Responses.Find( name ); + if ( responseIndex == m_Responses.InvalidIndex() ) + return false; + + ResponseGroup *g = &m_Responses[ responseIndex ]; + // Group has been disabled + if ( !g->IsEnabled() ) + return false; + + int c = g->group.Count(); + if ( !c ) + return false; + + int idx = 0; + + if ( g->IsSequential() ) + { + // See if next index is valid + int initialIndex = g->GetCurrentIndex(); + bool bFoundValid = false; + + do + { + idx = g->GetCurrentIndex(); + g->SetCurrentIndex( idx + 1 ); + if ( idx >= c ) + { + if ( g->IsNoRepeat() ) + { + g->SetEnabled( false ); +#ifdef MAPBASE + DisableEmptyRules(); +#endif + return false; + } + idx = 0; + g->SetCurrentIndex( 0 ); + } + + if ( !pFilter || pFilter->IsValidResponse( g->group[idx].GetType(), g->group[idx].value ) ) + { + bFoundValid = true; + break; + } + + } while ( g->GetCurrentIndex() != initialIndex ); + + if ( !bFoundValid ) + return false; + } + else + { + idx = SelectWeightedResponseFromResponseGroup( g, pFilter ); + if ( idx < 0 ) + return false; + } + + if ( verbose ) + { + DebugPrint( depth, "%s\n", m_Responses.GetElementName( responseIndex ) ); + DebugPrint( depth, "{\n" ); + DescribeResponseGroup( g, idx, depth ); + } + + bool bret = true; + + ParserResponse *result = &g->group[ idx ]; + if ( result->type == RESPONSE_RESPONSE ) + { + // Recurse + bret = ResolveResponse( searchResult, depth + 1, result->value, verbose, pFilter ); + } + else + { + searchResult.action = result; + searchResult.group = g; + } + + if( verbose ) + { + DebugPrint( depth, "}\n" ); + } + + return bret; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *group - +// selected - +// depth - +//----------------------------------------------------------------------------- +void CResponseSystem::DescribeResponseGroup( ResponseGroup *group, int selected, int depth ) +{ + int c = group->group.Count(); + + for ( int i = 0; i < c ; i++ ) + { + ParserResponse *r = &group->group[ i ]; + DebugPrint( depth + 1, "%s%20s : %40s %5.3f\n", + i == selected ? "-> " : " ", + CRR_Response::DescribeResponse( r->GetType() ), + r->value, + r->weight.GetFloat() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *rule - +// Output : CResponseSystem::Response +//----------------------------------------------------------------------------- +bool CResponseSystem::GetBestResponse( ResponseSearchResult& searchResult, Rule *rule, bool verbose /*=false*/, IResponseFilter *pFilter ) +{ + int c = rule->m_Responses.Count(); + if ( !c ) + return false; + + int index = IEngineEmulator::Get()->GetRandomStream()->RandomInt( 0, c - 1 ); + int groupIndex = rule->m_Responses[ index ]; + + ResponseGroup *g = &m_Responses[ groupIndex ]; + + // Group has been disabled + if ( !g->IsEnabled() ) + return false; + + int count = g->group.Count(); + if ( !count ) + return false; + + int responseIndex = 0; + + if ( g->IsSequential() ) + { + // See if next index is valid + int initialIndex = g->GetCurrentIndex(); + bool bFoundValid = false; + + do + { + responseIndex = g->GetCurrentIndex(); + g->SetCurrentIndex( responseIndex + 1 ); + if ( responseIndex >= count ) + { + if ( g->IsNoRepeat() ) + { + g->SetEnabled( false ); +#ifdef MAPBASE + DisableEmptyRules(); +#endif + return false; + } + responseIndex = 0; + g->SetCurrentIndex( 0 ); + } + + if ( !pFilter || pFilter->IsValidResponse( g->group[responseIndex].GetType(), g->group[responseIndex].value ) ) + { + bFoundValid = true; + break; + } + + } while ( g->GetCurrentIndex() != initialIndex ); + + if ( !bFoundValid ) + return false; + } + else + { + responseIndex = SelectWeightedResponseFromResponseGroup( g, pFilter ); + if ( responseIndex < 0 ) + return false; + } + + + ParserResponse *r = &g->group[ responseIndex ]; + + int depth = 0; + + if ( verbose ) + { + DebugPrint( depth, "%s\n", m_Responses.GetElementName( groupIndex ) ); + DebugPrint( depth, "{\n" ); + + DescribeResponseGroup( g, responseIndex, depth ); + } + + bool bret = true; + + if ( r->type == RESPONSE_RESPONSE ) + { + bret = ResolveResponse( searchResult, depth + 1, r->value, verbose, pFilter ); + } + else + { + searchResult.action = r; + searchResult.group = g; + } + + if ( verbose ) + { + DebugPrint( depth, "}\n" ); + } + + return bret; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : set - +// verbose - +// Output : int +// Warning: If you change this, be sure to also change +// ResponseSystemImplementationCLI::FindAllRulesMatchingCriteria(). +//----------------------------------------------------------------------------- +ResponseRulePartition::tIndex CResponseSystem::FindBestMatchingRule( const CriteriaSet& set, bool verbose, float &scoreOfBestMatchingRule ) +{ + CUtlVector< ResponseRulePartition::tIndex > bestrules(16,4); + float bestscore = 0.001f; + scoreOfBestMatchingRule = 0; + + CUtlVectorFixed< ResponseRulePartition::tRuleDict *, 2 > buckets( 0, 2 ); + m_RulePartitions.GetDictsForCriteria( &buckets, set ); + for ( int b = 0 ; b < buckets.Count() ; ++b ) + { + ResponseRulePartition::tRuleDict *prules = buckets[b]; + int c = prules->Count(); + int i; + for ( i = 0; i < c; i++ ) + { + float score = ScoreCriteriaAgainstRule( set, *prules, i, verbose ); + // Check equals so that we keep track of all matching rules + if ( score >= bestscore ) + { + // Reset bucket + if( score != bestscore ) + { + bestscore = score; + bestrules.RemoveAll(); + } + + // Add to bucket + bestrules.AddToTail( m_RulePartitions.IndexFromDictElem( prules, i ) ); + } + } + } + + int bestCount = bestrules.Count(); + if ( bestCount <= 0 ) + return m_RulePartitions.InvalidIdx(); + + scoreOfBestMatchingRule = bestscore ; + if ( bestCount == 1 ) + { + return bestrules[ 0 ] ; + } + else + { + // Randomly pick one of the tied matching rules + int idx = IEngineEmulator::Get()->GetRandomStream()->RandomInt( 0, bestCount - 1 ); + if ( verbose ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "Found %i matching rules, selecting slot %i\n", bestCount, idx ); + } + return bestrules[ idx ] ; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : set - +// Output : CRR_Response +//----------------------------------------------------------------------------- +bool CResponseSystem::FindBestResponse( const CriteriaSet& set, CRR_Response& response, IResponseFilter *pFilter ) +{ + bool valid = false; + + int iDbgResponse = rr_debugresponses.GetInt(); + bool showRules = ( iDbgResponse >= 2 && iDbgResponse < RR_DEBUGRESPONSES_SPECIALCASE ); + bool showResult = ( iDbgResponse >= 1 && iDbgResponse < RR_DEBUGRESPONSES_SPECIALCASE ); + + // Look for match. verbose mode used to be at level 2, but disabled because the writers don't actually care for that info. + float scoreOfBestRule; + ResponseRulePartition::tIndex bestRule = FindBestMatchingRule( set, + ( iDbgResponse >= 3 && iDbgResponse < RR_DEBUGRESPONSES_SPECIALCASE ), + scoreOfBestRule ); + + ResponseType_t responseType = RESPONSE_NONE; + ResponseParams rp; + + char ruleName[ 128 ]; + char responseName[ 128 ]; + const char *context; +#ifdef MAPBASE + int contextflags; +#else + bool bcontexttoworld; +#endif + ruleName[ 0 ] = 0; + responseName[ 0 ] = 0; + context = NULL; +#ifdef MAPBASE + contextflags = 0; +#else + bcontexttoworld = false; +#endif + if ( m_RulePartitions.IsValid( bestRule ) ) + { + Rule * RESTRICT r = &m_RulePartitions[ bestRule ]; + + ResponseSearchResult result; + if ( GetBestResponse( result, r, showResult, pFilter ) ) + { + Q_strncpy( responseName, result.action->value, sizeof( responseName ) ); + responseType = result.action->GetType(); + rp = result.action->params; + rp.m_pFollowup = &result.action->m_followup; + } + + Q_strncpy( ruleName, m_RulePartitions.GetElementName( bestRule ), sizeof( ruleName ) ); + + // Disable the rule if it only allows for matching one time + if ( r->IsMatchOnce() ) + { + r->Disable(); + } + context = r->GetContext(); +#ifdef MAPBASE + contextflags = r->GetContextFlags(); + + // Sets the internal indices for the response to call back to later for prospective responses + // (NOTE: Performance not tested; Be wary of turning off the m_bInProspective check!) + if (m_bInProspective) + { + for ( int i = 0; i < (int)m_Responses.Count(); i++ ) + { + if (&m_Responses[i] == result.group) + { + ResponseGroup &group = m_Responses[i]; + for ( int j = 0; j < group.group.Count(); j++) + { + if (&group.group[j] == result.action) + { + response.SetInternalIndices( i, j ); + } + } + } + } + } +#else + bcontexttoworld = r->IsApplyContextToWorld(); +#endif + + response.SetMatchScore(scoreOfBestRule); + valid = true; + } + +#ifdef MAPBASE + response.Init( responseType, responseName, rp, ruleName, context, contextflags ); +#else + response.Init( responseType, responseName, rp, ruleName, context, bcontexttoworld ); +#endif + + if ( showResult ) + { + /* + // clipped -- chet doesn't really want this info + if ( valid ) + { + // Rescore the winner and dump to console + ScoreCriteriaAgainstRule( set, bestRule, true ); + } + */ + + + if ( valid || showRules ) + { + const char *pConceptFilter = rr_debugresponseconcept.GetString(); + // Describe the response, too + if ( V_strlen(pConceptFilter) > 0 && !rr_debugresponseconcept.GetBool() ) + { // filter for only one concept + if ( V_stricmp(pConceptFilter, set.GetValue(set.FindCriterionIndex("concept")) ) == 0 ) + { + response.Describe(&set); + } // else don't print + } + else + { + // maybe we need to filter *out* some concepts + if ( m_DebugExcludeList.IsValidIndex( m_DebugExcludeList.Head() ) ) + { + // we are excluding at least one concept + CRR_Concept test( set.GetValue(set.FindCriterionIndex("concept")) ); + if ( ! m_DebugExcludeList.IsValidIndex( m_DebugExcludeList.Find( test ) ) ) + { // if not found in exclude list, then print + response.Describe(&set); + } + } + else + { + // describe everything + response.Describe(&set); + } + } + } + } + + return valid; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CResponseSystem::GetAllResponses( CUtlVector *pResponses ) +{ + for ( int i = 0; i < (int)m_Responses.Count(); i++ ) + { + ResponseGroup &group = m_Responses[i]; + + for ( int j = 0; j < group.group.Count(); j++) + { + ParserResponse &response = group.group[j]; + if ( response.type != RESPONSE_RESPONSE ) + { + /* + CRR_Response *pResponse = new CRR_Response; + pResponse->Init( response.GetType(), response.value, CriteriaSet(), response.params, NULL, NULL, false ); + pResponses->AddToTail(pResponse); + */ + pResponses->Element(pResponses->AddToTail()).Init( response.GetType(), response.value, response.params, NULL, NULL, false ); + } + } + } +} + +#ifdef MAPBASE +void CResponseSystem::MarkResponseAsUsed( short iGroup, short iWithinGroup ) +{ + if (m_Responses.Count() > (unsigned int)iGroup) + { + ResponseGroup &group = m_Responses[iGroup]; + if (group.group.Count() > (int)iWithinGroup) + { + group.MarkResponseUsed( iWithinGroup ); + + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "Marked response %s (%i) used\n", group.group[iWithinGroup].value, iWithinGroup ); + } + } +} +#endif + +void CResponseSystem::ParseInclude() +{ + char includefile[ 256 ]; + ParseToken(); + +#ifdef MAPBASE + char scriptfile[256]; + GetCurrentScript( scriptfile, sizeof( scriptfile ) ); + + // Gets first path + // (for example, an #include from a file in resource/script/resp will return resource) + size_t len = strlen(scriptfile)-1; + for (size_t i = 0; i < len; i++) + { + if (scriptfile[i] == CORRECT_PATH_SEPARATOR || scriptfile[i] == INCORRECT_PATH_SEPARATOR) + { + len = i; + } + } + Q_strncpy(includefile, scriptfile, len+1); + + if (len+1 != strlen(scriptfile)) + { + Q_snprintf(includefile, sizeof(includefile), "%s/%s", includefile, token); + } + else + includefile[0] = '\0'; + + if (!includefile[0]) + Q_snprintf( includefile, sizeof( includefile ), "scripts/%s", token ); +#else + Q_snprintf( includefile, sizeof( includefile ), "scripts/%s", token ); +#endif + + // check if the file is already included + if ( m_IncludedFiles.Find( includefile ) != NULL ) + { + return; + } + + MEM_ALLOC_CREDIT(); + + // Try and load it + CUtlBuffer buf; + if ( !IEngineEmulator::Get()->GetFilesystem()->ReadFile( includefile, "GAME", buf ) ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "Unable to load #included script %s\n", includefile ); + return; + } + + LoadFromBuffer( includefile, (const char *)buf.PeekGet() ); +} + +void CResponseSystem::LoadFromBuffer( const char *scriptfile, const char *buffer ) +{ + COM_TimestampedLog( "CResponseSystem::LoadFromBuffer [%s] - Start", scriptfile ); + m_IncludedFiles.Allocate( scriptfile ); + PushScript( scriptfile, (unsigned char * )buffer ); + + if( rr_dumpresponses.GetBool() ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM,"Reading: %s\n", scriptfile ); + } + + while ( 1 ) + { + ParseToken(); + if ( !token[0] ) + { + break; + } + + unsigned int hash = RR_HASH( token ); + bool bSuccess = Dispatch( token, hash, m_FileDispatch ); + if ( !bSuccess ) + { + int byteoffset = m_ScriptStack[ 0 ].currenttoken - (const char *)m_ScriptStack[ 0 ].buffer; + + Error( "CResponseSystem::LoadFromBuffer: Unknown entry type '%s', expecting 'response', 'criterion', 'enumeration' or 'rules' in file %s(offset:%i)\n", + token, scriptfile, byteoffset ); + break; + } + } + + if ( m_ScriptStack.Count() == 1 ) + { + char cur[ 256 ]; + GetCurrentScript( cur, sizeof( cur ) ); + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "CResponseSystem: %s (%i rules, %i criteria, and %i responses)\n", + cur, m_RulePartitions.Count(), m_Criteria.Count(), m_Responses.Count() ); + + if( rr_dumpresponses.GetBool() ) + { + DumpRules(); + } + } + + PopScript(); + COM_TimestampedLog( "CResponseSystem::LoadFromBuffer [%s] - Finish", scriptfile ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::LoadRuleSet( const char *basescript ) +{ + float flStart = Plat_FloatTime(); + int length = 0; + unsigned char *buffer = (unsigned char *)IEngineEmulator::Get()->LoadFileForMe( basescript, &length ); + if ( length <= 0 || !buffer ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "CResponseSystem: failed to load %s\n", basescript ); + return; + } + + m_IncludedFiles.FreeAll(); + LoadFromBuffer( basescript, (const char *)buffer ); + + IEngineEmulator::Get()->FreeFile( buffer ); + + Assert( m_ScriptStack.Count() == 0 ); + float flEnd = Plat_FloatTime(); + COM_TimestampedLog( "CResponseSystem::LoadRuleSet took %f msec", 1000.0f * ( flEnd - flStart ) ); +} + +inline ResponseType_t ComputeResponseType( const char *s ) +{ + switch ( s[ 0 ] ) + { + default: + break; + case 's': + switch ( s[ 1 ] ) + { + default: + break; + case 'c': + return RESPONSE_SCENE; + case 'e': + return RESPONSE_SENTENCE; + case 'p': + return RESPONSE_SPEAK; + } + break; + case 'r': + return RESPONSE_RESPONSE; + case 'p': + return RESPONSE_PRINT; + case 'e': + return RESPONSE_ENTITYIO; +#ifdef MAPBASE + case 'v': + if (*(s + 7) == '_') + return RESPONSE_VSCRIPT_FILE; + else + return RESPONSE_VSCRIPT; +#endif + } + + return RESPONSE_NONE; +} + +void CResponseSystem::ParseResponse_Weight( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + ParseToken(); + newResponse.weight.SetFloat( (float)atof( token ) ); +} + +void CResponseSystem::ParseResponse_PreDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + ParseToken(); + rp->flags |= AI_ResponseParams::RG_DELAYBEFORESPEAK; + rp->predelay.FromInterval( ReadInterval( token ) ); +} + +void CResponseSystem::ParseResponse_NoDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + ParseToken(); + rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + rp->delay.start = 0; + rp->delay.range = 0; +} + +void CResponseSystem::ParseResponse_DefaultDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + rp->delay.start = AIS_DEF_MIN_DELAY; + rp->delay.range = ( AIS_DEF_MAX_DELAY - AIS_DEF_MIN_DELAY ); +} + +void CResponseSystem::ParseResponse_Delay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + ParseToken(); + rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + rp->delay.FromInterval( ReadInterval( token ) ); +} + +void CResponseSystem::ParseResponse_SpeakOnce( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + rp->flags |= AI_ResponseParams::RG_SPEAKONCE; +} + +void CResponseSystem::ParseResponse_NoScene( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + rp->flags |= AI_ResponseParams::RG_DONT_USE_SCENE; +} + +void CResponseSystem::ParseResponse_StopOnNonIdle( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + rp->flags |= AI_ResponseParams::RG_STOP_ON_NONIDLE; +} + +void CResponseSystem::ParseResponse_Odds( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + ParseToken(); + rp->flags |= AI_ResponseParams::RG_ODDS; + rp->odds = clamp( atoi( token ), 0, 100 ); +} + +void CResponseSystem::ParseResponse_RespeakDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + ParseToken(); + rp->flags |= AI_ResponseParams::RG_RESPEAKDELAY; + rp->respeakdelay.FromInterval( ReadInterval( token ) ); +} + +void CResponseSystem::ParseResponse_WeaponDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + ParseToken(); + rp->flags |= AI_ResponseParams::RG_WEAPONDELAY; + rp->weapondelay.FromInterval( ReadInterval( token ) ); +} + +void CResponseSystem::ParseResponse_Soundlevel( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + ParseToken(); + rp->flags |= AI_ResponseParams::RG_SOUNDLEVEL; + rp->soundlevel = (soundlevel_t)TextToSoundLevel( token ); +} + +void CResponseSystem::ParseResponse_DisplayFirst( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + newResponse.first = true; + group.m_bHasFirst = true; +} + +void CResponseSystem::ParseResponse_DisplayLast( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + newResponse.last = true; + group.m_bHasLast= true; +} + +void CResponseSystem::ParseResponse_Fire( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + // get target name + bool bSuc = ParseToken(); + if (!bSuc) + { + ResponseWarning( "FIRE token in response needs exactly three parameters." ); + return; + } + newResponse.m_followup.followup_entityiotarget = ResponseCopyString(token); + + bSuc = ParseToken(); + if (!bSuc) + { + ResponseWarning( "FIRE token in response needs exactly three parameters." ); + return; + } + newResponse.m_followup.followup_entityioinput = ResponseCopyString(token); + + bSuc = ParseToken(); + if (!bSuc) + { + ResponseWarning( "FIRE token in response needs exactly three parameters." ); + return; + } + newResponse.m_followup.followup_entityiodelay = atof( token ); + /* + m_followup.followup_entityioinput = ResponseCopyString(src.m_followup.followup_entityioinput); + m_followup.followup_entityiotarget = ResponseCopyString(src.m_followup.followup_entityiotarget); + */ +} + +void CResponseSystem::ParseResponse_Then( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + // eg, "subject TALK_ANSWER saidunplant:1 3" + bool bSuc = ParseToken(); + if (!bSuc) + { + AssertMsg(false, "THEN token in response lacked any further info.\n"); + ResponseWarning( "THEN token in response lacked any further info.\n" ); + return; + } + + newResponse.m_followup.followup_target = ResponseCopyString(token); + + bSuc = ParseToken(); // get another token + if (!bSuc) + { + AssertMsg1(false, "THEN token in response had a target '%s', but lacked any further info.\n", newResponse.m_followup.followup_target ); + ResponseWarning( "THEN token in response had a target '%s', but lacked any further info.\n", newResponse.m_followup.followup_target ); + return; + } + + newResponse.m_followup.followup_concept = ResponseCopyString( token ); + + + // Okay, this is totally asinine. + // Because the ParseToken() function will split foo:bar into three tokens + // (which is reasonable), but we have no safe way to parse the file otherwise + // because it's all behind an engine interface, it's necessary to parse all + // the tokens to the end of the line and catenate them, except for the last one + // which is the delay. That's crap. + bSuc = ParseToken(); + if (!bSuc) + { + AssertMsg(false, "THEN token in response lacked contexts.\n"); + ResponseWarning( "THEN token in response lacked contexts.\n" ); + return; + } + + // okay, as long as there is at least one more token, catenate the ones we + // see onto a temporary buffer. When we're down to the last token, that is + // the delay. + char buf[4096]; + buf[0] = '\0'; + while ( TokenWaiting() ) + { + Q_strncat( buf, token, 4096 ); + bSuc = ParseToken(); + AssertMsg(bSuc, "Token parsing mysteriously failed."); + } + + // down here, token is the last token, and buf is everything up to there. + newResponse.m_followup.followup_contexts = ResponseCopyString( buf ); + + newResponse.m_followup.followup_delay = atof( token ); +} + +void CResponseSystem::ParseOneResponse( const char *responseGroupName, ResponseGroup& group, ResponseParams *defaultParams ) +{ + ParserResponse &newResponse = group.group[ group.group.AddToTail() ]; + newResponse.weight.SetFloat( 1.0f ); + // inherit from group if appropriate + if (defaultParams) + { + newResponse.params = *defaultParams; + } + + ResponseParams *rp = &newResponse.params; + + newResponse.type = ComputeResponseType( token ); + if ( RESPONSE_NONE == newResponse.type ) +{ + ResponseWarning( "response entry '%s' with unknown response type '%s'\n", responseGroupName, token ); + return; +} + +#ifdef MAPBASE + // HACKHACK: Some response system usage in the pre-Alien Swarm system require response names to preserve casing or even have escaped quotes. + ParseTokenIntact(); +#else + ParseToken(); +#endif + newResponse.value = ResponseCopyString( token ); + + while ( TokenWaiting() ) + { + ParseToken(); + + unsigned int hash = RR_HASH( token ); + if ( DispatchParseResponse( token, hash, m_ResponseDispatch, newResponse, group, rp ) ) + { + continue; + } + + ResponseWarning( "response entry '%s' with unknown command '%s'\n", responseGroupName, token ); + } + +} + +void CResponseSystem::ParseResponseGroup_Start( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + while ( 1 ) + { + ParseToken(); + if ( !Q_stricmp( token, "}" ) ) + break; + + if ( !Q_stricmp( token, "permitrepeats" ) ) + { + newGroup.m_bDepleteBeforeRepeat = false; + continue; + } + else if ( !Q_stricmp( token, "sequential" ) ) + { + newGroup.SetSequential( true ); + continue; + } + else if ( !Q_stricmp( token, "norepeat" ) ) + { + newGroup.SetNoRepeat( true ); + continue; + } + + ParseOneResponse( responseGroupName, newGroup, &groupResponseParams ); + } + } + +void CResponseSystem::ParseResponseGroup_PreDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + ParseToken(); + groupResponseParams.flags |= AI_ResponseParams::RG_DELAYBEFORESPEAK; + groupResponseParams.predelay.FromInterval( ReadInterval( token ) ); + } + +void CResponseSystem::ParseResponseGroup_NoDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + ParseToken(); + groupResponseParams.flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + groupResponseParams.delay.start = 0; + groupResponseParams.delay.range = 0; + } + +void CResponseSystem::ParseResponseGroup_DefaultDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + groupResponseParams.flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + groupResponseParams.delay.start = AIS_DEF_MIN_DELAY; + groupResponseParams.delay.range = ( AIS_DEF_MAX_DELAY - AIS_DEF_MIN_DELAY ); + } + +void CResponseSystem::ParseResponseGroup_Delay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + ParseToken(); + groupResponseParams.flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + groupResponseParams.delay.FromInterval( ReadInterval( token ) ); + } + +void CResponseSystem::ParseResponseGroup_SpeakOnce( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + groupResponseParams.flags |= AI_ResponseParams::RG_SPEAKONCE; + } + +void CResponseSystem::ParseResponseGroup_NoScene( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + groupResponseParams.flags |= AI_ResponseParams::RG_DONT_USE_SCENE; + } + +void CResponseSystem::ParseResponseGroup_StopOnNonIdle( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + groupResponseParams.flags |= AI_ResponseParams::RG_STOP_ON_NONIDLE; + } + +void CResponseSystem::ParseResponseGroup_Odds( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + ParseToken(); + groupResponseParams.flags |= AI_ResponseParams::RG_ODDS; + groupResponseParams.odds = clamp( atoi( token ), 0, 100 ); + } + +void CResponseSystem::ParseResponseGroup_RespeakDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + ParseToken(); + groupResponseParams.flags |= AI_ResponseParams::RG_RESPEAKDELAY; + groupResponseParams.respeakdelay.FromInterval( ReadInterval( token ) ); + } + +void CResponseSystem::ParseResponseGroup_WeaponDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + ParseToken(); + groupResponseParams.flags |= AI_ResponseParams::RG_WEAPONDELAY; + groupResponseParams.weapondelay.FromInterval( ReadInterval( token ) ); + } + +void CResponseSystem::ParseResponseGroup_Soundlevel( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + ParseToken(); + groupResponseParams.flags |= AI_ResponseParams::RG_SOUNDLEVEL; + groupResponseParams.soundlevel = (soundlevel_t)TextToSoundLevel( token ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::ParseResponse( void ) +{ + AI_ResponseParams groupResponseParams; // default response parameters inherited from single line format for group + + // Should have groupname at start + ParseToken(); + char responseGroupName[ 128 ]; + Q_strncpy( responseGroupName, token, sizeof( responseGroupName ) ); + + int slot = m_Responses.Insert( responseGroupName ); + ResponseGroup &newGroup = m_Responses[ slot ]; + + while ( 1 ) + { + ParseToken(); + + unsigned int hash = RR_HASH( token ); + + // Oops, part of next definition + if( IsRootCommand( hash ) ) + { + Unget(); + break; + } + + if ( DispatchParseResponseGroup( token, hash, m_ResponseGroupDispatch, responseGroupName, newGroup, groupResponseParams ) ) + { + continue; + } + + ParseOneResponse( responseGroupName, newGroup, &groupResponseParams ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *criterion - +//----------------------------------------------------------------------------- +int CResponseSystem::ParseOneCriterion( const char *criterionName ) +{ + char key[ 128 ]; + char value[ 128 ]; + + Criteria *pNewCriterion = NULL; + + int idx; +#ifdef MAPBASE + short existing = m_Criteria.Find( criterionName ); + if ( existing != m_Criteria.InvalidIndex() ) + { + //ResponseWarning( "Additional definition for criteria '%s', overwriting\n", criterionName ); + m_Criteria[existing] = Criteria(); + m_Criteria.SetElementName(existing, criterionName); + idx = existing; + pNewCriterion = &m_Criteria[ idx ]; + } +#else + if ( m_Criteria.Find( criterionName ) != m_Criteria.InvalidIndex() ) + { + static Criteria dummy; + pNewCriterion = &dummy; + + ResponseWarning( "Multiple definitions for criteria '%s' [%d]\n", criterionName, RR_HASH( criterionName ) ); + idx = m_Criteria.InvalidIndex(); + } +#endif + else + { + idx = m_Criteria.Insert( criterionName ); + pNewCriterion = &m_Criteria[ idx ]; + } + + bool gotbody = false; + + while ( TokenWaiting() || !gotbody ) + { + ParseToken(); + + // Oops, part of next definition + if( IsRootCommand() ) + { + Unget(); + break; + } + + if ( !Q_stricmp( token, "{" ) ) + { + gotbody = true; + + while ( 1 ) + { + ParseToken(); + if ( !Q_stricmp( token, "}" ) ) + break; + + // Look up subcriteria index + int idx = m_Criteria.Find( token ); + if ( idx != m_Criteria.InvalidIndex() ) + { + pNewCriterion->subcriteria.AddToTail( idx ); + } + else + { + ResponseWarning( "Skipping unrecongized subcriterion '%s' in '%s'\n", token, criterionName ); + } + } + continue; + } + else if ( !Q_stricmp( token, "required" ) ) + { + pNewCriterion->required = true; + } + else if ( !Q_stricmp( token, "weight" ) ) + { + ParseToken(); + pNewCriterion->weight.SetFloat( (float)atof( token ) ); + } + else + { + Assert( pNewCriterion->subcriteria.Count() == 0 ); + + // Assume it's the math info for a non-subcriteria resposne + Q_strncpy( key, token, sizeof( key ) ); + ParseToken(); + Q_strncpy( value, token, sizeof( value ) ); + + V_strlower( key ); + pNewCriterion->nameSym = CriteriaSet::ComputeCriteriaSymbol( key ); + pNewCriterion->value = ResponseCopyString( value ); + + gotbody = true; + } + } + + if ( !pNewCriterion->IsSubCriteriaType() ) + { + ComputeMatcher( pNewCriterion, pNewCriterion->matcher ); + } + + return idx; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *kv - +//----------------------------------------------------------------------------- +void CResponseSystem::ParseCriterion( void ) +{ + // Should have groupname at start + char criterionName[ 128 ]; + ParseToken(); + Q_strncpy( criterionName, token, sizeof( criterionName ) ); + + ParseOneCriterion( criterionName ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *kv - +//----------------------------------------------------------------------------- +void CResponseSystem::ParseEnumeration( void ) +{ + char enumerationName[ 128 ]; + ParseToken(); + Q_strncpy( enumerationName, token, sizeof( enumerationName ) ); + + ParseToken(); + if ( Q_stricmp( token, "{" ) ) + { + ResponseWarning( "Expecting '{' in enumeration '%s', got '%s'\n", enumerationName, token ); + return; + } + + while ( 1 ) + { + ParseToken(); + if ( !Q_stricmp( token, "}" ) ) + break; + + if ( Q_strlen( token ) <= 0 ) + { + ResponseWarning( "Expecting more tokens in enumeration '%s'\n", enumerationName ); + break; + } + + char key[ 128 ]; + + Q_strncpy( key, token, sizeof( key ) ); + ParseToken(); + float value = (float)atof( token ); + + char sz[ 128 ]; + Q_snprintf( sz, sizeof( sz ), "[%s::%s]", enumerationName, key ); + Q_strlower( sz ); + + Enumeration newEnum; + newEnum.value = value; + + if ( m_Enumerations.Find( sz ) == m_Enumerations.InvalidIndex() ) + { + m_Enumerations.Insert( sz, newEnum ); + } + /* + else + { + ResponseWarning( "Ignoring duplication enumeration '%s'\n", sz ); + } + */ + } +} + +void CResponseSystem::ParseRule_MatchOnce( Rule &newRule ) + { + newRule.m_bMatchOnce = true; + } + +#ifdef MAPBASE +void CResponseSystem::ParseRule_ApplyContextToWorld( Rule &newRule ) + { + newRule.m_iContextFlags |= APPLYCONTEXT_WORLD; + } + +void CResponseSystem::ParseRule_ApplyContextToSquad( Rule &newRule ) + { + newRule.m_iContextFlags |= APPLYCONTEXT_SQUAD; + } + +void CResponseSystem::ParseRule_ApplyContextToEnemy( Rule &newRule ) + { + newRule.m_iContextFlags |= APPLYCONTEXT_ENEMY; + } +#else +void CResponseSystem::ParseRule_ApplyContextToWorld( Rule &newRule ) + { + newRule.m_bApplyContextToWorld = true; + } +#endif + +void CResponseSystem::ParseRule_ApplyContext( Rule &newRule ) + { + ParseToken(); + if ( newRule.GetContext() == NULL ) + { + newRule.SetContext( token ); + } + else + { + CFmtStrN<1024> newContext( "%s,%s", newRule.GetContext(), token ); + newRule.SetContext( newContext ); + } + } + +void CResponseSystem::ParseRule_Response( Rule &newRule ) + { + // Read them until we run out. + while ( TokenWaiting() ) + { + ParseToken(); + int idx = m_Responses.Find( token ); + if ( idx != m_Responses.InvalidIndex() ) + { + MEM_ALLOC_CREDIT(); + newRule.m_Responses.AddToTail( idx ); + } + else + { + m_bParseRuleValid = false; + ResponseWarning( "No such response '%s' for rule '%s'\n", token, m_pParseRuleName ); + } + } +} + +/* +void CResponseSystem::ParseRule_ForceWeight( Rule &newRule ) +{ + ParseToken(); + if ( token[0] == 0 ) + { + // no token followed forceweight? + ResponseWarning( "Forceweight token in rule '%s' did not specify a numerical weight! Ignoring.\n", m_pParseRuleName ); + } + else + { + newRule.m_nForceWeight = atoi(token); + if ( newRule.m_nForceWeight == 0 ) + { + ResponseWarning( "Rule '%s' had forceweight '%s', which doesn't work out to a nonzero number. Ignoring.\n", + m_pParseRuleName, token ); + } + } + } +*/ + +void CResponseSystem::ParseRule_Criteria( Rule &newRule ) + { + // Read them until we run out. + while ( TokenWaiting() ) + { + ParseToken(); + + int idx = m_Criteria.Find( token ); + if ( idx != m_Criteria.InvalidIndex() ) + { + MEM_ALLOC_CREDIT(); + newRule.m_Criteria.AddToTail( idx ); + } + else + { + m_bParseRuleValid = false; + ResponseWarning( "No such criterion '%s' for rule '%s'\n", token, m_pParseRuleName ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *kv - +//----------------------------------------------------------------------------- +void CResponseSystem::ParseRule( void ) +{ + static int instancedCriteria = 0; + + char ruleName[ 128 ]; + ParseToken(); + Q_strncpy( ruleName, token, sizeof( ruleName ) ); + + ParseToken(); + if ( Q_stricmp( token, "{" ) ) + { + ResponseWarning( "Expecting '{' in rule '%s', got '%s'\n", ruleName, token ); + return; + } + + // entries are "criteria", "response" or an in-line criteria to instance + Rule *newRule = new Rule; + + char sz[ 128 ]; + + m_bParseRuleValid = true; + m_pParseRuleName = ruleName; + while ( 1 ) + { + ParseToken(); + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + + if ( Q_strlen( token ) <= 0 ) + { + ResponseWarning( "Expecting more tokens in rule '%s'\n", ruleName ); + break; + } + + unsigned int hash = RR_HASH( token ); + if ( DispatchParseRule( token, hash, m_RuleDispatch, *newRule ) ) + continue; + + // It's an inline criteria, generate a name and parse it in + Q_snprintf( sz, sizeof( sz ), "[%s%03i]", ruleName, ++instancedCriteria ); + Unget(); + int idx = ParseOneCriterion( sz ); + if ( idx != m_Criteria.InvalidIndex() ) + { + newRule->m_Criteria.AddToTail( idx ); + } + } + + if ( m_bParseRuleValid ) + { + m_RulePartitions.GetDictForRule( this, newRule ).Insert( ruleName, newRule ); + } + else + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "Discarded rule %s\n", ruleName ); + delete newRule; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CResponseSystem::GetCurrentToken() const +{ + if ( m_ScriptStack.Count() <= 0 ) + return -1; + + return m_ScriptStack[ 0 ].tokencount; +} + + +void CResponseSystem::ResponseWarning( const char *fmt, ... ) +{ + va_list argptr; + char string[1024]; + + va_start (argptr, fmt); + Q_vsnprintf(string, sizeof(string), fmt,argptr); + va_end (argptr); + + char cur[ 256 ]; + GetCurrentScript( cur, sizeof( cur ) ); + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "%s(token %i) : %s", cur, GetCurrentToken(), string ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::CopyCriteriaFrom( Rule *pSrcRule, Rule *pDstRule, CResponseSystem *pCustomSystem ) +{ + // Add criteria from this rule to global list in custom response system. + int nCriteriaCount = pSrcRule->m_Criteria.Count(); + for ( int iCriteria = 0; iCriteria < nCriteriaCount; ++iCriteria ) + { + int iSrcIndex = pSrcRule->m_Criteria[iCriteria]; + Criteria *pSrcCriteria = &m_Criteria[iSrcIndex]; + if ( pSrcCriteria ) + { + int iIndex = pCustomSystem->m_Criteria.Find( m_Criteria.GetElementName( iSrcIndex ) ); + if ( iIndex != pCustomSystem->m_Criteria.InvalidIndex() ) + { + pDstRule->m_Criteria.AddToTail( iIndex ); + continue; + } + + // Add the criteria. + Criteria dstCriteria; + + dstCriteria.nameSym = pSrcCriteria->nameSym ; + dstCriteria.value = ResponseCopyString( pSrcCriteria->value ); + dstCriteria.weight = pSrcCriteria->weight; + dstCriteria.required = pSrcCriteria->required; + dstCriteria.matcher = pSrcCriteria->matcher; + + int nSubCriteriaCount = pSrcCriteria->subcriteria.Count(); + for ( int iSubCriteria = 0; iSubCriteria < nSubCriteriaCount; ++iSubCriteria ) + { + int iSrcSubIndex = pSrcCriteria->subcriteria[iSubCriteria]; + Criteria *pSrcSubCriteria = &m_Criteria[iSrcSubIndex]; + if ( pSrcCriteria ) + { + int iSubIndex = pCustomSystem->m_Criteria.Find( pSrcSubCriteria->value ); + if ( iSubIndex != pCustomSystem->m_Criteria.InvalidIndex() ) + continue; + + // Add the criteria. + Criteria dstSubCriteria; + + dstSubCriteria.nameSym = pSrcSubCriteria->nameSym ; + dstSubCriteria.value = ResponseCopyString( pSrcSubCriteria->value ); + dstSubCriteria.weight = pSrcSubCriteria->weight; + dstSubCriteria.required = pSrcSubCriteria->required; + dstSubCriteria.matcher = pSrcSubCriteria->matcher; + + int iSubInsertIndex = pCustomSystem->m_Criteria.Insert( pSrcSubCriteria->value, dstSubCriteria ); + dstCriteria.subcriteria.AddToTail( iSubInsertIndex ); + } + } + + int iInsertIndex = pCustomSystem->m_Criteria.Insert( m_Criteria.GetElementName( iSrcIndex ), dstCriteria ); + pDstRule->m_Criteria.AddToTail( iInsertIndex ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::CopyResponsesFrom( Rule *pSrcRule, Rule *pDstRule, CResponseSystem *pCustomSystem ) +{ + // Add responses from this rule to global list in custom response system. + int nResponseGroupCount = pSrcRule->m_Responses.Count(); + for ( int iResponseGroup = 0; iResponseGroup < nResponseGroupCount; ++iResponseGroup ) + { + int iSrcResponseGroup = pSrcRule->m_Responses[iResponseGroup]; + ResponseGroup *pSrcResponseGroup = &m_Responses[iSrcResponseGroup]; + if ( pSrcResponseGroup ) + { + // Add response group. + ResponseGroup dstResponseGroup; + + dstResponseGroup.m_bDepleteBeforeRepeat = pSrcResponseGroup->m_bDepleteBeforeRepeat; + dstResponseGroup.m_nDepletionCount = pSrcResponseGroup->m_nDepletionCount; + dstResponseGroup.m_bHasFirst = pSrcResponseGroup->m_bHasFirst; + dstResponseGroup.m_bHasLast = pSrcResponseGroup->m_bHasLast; + dstResponseGroup.m_bSequential = pSrcResponseGroup->m_bSequential; + dstResponseGroup.m_bNoRepeat = pSrcResponseGroup->m_bNoRepeat; + dstResponseGroup.m_bEnabled = pSrcResponseGroup->m_bEnabled; + dstResponseGroup.m_nCurrentIndex = pSrcResponseGroup->m_nCurrentIndex; + + int nSrcResponseCount = pSrcResponseGroup->group.Count(); + for ( int iResponse = 0; iResponse < nSrcResponseCount; ++iResponse ) + { + ParserResponse *pSrcResponse = &pSrcResponseGroup->group[iResponse]; + if ( pSrcResponse ) + { + // Add Response + ParserResponse dstResponse; + + dstResponse.weight = pSrcResponse->weight; + dstResponse.type = pSrcResponse->type; + dstResponse.value = ResponseCopyString( pSrcResponse->value ); + dstResponse.depletioncount = pSrcResponse->depletioncount; + dstResponse.first = pSrcResponse->first; + dstResponse.last = pSrcResponse->last; + + dstResponseGroup.group.AddToTail( dstResponse ); + } + } + + int iInsertIndex = pCustomSystem->m_Responses.Insert( m_Responses.GetElementName( iSrcResponseGroup ), dstResponseGroup ); + pDstRule->m_Responses.AddToTail( iInsertIndex ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::CopyEnumerationsFrom( CResponseSystem *pCustomSystem ) +{ + int nEnumerationCount = m_Enumerations.Count(); + for ( int iEnumeration = 0; iEnumeration < nEnumerationCount; ++iEnumeration ) + { + Enumeration *pSrcEnumeration = &m_Enumerations[iEnumeration]; + if ( pSrcEnumeration ) + { + Enumeration dstEnumeration; + dstEnumeration.value = pSrcEnumeration->value; + pCustomSystem->m_Enumerations.Insert( m_Enumerations.GetElementName( iEnumeration ), dstEnumeration ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::CopyRuleFrom( Rule *pSrcRule, ResponseRulePartition::tIndex iRule, CResponseSystem *pCustomSystem ) +{ + // Verify data. + Assert( pSrcRule ); + Assert( pCustomSystem ); + if ( !pSrcRule || !pCustomSystem ) + return; + + // New rule + Rule *dstRule = new Rule; + + dstRule->SetContext( pSrcRule->GetContext() ); + dstRule->m_bMatchOnce = pSrcRule->m_bMatchOnce; + dstRule->m_bEnabled = pSrcRule->m_bEnabled; +#ifdef MAPBASE + dstRule->m_iContextFlags = pSrcRule->m_iContextFlags; +#else + dstRule->m_bApplyContextToWorld = pSrcRule->m_bApplyContextToWorld; +#endif + + // Copy off criteria. + CopyCriteriaFrom( pSrcRule, dstRule, pCustomSystem ); + + // Copy off responses. + CopyResponsesFrom( pSrcRule, dstRule, pCustomSystem ); + + // Copy off enumerations - Don't think we use these. + // CopyEnumerationsFrom( pCustomSystem ); + + // Add rule. + pCustomSystem->m_RulePartitions.GetDictForRule( this, dstRule ).Insert( m_RulePartitions.GetElementName( iRule ), dstRule ); +} + + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CResponseSystem::DumpRules() +{ + for ( ResponseRulePartition::tIndex idx = m_RulePartitions.First() ; + m_RulePartitions.IsValid(idx) ; + idx = m_RulePartitions.Next(idx) ) + { + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "%s\n", m_RulePartitions.GetElementName( idx ) ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CResponseSystem::DumpDictionary( const char *pszName ) +{ + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "\nDictionary: %s\n", pszName ); + + // int nRuleCount = m_Rules.Count(); + // for ( int iRule = 0; iRule < nRuleCount; ++iRule ) + for ( ResponseRulePartition::tIndex idx = m_RulePartitions.First() ; + m_RulePartitions.IsValid(idx) ; + idx = m_RulePartitions.Next(idx) ) + { + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, " Rule %d/%d: %s\n", m_RulePartitions.BucketFromIdx( idx ), m_RulePartitions.PartFromIdx( idx ), m_RulePartitions.GetElementName( idx ) ); + + Rule *pRule = &m_RulePartitions[idx]; + + int nCriteriaCount = pRule->m_Criteria.Count(); + for( int iCriteria = 0; iCriteria < nCriteriaCount; ++iCriteria ) + { + int iRuleCriteria = pRule->m_Criteria[iCriteria]; + Criteria *pCriteria = &m_Criteria[iRuleCriteria]; + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, " Criteria %d: %s %s\n", iCriteria, CriteriaSet::SymbolToStr( pCriteria->nameSym ), pCriteria->value ); + } + + int nResponseGroupCount = pRule->m_Responses.Count(); + for ( int iResponseGroup = 0; iResponseGroup < nResponseGroupCount; ++iResponseGroup ) + { + int iRuleResponse = pRule->m_Responses[iResponseGroup]; + ResponseGroup *pResponseGroup = &m_Responses[iRuleResponse]; + + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, " ResponseGroup %d: %s\n", iResponseGroup, m_Responses.GetElementName( iRuleResponse ) ); + + int nResponseCount = pResponseGroup->group.Count(); + for ( int iResponse = 0; iResponse < nResponseCount; ++iResponse ) + { + ParserResponse *pResponse = &pResponseGroup->group[iResponse]; + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, " Response %d: %s\n", iResponse, pResponse->value ); + } + } + } +} + +void CResponseSystem::BuildDispatchTables() +{ + m_RootCommandHashes.Insert( RR_HASH( "#include" ) ); + m_RootCommandHashes.Insert( RR_HASH( "response" ) ); + m_RootCommandHashes.Insert( RR_HASH( "enumeration" ) ); + m_RootCommandHashes.Insert( RR_HASH( "criterion" ) ); + m_RootCommandHashes.Insert( RR_HASH( "criteria" ) ); + m_RootCommandHashes.Insert( RR_HASH( "rule" ) ); + + m_FileDispatch.Insert( RR_HASH( "#include" ), &CResponseSystem::ParseInclude ); + m_FileDispatch.Insert( RR_HASH( "response" ), &CResponseSystem::ParseResponse ); + m_FileDispatch.Insert( RR_HASH( "criterion" ), &CResponseSystem::ParseCriterion ); + m_FileDispatch.Insert( RR_HASH( "criteria" ), &CResponseSystem::ParseCriterion ); + m_FileDispatch.Insert( RR_HASH( "rule" ), &CResponseSystem::ParseRule ); + m_FileDispatch.Insert( RR_HASH( "enumeration" ), &CResponseSystem::ParseEnumeration ); + + m_RuleDispatch.Insert( RR_HASH( "matchonce" ), &CResponseSystem::ParseRule_MatchOnce ); + m_RuleDispatch.Insert( RR_HASH( "applycontexttoworld" ), &CResponseSystem::ParseRule_ApplyContextToWorld ); +#ifdef MAPBASE + m_RuleDispatch.Insert( RR_HASH( "applycontexttosquad" ), &CResponseSystem::ParseRule_ApplyContextToSquad ); + m_RuleDispatch.Insert( RR_HASH( "applycontexttoenemy" ), &CResponseSystem::ParseRule_ApplyContextToEnemy ); +#endif + m_RuleDispatch.Insert( RR_HASH( "applycontext" ), &CResponseSystem::ParseRule_ApplyContext ); + m_RuleDispatch.Insert( RR_HASH( "response" ), &CResponseSystem::ParseRule_Response ); +// m_RuleDispatch.Insert( RR_HASH( "forceweight" ), &CResponseSystem::ParseRule_ForceWeight ); + m_RuleDispatch.Insert( RR_HASH( "criteria" ), &CResponseSystem::ParseRule_Criteria ); + m_RuleDispatch.Insert( RR_HASH( "criterion" ), &CResponseSystem::ParseRule_Criteria ); + + + m_ResponseDispatch.Insert( RR_HASH( "weight" ), &CResponseSystem::ParseResponse_Weight ); + m_ResponseDispatch.Insert( RR_HASH( "predelay" ), &CResponseSystem::ParseResponse_PreDelay ); + m_ResponseDispatch.Insert( RR_HASH( "nodelay" ), &CResponseSystem::ParseResponse_NoDelay ); + m_ResponseDispatch.Insert( RR_HASH( "defaultdelay" ), &CResponseSystem::ParseResponse_DefaultDelay ); + m_ResponseDispatch.Insert( RR_HASH( "delay" ), &CResponseSystem::ParseResponse_Delay ); + m_ResponseDispatch.Insert( RR_HASH( "speakonce" ), &CResponseSystem::ParseResponse_SpeakOnce ); + m_ResponseDispatch.Insert( RR_HASH( "noscene" ), &CResponseSystem::ParseResponse_NoScene ); + m_ResponseDispatch.Insert( RR_HASH( "stop_on_nonidle" ), &CResponseSystem::ParseResponse_StopOnNonIdle ); + m_ResponseDispatch.Insert( RR_HASH( "odds" ), &CResponseSystem::ParseResponse_Odds ); + m_ResponseDispatch.Insert( RR_HASH( "respeakdelay" ), &CResponseSystem::ParseResponse_RespeakDelay ); + m_ResponseDispatch.Insert( RR_HASH( "weapondelay" ), &CResponseSystem::ParseResponse_WeaponDelay ); + m_ResponseDispatch.Insert( RR_HASH( "soundlevel" ), &CResponseSystem::ParseResponse_Soundlevel ); + m_ResponseDispatch.Insert( RR_HASH( "displayfirst" ), &CResponseSystem::ParseResponse_DisplayFirst ); + m_ResponseDispatch.Insert( RR_HASH( "displaylast" ), &CResponseSystem::ParseResponse_DisplayLast ); + m_ResponseDispatch.Insert( RR_HASH( "fire" ), &CResponseSystem::ParseResponse_Fire ); + m_ResponseDispatch.Insert( RR_HASH( "then" ), &CResponseSystem::ParseResponse_Then ); + + m_ResponseGroupDispatch.Insert( RR_HASH( "{" ), &CResponseSystem::ParseResponseGroup_Start ); + m_ResponseGroupDispatch.Insert( RR_HASH( "predelay" ), &CResponseSystem::ParseResponseGroup_PreDelay ); + m_ResponseGroupDispatch.Insert( RR_HASH( "nodelay" ), &CResponseSystem::ParseResponseGroup_NoDelay ); + m_ResponseGroupDispatch.Insert( RR_HASH( "defaultdelay" ), &CResponseSystem::ParseResponseGroup_DefaultDelay ); + m_ResponseGroupDispatch.Insert( RR_HASH( "delay" ), &CResponseSystem::ParseResponseGroup_Delay ); + m_ResponseGroupDispatch.Insert( RR_HASH( "speakonce" ), &CResponseSystem::ParseResponseGroup_SpeakOnce ); + m_ResponseGroupDispatch.Insert( RR_HASH( "noscene" ), &CResponseSystem::ParseResponseGroup_NoScene ); + m_ResponseGroupDispatch.Insert( RR_HASH( "stop_on_nonidle" ), &CResponseSystem::ParseResponseGroup_StopOnNonIdle ); + m_ResponseGroupDispatch.Insert( RR_HASH( "odds" ), &CResponseSystem::ParseResponseGroup_Odds ); + m_ResponseGroupDispatch.Insert( RR_HASH( "respeakdelay" ), &CResponseSystem::ParseResponseGroup_RespeakDelay ); + m_ResponseGroupDispatch.Insert( RR_HASH( "weapondelay" ), &CResponseSystem::ParseResponseGroup_WeaponDelay ); + m_ResponseGroupDispatch.Insert( RR_HASH( "soundlevel" ), &CResponseSystem::ParseResponseGroup_Soundlevel ); +} + +bool CResponseSystem::Dispatch( char const *pToken, unsigned int uiHash, CResponseSystem::DispatchMap_t &rMap ) +{ + int slot = rMap.Find( uiHash ); + if ( slot != rMap.InvalidIndex() ) + { + CResponseSystem::pfnResponseDispatch dispatch = rMap[ slot ]; + (this->*dispatch)(); + return true; + } + + return false; +} + +bool CResponseSystem::DispatchParseRule( char const *pToken, unsigned int uiHash, ParseRuleDispatchMap_t &rMap, Rule &newRule ) +{ + int slot = rMap.Find( uiHash ); + if ( slot != rMap.InvalidIndex() ) + { + CResponseSystem::pfnParseRuleDispatch dispatch = rMap[ slot ]; + (this->*dispatch)( newRule ); + return true; + } + + return false; +} + +bool CResponseSystem::DispatchParseResponse( char const *pToken, unsigned int uiHash, ParseResponseDispatchMap_t &rMap, ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + int slot = rMap.Find( uiHash ); + if ( slot != rMap.InvalidIndex() ) + { + CResponseSystem::pfnParseResponseDispatch dispatch = rMap[ slot ]; + (this->*dispatch)( newResponse, group, rp ); + return true; + } + + return false; +} + +bool CResponseSystem::DispatchParseResponseGroup( char const *pToken, unsigned int uiHash, ParseResponseGroupDispatchMap_t &rMap, char const *responseGroupName, ResponseGroup& newGroup, AI_ResponseParams &groupResponseParams ) +{ + int slot = rMap.Find( uiHash ); + if ( slot != rMap.InvalidIndex() ) + { + CResponseSystem::pfnParseResponseGroupDispatch dispatch = rMap[ slot ]; + (this->*dispatch)( responseGroupName, newGroup, groupResponseParams ); + return true; + } + + return false; +} + +unsigned int ResponseRulePartition::GetBucketForSpeakerAndConcept( const char *pszSpeaker, const char *pszConcept, const char *pszSubject ) +{ + // make sure is a power of two + COMPILE_TIME_ASSERT( ( N_RESPONSE_PARTITIONS & ( N_RESPONSE_PARTITIONS - 1 ) ) == 0 ); + + // hash together the speaker and concept strings, and mask off by the bucket mask + unsigned hashSpeaker = 0; // pszSpeaker ? HashStringCaseless( pszSpeaker ) : 0; + unsigned hashConcept = pszConcept ? HashStringCaseless( pszConcept ) : 0; + unsigned hashSubject = pszSubject ? HashStringCaseless( pszSubject ) : 0; + unsigned hashBrowns = ( ( hashSubject >> 3 ) ^ (hashSpeaker >> 1) ^ hashConcept ) & ( N_RESPONSE_PARTITIONS - 1 ); + return hashBrowns; +} + +const char *Rule::GetValueForRuleCriterionByName( CResponseSystem * RESTRICT pSystem, const CUtlSymbol &pCritNameSym ) +{ + const char * retval = NULL; + // for each rule criterion... + for ( int i = 0 ; i < m_Criteria.Count() ; ++i ) + { + retval = RecursiveGetValueForRuleCriterionByName( pSystem, &pSystem->m_Criteria[m_Criteria[i]], pCritNameSym ); + if ( retval != NULL ) + { + // we found a result, early out + break; + } + } + + return retval; +} + +const Criteria *Rule::GetPointerForRuleCriterionByName( CResponseSystem *pSystem, const CUtlSymbol &pCritNameSym ) +{ + const Criteria * retval = NULL; + // for each rule criterion... + for ( int i = 0 ; i < m_Criteria.Count() ; ++i ) + { + retval = RecursiveGetPointerForRuleCriterionByName( pSystem, &pSystem->m_Criteria[m_Criteria[i]], pCritNameSym ); + if ( retval != NULL ) + { + // we found a result, early out + break; + } + } + + return retval; +} + +const char *Rule::RecursiveGetValueForRuleCriterionByName( CResponseSystem * RESTRICT pSystem, + const Criteria * RESTRICT pCrit, const CUtlSymbol &pCritNameSym ) +{ + Assert( pCrit ); + if ( !pCrit ) return NULL; + if ( pCrit->IsSubCriteriaType() ) + { + // test each of the children (depth first) + const char *pRet = NULL; + for ( int i = 0 ; i < pCrit->subcriteria.Count() ; ++i ) + { + pRet = RecursiveGetValueForRuleCriterionByName( pSystem, &pSystem->m_Criteria[pCrit->subcriteria[i]], pCritNameSym ); + if ( pRet ) // if found something, early out + return pRet; + } + } + else // leaf criterion + { + if ( pCrit->nameSym == pCritNameSym ) + { + return pCrit->value; + } + else + { + return NULL; + } + } + + return NULL; +} + + +const Criteria *Rule::RecursiveGetPointerForRuleCriterionByName( CResponseSystem *pSystem, const Criteria *pCrit, const CUtlSymbol &pCritNameSym ) +{ + Assert( pCrit ); + if ( !pCrit ) return NULL; + if ( pCrit->IsSubCriteriaType() ) + { + // test each of the children (depth first) + const Criteria *pRet = NULL; + for ( int i = 0 ; i < pCrit->subcriteria.Count() ; ++i ) + { + pRet = RecursiveGetPointerForRuleCriterionByName( pSystem, &pSystem->m_Criteria[pCrit->subcriteria[i]], pCritNameSym ); + if ( pRet ) // if found something, early out + return pRet; + } + } + else // leaf criterion + { + if ( pCrit->nameSym == pCritNameSym ) + { + return pCrit; + } + else + { + return NULL; + } + } + + return NULL; +} + + +static void CC_RR_Debug_ResponseConcept_Exclude( const CCommand &args ) +{ + // shouldn't use this extern elsewhere -- it's meant to be a hidden + // implementation detail + extern CRR_ConceptSymbolTable *g_pRRConceptTable; + Assert( g_pRRConceptTable ); + if ( !g_pRRConceptTable ) return; + + + // different things for different argument lengths + switch ( args.ArgC() ) + { + case 0: + { + AssertMsg( args.ArgC() > 0, "WTF error in ccommand parsing: zero arguments!\n" ); + return; + } + case 1: + { + // print usage info + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "Usage: rr_debugresponseconcept_exclude Concept1 Concept2 Concept3...\n"); + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "\tseparate multiple concepts with spaces.\n"); + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "\tcall with no arguments to see this message and a list of current excludes.\n"); + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "\tto reset the exclude list, type \"rr_debugresponseconcept_exclude !\"\n"); + + // print current excludes + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "\nCurrent exclude list:\n" ); + if ( !CResponseSystem::m_DebugExcludeList.IsValidIndex( CResponseSystem::m_DebugExcludeList.Head() ) ) + { + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "\t\n" ); + } + else + { + CResponseSystem::ExcludeList_t::IndexLocalType_t i; + for ( i = CResponseSystem::m_DebugExcludeList.Head() ; + CResponseSystem::m_DebugExcludeList.IsValidIndex(i) ; + i = CResponseSystem::m_DebugExcludeList.Next(i) ) + { + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "\t%s\n", CResponseSystem::m_DebugExcludeList[i].GetStringConcept() ); + } + } + return; + } + case 2: + // deal with the erase operator + if ( args[1][0] == '!' ) + { + CResponseSystem::m_DebugExcludeList.Purge(); + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "Exclude list emptied.\n" ); + return; + } + // else, FALL THROUGH: + default: + // add each arg to the exclude list + for ( int i = 1 ; i < args.ArgC() ; ++i ) + { + if ( !g_pRRConceptTable->Find(args[i]).IsValid() ) + { + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "\t'%s' is not a known concept (adding it anyway)\n", args[i] ); + } + CRR_Concept concept( args[i] ); + CResponseSystem::m_DebugExcludeList.AddToTail( concept ); + } + } +} +#if RR_DUMPHASHINFO_ENABLED +void ResponseRulePartition::PrintBucketInfo( CResponseSystem *pSys ) +{ + struct bucktuple_t + { + int nBucket; + int nCount; + bucktuple_t() : nBucket(-1), nCount(-1) {}; + bucktuple_t( int bucket, int count ) : nBucket(bucket), nCount(count) {}; + + static int __cdecl SortCompare( const bucktuple_t * a, const bucktuple_t * b ) + { + return a->nCount - b->nCount; + } + }; + + CUtlVector infos( N_RESPONSE_PARTITIONS, N_RESPONSE_PARTITIONS ); + + float nAverage = 0; + for ( int i = 0 ; i < N_RESPONSE_PARTITIONS ; ++i ) + { + int count = m_RuleParts[i].Count(); + infos.AddToTail( bucktuple_t( i, count ) ); + nAverage += count; + } + nAverage /= N_RESPONSE_PARTITIONS; + infos.Sort( bucktuple_t::SortCompare ); + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "%d buckets, %d total, %.2f average size\n", N_RESPONSE_PARTITIONS, Count(), nAverage ); + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "8 shortest buckets:\n" ); + for ( int i = 0 ; i < 8 ; ++i ) + { + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "\t%d: %d\n", infos[i].nBucket, infos[i].nCount ); + } + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "8 longest buckets:\n" ); + for ( int i = infos.Count() - 1 ; i >= infos.Count() - 9 ; --i ) + { + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "\t%d: %d\n", infos[i].nBucket, infos[i].nCount ); + } + int nempty = 0; + for ( nempty = 0 ; nempty < infos.Count() ; ++nempty ) + { + if ( infos[nempty].nCount != 0 ) + break; + } + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "%d empty buckets\n", nempty ); + + /* + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, " Contents of longest bucket\nwho\tconcept\n" ); + tRuleDict &bucket = m_RuleParts[infos[infos.Count()-1].nBucket]; + for ( tRuleDict::IndexType_t i = bucket.FirstInorder(); bucket.IsValidIndex(i); i = bucket.NextInorder(i) ) + { + Rule &rule = bucket.Element(i) ; + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "%s\t%s\n", rule.GetValueForRuleCriterionByName( pSys, "who" ), rule.GetValueForRuleCriterionByName( pSys, CriteriaSet::ComputeCriteriaSymbol("concept") ) ); + } + */ +} +#endif \ No newline at end of file diff --git a/sp/src/responserules/runtime/response_system.h b/sp/src/responserules/runtime/response_system.h new file mode 100644 index 0000000000..a862d76143 --- /dev/null +++ b/sp/src/responserules/runtime/response_system.h @@ -0,0 +1,336 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: The CResponseSystem class. Don't include this header; include the response_types +// into which it is transcluded. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef RESPONSE_SYSTEM_H +#define RESPONSE_SYSTEM_H +#ifdef _WIN32 +#pragma once +#endif + +#include "utldict.h" + +namespace ResponseRules +{ + typedef ResponseParams AI_ResponseParams ; + #define AI_CriteriaSet ResponseRules::CriteriaSet + + //----------------------------------------------------------------------------- + // Purpose: The database of all available responses. + // The Rules are partitioned based on a variety of factors (presently, + // speaker and concept) for faster lookup, basically a seperate-chained hash. + //----------------------------------------------------------------------------- + class CResponseSystem : public IResponseSystem + { + public: + CResponseSystem(); + ~CResponseSystem(); + + typedef void (CResponseSystem::*pfnResponseDispatch)( void ); + typedef void (CResponseSystem::*pfnParseRuleDispatch)( Rule & ); + typedef void (CResponseSystem::*pfnParseResponseDispatch)( ParserResponse &, ResponseGroup&, AI_ResponseParams * ); + typedef void (CResponseSystem::*pfnParseResponseGroupDispatch) ( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + + typedef CUtlMap< unsigned,pfnResponseDispatch > DispatchMap_t; + typedef CUtlMap< unsigned,pfnParseRuleDispatch > ParseRuleDispatchMap_t; + typedef CUtlMap< unsigned,pfnParseResponseDispatch > ParseResponseDispatchMap_t; + typedef CUtlMap< unsigned,pfnParseResponseGroupDispatch > ParseResponseGroupDispatchMap_t; + +#pragma region IResponseSystem + // IResponseSystem + virtual bool FindBestResponse( const CriteriaSet& set, CRR_Response& response, IResponseFilter *pFilter = NULL ); + virtual void GetAllResponses( CUtlVector *pResponses ); + +#ifdef MAPBASE + virtual void SetProspective( bool bToggle ) { m_bInProspective = bToggle; } + + virtual void MarkResponseAsUsed( short iGroup, short iWithinGroup ); +#endif +#pragma endregion Implement interface from IResponseSystem + + virtual void Release() = 0; + + virtual void DumpRules(); + + bool IsCustomManagable() { return m_bCustomManagable; } + +#ifdef MAPBASE + virtual +#endif + void Clear(); + + void DumpDictionary( const char *pszName ); + + protected: + + void BuildDispatchTables(); + bool Dispatch( char const *pToken, unsigned int uiHash, DispatchMap_t &rMap ); + bool DispatchParseRule( char const *pToken, unsigned int uiHash, ParseRuleDispatchMap_t &rMap, Rule &newRule ); + bool DispatchParseResponse( char const *pToken, unsigned int uiHash, ParseResponseDispatchMap_t &rMap, ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + bool DispatchParseResponseGroup( char const *pToken, unsigned int uiHash, ParseResponseGroupDispatchMap_t &rMap, char const *responseGroupName, ResponseGroup& newGroup, AI_ResponseParams &groupResponseParams ); + + virtual const char *GetScriptFile( void ) = 0; + void LoadRuleSet( const char *setname ); + + void ResetResponseGroups(); + + float LookForCriteria( const CriteriaSet &criteriaSet, int iCriteria ); + float RecursiveLookForCriteria( const CriteriaSet &criteriaSet, Criteria *pParent ); + + public: + + void CopyRuleFrom( Rule *pSrcRule, ResponseRulePartition::tIndex iRule, CResponseSystem *pCustomSystem ); + void CopyCriteriaFrom( Rule *pSrcRule, Rule *pDstRule, CResponseSystem *pCustomSystem ); + void CopyResponsesFrom( Rule *pSrcRule, Rule *pDstRule, CResponseSystem *pCustomSystem ); + void CopyEnumerationsFrom( CResponseSystem *pCustomSystem ); + + //private: + + struct Enumeration + { + float value; + }; + + struct ResponseSearchResult + { + ResponseSearchResult() + { + group = NULL; + action = NULL; + } + + ResponseGroup *group; + ParserResponse *action; + }; + + inline bool ParseToken( void ) + { + if ( m_bUnget ) + { + m_bUnget = false; + return true; + } + if ( m_ScriptStack.Count() <= 0 ) + { + Assert( 0 ); + return false; + } + + m_ScriptStack[ 0 ].currenttoken = IEngineEmulator::Get()->ParseFile( m_ScriptStack[ 0 ].currenttoken, token, sizeof( token ) ); + m_ScriptStack[ 0 ].tokencount++; + return m_ScriptStack[ 0 ].currenttoken != NULL ? true : false; + } + +#ifdef MAPBASE + inline bool ParseTokenIntact( void ) + { + if ( m_bUnget ) + { + m_bUnget = false; + return true; + } + if ( m_ScriptStack.Count() <= 0 ) + { + Assert( 0 ); + return false; + } + + m_ScriptStack[ 0 ].currenttoken = IEngineEmulator::Get()->ParseFilePreserve( m_ScriptStack[ 0 ].currenttoken, token, sizeof( token ) ); + m_ScriptStack[ 0 ].tokencount++; + return m_ScriptStack[ 0 ].currenttoken != NULL ? true : false; + } +#endif + + inline void Unget() + { + m_bUnget = true; + } + + inline bool TokenWaiting( void ) + { + if ( m_ScriptStack.Count() <= 0 ) + { + Assert( 0 ); + return false; + } + + const char *p = m_ScriptStack[ 0 ].currenttoken; + + if ( !p ) + { + Error( "AI_ResponseSystem: Unxpected TokenWaiting() with NULL buffer in %s", (char * ) m_ScriptStack[ 0 ].name ); + return false; + } + + + while ( *p && *p!='\n') + { + // Special handler for // comment blocks + if ( *p == '/' && *(p+1) == '/' ) + return false; + + if ( !V_isspace( *p ) || isalnum( *p ) ) + return true; + + p++; + } + + return false; + } + + void ParseOneResponse( const char *responseGroupName, ResponseGroup& group, ResponseParams *defaultParams = NULL ); + + void ParseInclude( void ); + void ParseResponse( void ); + void ParseCriterion( void ); + void ParseRule( void ); + void ParseEnumeration( void ); + + private: + void ParseRule_MatchOnce( Rule &newRule ); + void ParseRule_ApplyContextToWorld( Rule &newRule ); +#ifdef MAPBASE + void ParseRule_ApplyContextToSquad( Rule &newRule ); + void ParseRule_ApplyContextToEnemy( Rule &newRule ); +#endif + void ParseRule_ApplyContext( Rule &newRule ); + void ParseRule_Response( Rule &newRule ); + //void ParseRule_ForceWeight( Rule &newRule ); + void ParseRule_Criteria( Rule &newRule ); + char const *m_pParseRuleName; + bool m_bParseRuleValid; + + void ParseResponse_Weight( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_PreDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_NoDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_DefaultDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_Delay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_SpeakOnce( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_NoScene( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_StopOnNonIdle( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_Odds( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_RespeakDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_WeaponDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_Soundlevel( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_DisplayFirst( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_DisplayLast( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_Fire( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_Then( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + + void ParseResponseGroup_Start( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + void ParseResponseGroup_PreDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + void ParseResponseGroup_NoDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + void ParseResponseGroup_DefaultDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + void ParseResponseGroup_Delay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + void ParseResponseGroup_SpeakOnce( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + void ParseResponseGroup_NoScene( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + void ParseResponseGroup_StopOnNonIdle( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + void ParseResponseGroup_Odds( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + void ParseResponseGroup_RespeakDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + void ParseResponseGroup_WeaponDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + void ParseResponseGroup_Soundlevel( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + +public: + int ParseOneCriterion( const char *criterionName ); + + bool Compare( const char *setValue, Criteria *c, bool verbose = false ); + bool CompareUsingMatcher( const char *setValue, Matcher& m, bool verbose = false ); + void ComputeMatcher( Criteria *c, Matcher& matcher ); + void ResolveToken( Matcher& matcher, char *token, size_t bufsize, char const *rawtoken ); + float LookupEnumeration( const char *name, bool& found ); + + ResponseRulePartition::tIndex FindBestMatchingRule( const CriteriaSet& set, bool verbose, float &scoreOfBestMatchingRule ); + +#ifdef MAPBASE + void DisableEmptyRules(); +#endif + + float ScoreCriteriaAgainstRule( const CriteriaSet& set, ResponseRulePartition::tRuleDict &dict, int irule, bool verbose = false ); + float RecursiveScoreSubcriteriaAgainstRule( const CriteriaSet& set, Criteria *parent, bool& exclude, bool verbose /*=false*/ ); + float ScoreCriteriaAgainstRuleCriteria( const CriteriaSet& set, int icriterion, bool& exclude, bool verbose = false ); + void FakeDepletes( ResponseGroup *g, IResponseFilter *pFilter ); + void RevertFakedDepletes( ResponseGroup *g ); + bool GetBestResponse( ResponseSearchResult& result, Rule *rule, bool verbose = false, IResponseFilter *pFilter = NULL ); + bool ResolveResponse( ResponseSearchResult& result, int depth, const char *name, bool verbose = false, IResponseFilter *pFilter = NULL ); + int SelectWeightedResponseFromResponseGroup( ResponseGroup *g, IResponseFilter *pFilter ); + void DescribeResponseGroup( ResponseGroup *group, int selected, int depth ); + void DebugPrint( int depth, const char *fmt, ... ); + + void LoadFromBuffer( const char *scriptfile, const char *buffer ); + + void GetCurrentScript( char *buf, size_t buflen ); + int GetCurrentToken() const; + void SetCurrentScript( const char *script ); + + inline bool IsRootCommand( unsigned int hash ) const + { + int slot = m_RootCommandHashes.Find( hash ); + return slot != m_RootCommandHashes.InvalidIndex(); + } + + inline bool IsRootCommand() const + { + return IsRootCommand( RR_HASH( token ) ); + } + + void PushScript( const char *scriptfile, unsigned char *buffer ); + void PopScript(void); + + void ResponseWarning( const char *fmt, ... ); + + CUtlDict< ResponseGroup, short > m_Responses; + CUtlDict< Criteria, short > m_Criteria; + // CUtlDict< Rule, short > m_Rules; + ResponseRulePartition m_RulePartitions; + CUtlDict< Enumeration, short > m_Enumerations; + + CUtlVector m_FakedDepletes; + + char token[ 1204 ]; + + bool m_bUnget; + + bool m_bCustomManagable; + +#ifdef MAPBASE + // This is a hack specifically designed to fix displayfirst, speakonce, etc. in "prospective" response searches, + // especially the prospective lookups in followup responses. + // It works by preventing responses from being marked as "used". + bool m_bInProspective; +#endif + + struct ScriptEntry + { + unsigned char *buffer; + FileNameHandle_t name; + const char *currenttoken; + int tokencount; + }; + + CUtlVector< ScriptEntry > m_ScriptStack; + CStringPool m_IncludedFiles; + + DispatchMap_t m_FileDispatch; + ParseRuleDispatchMap_t m_RuleDispatch; + ParseResponseDispatchMap_t m_ResponseDispatch; + ParseResponseGroupDispatchMap_t m_ResponseGroupDispatch; + CUtlRBTree< unsigned int > m_RootCommandHashes; + + // for debugging purposes only: concepts to be emitted from rr_debugresponses 2 + typedef CUtlLinkedList< CRR_Concept, unsigned short, false, unsigned int > ExcludeList_t; + static ExcludeList_t m_DebugExcludeList; + + friend class CDefaultResponseSystemSaveRestoreBlockHandler; + friend class CResponseSystemSaveRestoreOps; + }; + + // Some globals inherited from AI_Speech.h: + const float AIS_DEF_MIN_DELAY = 2.8; // Minimum amount of time an NPCs will wait after someone has spoken before considering speaking again + const float AIS_DEF_MAX_DELAY = 3.2; // Maximum amount of time an NPCs will wait after someone has spoken before considering speaking again +} + +#endif // RESPONSE_SYSTEM_H \ No newline at end of file diff --git a/sp/src/responserules/runtime/response_types.cpp b/sp/src/responserules/runtime/response_types.cpp new file mode 100644 index 0000000000..1d8f6b311d --- /dev/null +++ b/sp/src/responserules/runtime/response_types.cpp @@ -0,0 +1,281 @@ +//========= Copyright © 1996-2010, Valve Corporation, All rights reserved. ============// +// +// Purpose: Core types for the response rules -- criteria, responses, rules, and matchers. +// +// $NoKeywords: $ +//=============================================================================// + +#include "rrbase.h" + +#include "tier1/mapbase_con_groups.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +using namespace ResponseRules; + + +// bizarre function handed down from the misty days of yore +// and the original response system. a lot of stuff uses it +// and I can't be arsed to replace everything with the c stdlib +// stuff +namespace ResponseRules +{ + extern const char *ResponseCopyString( const char *in ); +}; + + +//-------------------- MATCHER ---------------------------------------------- + +Matcher::Matcher() +{ + valid = false; + isnumeric = false; + notequal = false; + usemin = false; + minequals = false; + usemax = false; + maxequals = false; +#ifdef MAPBASE + isbit = false; +#endif + maxval = 0.0f; + minval = 0.0f; + + token = UTL_INVAL_SYMBOL; + rawtoken = UTL_INVAL_SYMBOL; +} + +void Matcher::Describe( void ) +{ + if ( !valid ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, " invalid!\n" ); + return; + } + char sz[ 128 ]; + + sz[ 0] = 0; + int minmaxcount = 0; + if ( usemin ) + { + Q_snprintf( sz, sizeof( sz ), ">%s%.3f", minequals ? "=" : "", minval ); + minmaxcount++; + } + if ( usemax ) + { + char sz2[ 128 ]; + Q_snprintf( sz2, sizeof( sz2 ), "<%s%.3f", maxequals ? "=" : "", maxval ); + + if ( minmaxcount > 0 ) + { + Q_strncat( sz, " and ", sizeof( sz ), COPY_ALL_CHARACTERS ); + } + Q_strncat( sz, sz2, sizeof( sz ), COPY_ALL_CHARACTERS ); + minmaxcount++; + } + + if ( minmaxcount >= 1 ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, " matcher: %s\n", sz ); + return; + } + +#ifdef MAPBASE + if ( isbit ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, " matcher: &%s%s\n", notequal ? "~" : "", GetToken() ); + return; + } +#endif + + if ( notequal ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, " matcher: !=%s\n", GetToken() ); + return; + } + + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, " matcher: ==%s\n", GetToken() ); +} + +void Matcher::SetToken( char const *s ) +{ + token = g_RS.AddString( s ); +} + +void Matcher::SetRaw( char const *raw ) +{ + rawtoken = g_RS.AddString( raw ); +} + +char const *Matcher::GetToken() +{ + if ( token.IsValid() ) + { + return g_RS.String( token ); + } + return ""; +} + +char const *Matcher::GetRaw() +{ + if ( rawtoken.IsValid() ) + { + return g_RS.String( rawtoken ); + } + return ""; +} + +//-------------------- CRITERIA ---------------------------------------------- + +Criteria::Criteria() +{ + value = NULL; + weight.SetFloat( 1.0f ); + required = false; +} +Criteria::Criteria(const Criteria& src ) +{ + operator=( src ); +} + +Criteria::~Criteria() +{ + // do nothing because we don't own name and value anymore +} + +Criteria& Criteria::operator =(const Criteria& src ) +{ + if ( this == &src ) + return *this; + + nameSym = src.nameSym; + value = ResponseCopyString( src.value ); + weight = src.weight; + required = src.required; + + matcher = src.matcher; + + int c = src.subcriteria.Count(); + subcriteria.EnsureCapacity( c ); + for ( int i = 0; i < c; i++ ) + { + subcriteria.AddToTail( src.subcriteria[ i ] ); + } + + return *this; +} + + +//-------------------- RESPONSE ---------------------------------------------- + + + +ParserResponse::ParserResponse() : m_followup() +{ + type = RESPONSE_NONE; + value = NULL; + weight.SetFloat( 1.0f ); + depletioncount = 0; + first = false; + last = false; +} + +ParserResponse& ParserResponse::operator =( const ParserResponse& src ) +{ + if ( this == &src ) + return *this; + weight = src.weight; + type = src.type; + value = ResponseCopyString( src.value ); + depletioncount = src.depletioncount; + first = src.first; + last = src.last; + params = src.params; + + m_followup.followup_concept = ResponseCopyString(src.m_followup.followup_concept); + m_followup.followup_contexts = ResponseCopyString(src.m_followup.followup_contexts); + m_followup.followup_target = ResponseCopyString(src.m_followup.followup_target); + m_followup.followup_entityioinput = ResponseCopyString(src.m_followup.followup_entityioinput); + m_followup.followup_entityiotarget = ResponseCopyString(src.m_followup.followup_entityiotarget); + m_followup.followup_delay = src.m_followup.followup_delay; + m_followup.followup_entityiodelay = src.m_followup.followup_entityiodelay; + + return *this; +} + +ParserResponse::ParserResponse( const ParserResponse& src ) +{ + operator=( src ); +} + +ParserResponse::~ParserResponse() +{ + // nothing to do, since we don't own + // the strings anymore +} + +// ------------ RULE --------------- + +Rule::Rule() : m_nForceWeight(0) +{ + m_bMatchOnce = false; + m_bEnabled = true; + m_szContext = NULL; +#ifdef MAPBASE + m_iContextFlags = 0; +#else + m_bApplyContextToWorld = false; +#endif +} + +Rule& Rule::operator =( const Rule& src ) +{ + if ( this == &src ) + return *this; + + int i; + int c; + + c = src.m_Criteria.Count(); + m_Criteria.EnsureCapacity( c ); + for ( i = 0; i < c; i++ ) + { + m_Criteria.AddToTail( src.m_Criteria[ i ] ); + } + + c = src.m_Responses.Count(); + m_Responses.EnsureCapacity( c ); + for ( i = 0; i < c; i++ ) + { + m_Responses.AddToTail( src.m_Responses[ i ] ); + } + + SetContext( src.m_szContext ); + m_bMatchOnce = src.m_bMatchOnce; + m_bEnabled = src.m_bEnabled; +#ifdef MAPBASE + m_iContextFlags = src.m_iContextFlags; +#else + m_bApplyContextToWorld = src.m_bApplyContextToWorld; +#endif + m_nForceWeight = src.m_nForceWeight; + return *this; +} + +Rule::Rule( const Rule& src ) +{ + operator=(src); +} + +Rule::~Rule() +{ +} + +void Rule::SetContext( const char *context ) +{ + // we don't own the data we point to, so just update pointer + m_szContext = ResponseCopyString( context ); +} + + diff --git a/sp/src/responserules/runtime/response_types_internal.cpp b/sp/src/responserules/runtime/response_types_internal.cpp new file mode 100644 index 0000000000..098801c452 --- /dev/null +++ b/sp/src/responserules/runtime/response_types_internal.cpp @@ -0,0 +1,167 @@ +//========= Copyright © 1996-2010, Valve Corporation, All rights reserved. ============// +// +// Purpose: Core types for the response rules -- criteria, responses, rules, and matchers. +// +// $NoKeywords: $ +//=============================================================================// + +#include "rrbase.h" +#ifdef MAPBASE +#include "convar.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +using namespace ResponseRules; + +#ifdef MAPBASE +ConVar rr_bucket_name_who( "rr_bucket_name_who", "Classname", FCVAR_NONE, "The name of the criteria to use for the 'Who' bucket." ); // Default changed to "Classname" for HL2 +ConVar rr_bucket_name_concept( "rr_bucket_name_concept", "Concept", FCVAR_NONE, "The name of the criteria to use for the 'Concept' bucket." ); +ConVar rr_bucket_name_subject( "rr_bucket_name_subject", "Subject", FCVAR_NONE, "The name of the criteria to use for the 'Subject' bucket." ); +#endif + + + + +ResponseRulePartition::ResponseRulePartition() +{ + Assert(true); + COMPILE_TIME_ASSERT( kIDX_ELEM_MASK < (1 << 16) ); + COMPILE_TIME_ASSERT( (kIDX_ELEM_MASK & (kIDX_ELEM_MASK + 1)) == 0 ); /// assert is power of two minus one +} + +ResponseRulePartition::~ResponseRulePartition() +{ + RemoveAll(); +} + +ResponseRulePartition::tIndex ResponseRulePartition::IndexFromDictElem( tRuleDict* pDict, int elem ) +{ + Assert( pDict ); + // If this fails, you've tried to build an index for a rule that's not stored + // in this partition + Assert( pDict >= m_RuleParts && pDict < m_RuleParts + N_RESPONSE_PARTITIONS ); + AssertMsg1( elem <= kIDX_ELEM_MASK, "A rule dictionary has %d elements; this exceeds the 255 that can be packed into an index.\n", elem ); + + int bucket = pDict - m_RuleParts; + return ( bucket << 16 ) | ( elem & kIDX_ELEM_MASK ); // this is a native op on PPC +} + + +char const *ResponseRulePartition::GetElementName( const tIndex &i ) const +{ + Assert( IsValid(i) ); + return m_RuleParts[ BucketFromIdx(i) ].GetElementName( PartFromIdx(i) ); +} + + +Rule *ResponseRulePartition::FindByName( char const *name ) const +{ + int count; + + for ( int bukkit = 0 ; bukkit < N_RESPONSE_PARTITIONS ; ++bukkit ) + { + count = m_RuleParts[bukkit].Count(); + for ( int i = 0; i < count; ++i ) + { + if (V_strncmp( m_RuleParts[bukkit].GetElementName(i), name, CRR_Response::MAX_RULE_NAME ) == 0) + return m_RuleParts[bukkit][i]; + } + } + + return NULL; +} + + +int ResponseRulePartition::Count( void ) +{ + int count = 0 ; + for ( int bukkit = 0 ; bukkit < N_RESPONSE_PARTITIONS ; ++bukkit ) + { + count += m_RuleParts[bukkit].Count(); + } + + return count; +} + +void ResponseRulePartition::RemoveAll( void ) +{ + for ( int bukkit = 0 ; bukkit < N_RESPONSE_PARTITIONS ; ++bukkit ) + { + for ( int i = m_RuleParts[bukkit].FirstInorder(); i != m_RuleParts[bukkit].InvalidIndex(); i = m_RuleParts[bukkit].NextInorder( i ) ) + { + delete m_RuleParts[bukkit][ i ]; + } + m_RuleParts[bukkit].RemoveAll(); + } +} + +#ifdef MAPBASE +void ResponseRulePartition::PurgeAndDeleteElements() +{ + for ( int bukkit = 0 ; bukkit < N_RESPONSE_PARTITIONS ; ++bukkit ) + { + for ( int i = m_RuleParts[bukkit].FirstInorder(); i != m_RuleParts[bukkit].InvalidIndex(); i = m_RuleParts[bukkit].NextInorder( i ) ) + { + delete m_RuleParts[bukkit][ i ]; + } + m_RuleParts[bukkit].Purge(); + } +} +#endif + +// don't bucket "subject" criteria that prefix with operators, since stripping all that out again would +// be a big pain, and the most important rules that need subjects are tlk_remarks anyway. +static inline bool CanBucketBySubject( const char * RESTRICT pszSubject ) +{ + return pszSubject && + ( ( pszSubject[0] >= 'A' && pszSubject[0] <= 'Z' ) || + ( pszSubject[0] >= 'a' && pszSubject[0] <= 'z' ) ); +} + +ResponseRulePartition::tRuleDict &ResponseRulePartition::GetDictForRule( CResponseSystem *pSystem, Rule *pRule ) +{ +#ifdef MAPBASE + const static CUtlSymbol kWHO = CriteriaSet::ComputeCriteriaSymbol( rr_bucket_name_who.GetString() ); + const static CUtlSymbol kCONCEPT = CriteriaSet::ComputeCriteriaSymbol( rr_bucket_name_concept.GetString() ); + const static CUtlSymbol kSUBJECT = CriteriaSet::ComputeCriteriaSymbol( rr_bucket_name_subject.GetString() ); +#else + const static CUtlSymbol kWHO = CriteriaSet::ComputeCriteriaSymbol("Who"); + const static CUtlSymbol kCONCEPT = CriteriaSet::ComputeCriteriaSymbol("Concept"); + const static CUtlSymbol kSUBJECT = CriteriaSet::ComputeCriteriaSymbol("Subject"); +#endif + + const char *pszSpeaker = pRule->GetValueForRuleCriterionByName( pSystem, kWHO ); + const char *pszConcept = pRule->GetValueForRuleCriterionByName( pSystem, kCONCEPT ); + const Criteria *pSubjCrit = pRule->GetPointerForRuleCriterionByName( pSystem, kSUBJECT ); + + return m_RuleParts[ + GetBucketForSpeakerAndConcept( pszSpeaker, pszConcept, + ( pSubjCrit && pSubjCrit->required && CanBucketBySubject(pSubjCrit->value) ) ? + pSubjCrit->value : + NULL ) + ]; +} + + +void ResponseRulePartition::GetDictsForCriteria( CUtlVectorFixed< ResponseRulePartition::tRuleDict *, 2 > *pResult, const CriteriaSet &criteria ) +{ + pResult->RemoveAll(); + pResult->EnsureCapacity( 2 ); + + // get the values for Who and Concept, which are what we bucket on + int speakerIdx = criteria.FindCriterionIndex( "Who" ); + const char *pszSpeaker = speakerIdx != -1 ? criteria.GetValue( speakerIdx ) : NULL ; + + int conceptIdx = criteria.FindCriterionIndex( "Concept" ); + const char *pszConcept = conceptIdx != -1 ? criteria.GetValue( conceptIdx ) : NULL ; + + int subjectIdx = criteria.FindCriterionIndex( "Subject" ); + const char *pszSubject = subjectIdx != -1 ? criteria.GetValue( subjectIdx ) : NULL ; + + pResult->AddToTail( &m_RuleParts[ GetBucketForSpeakerAndConcept(pszSpeaker, pszConcept, pszSubject) ] ); + // also try the rules not specifying subject + pResult->AddToTail( &m_RuleParts[ GetBucketForSpeakerAndConcept(pszSpeaker, pszConcept, NULL) ] ); + +} \ No newline at end of file diff --git a/sp/src/responserules/runtime/response_types_internal.h b/sp/src/responserules/runtime/response_types_internal.h new file mode 100644 index 0000000000..08f311855b --- /dev/null +++ b/sp/src/responserules/runtime/response_types_internal.h @@ -0,0 +1,560 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Core types for the response rules -- criteria, responses, rules, and matchers. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef RESPONSE_TYPES_INTERNAL_H +#define RESPONSE_TYPES_INTERNAL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "responserules/response_types.h" +#include "utldict.h" + + +namespace ResponseRules +{ + + inline unsigned FASTCALL HashStringConventional( const char *pszKey ) + { + unsigned hash = 0xAAAAAAAA; // Alternating 1's and 0's to maximize the effect of the later multiply and add + + for( ; *pszKey ; pszKey++ ) + { + hash = ( ( hash << 5 ) + hash ) + (uint8)(*pszKey); + } + + return hash; + } + + // Note: HashString causes collisions!!! +#define RR_HASH HashStringConventional + +#pragma pack(push,1) + + class Matcher + { + public: + Matcher(); + + void Describe( void ); + + float maxval; + float minval; + + bool valid : 1; //1 + bool isnumeric : 1; //2 + bool notequal : 1; //3 + bool usemin : 1; //4 + bool minequals : 1; //5 + bool usemax : 1; //6 + bool maxequals : 1; //7 +#ifdef MAPBASE + bool isbit : 1; //8 +#endif + + void SetToken( char const *s ); + + char const *GetToken(); + + void SetRaw( char const *raw ); + + char const *GetRaw(); + + private: + CUtlSymbol token; + CUtlSymbol rawtoken; + }; +#pragma pack(pop) + + struct Criteria + { + Criteria(); + Criteria& operator =(const Criteria& src ); + + Criteria(const Criteria& src ); + ~Criteria(); + + // Does this criterion recursively contain more criteria? + inline bool IsSubCriteriaType() const + { + return ( subcriteria.Count() > 0 ) ? true : false; + } + + // const char *name; + CUtlSymbol nameSym; + const char *value; + float16 weight; + bool required; + + Matcher matcher; + + // Indices into sub criteria + CUtlVectorConservative< unsigned short > subcriteria; + }; + +#pragma pack(push,1) + /// This is a response block as read from the file, + /// different from CRR_Response which is what is handed + /// back to queries. + struct ParserResponse + { + DECLARE_SIMPLE_DATADESC_INSIDE_NAMESPACE(); + + ParserResponse(); + ParserResponse( const ParserResponse& src ); + ParserResponse& operator =( const ParserResponse& src ); + ~ParserResponse(); + + ResponseType_t GetType() { return (ResponseType_t)type; } + + ResponseParams params; + + const char *value; // fixed up value spot // 4 + float16 weight; // 6 + + byte depletioncount; // 7 + byte type : 6; // 8 + byte first : 1; // + byte last : 1; // + + ALIGN32 AI_ResponseFollowup m_followup; // info on whether I should force the other guy to say something + }; +#pragma pack(pop) + +#pragma pack(push,1) + struct ResponseGroup + { + DECLARE_SIMPLE_DATADESC_INSIDE_NAMESPACE(); + + ResponseGroup() + { + // By default visit all nodes before repeating + m_bSequential = false; + m_bNoRepeat = false; + m_bEnabled = true; + m_nCurrentIndex = 0; + m_bDepleteBeforeRepeat = true; + m_nDepletionCount = 1; + m_bHasFirst = false; + m_bHasLast = false; + } + + ResponseGroup( const ResponseGroup& src ) + { + int c = src.group.Count(); + for ( int i = 0; i < c; i++ ) + { + group.AddToTail( src.group[ i ] ); + } + + m_bDepleteBeforeRepeat = src.m_bDepleteBeforeRepeat; + m_nDepletionCount = src.m_nDepletionCount; + m_bHasFirst = src.m_bHasFirst; + m_bHasLast = src.m_bHasLast; + m_bSequential = src.m_bSequential; + m_bNoRepeat = src.m_bNoRepeat; + m_bEnabled = src.m_bEnabled; + m_nCurrentIndex = src.m_nCurrentIndex; + } + + ResponseGroup& operator=( const ResponseGroup& src ) + { + if ( this == &src ) + return *this; + int c = src.group.Count(); + for ( int i = 0; i < c; i++ ) + { + group.AddToTail( src.group[ i ] ); + } + + m_bDepleteBeforeRepeat = src.m_bDepleteBeforeRepeat; + m_nDepletionCount = src.m_nDepletionCount; + m_bHasFirst = src.m_bHasFirst; + m_bHasLast = src.m_bHasLast; + m_bSequential = src.m_bSequential; + m_bNoRepeat = src.m_bNoRepeat; + m_bEnabled = src.m_bEnabled; + m_nCurrentIndex = src.m_nCurrentIndex; + return *this; + } + + bool HasUndepletedChoices() const + { + if ( !m_bDepleteBeforeRepeat ) + return true; + + int c = group.Count(); + for ( int i = 0; i < c; i++ ) + { + if ( group[ i ].depletioncount != m_nDepletionCount ) + return true; + } + + return false; + } + + void MarkResponseUsed( int idx ) + { + if ( !m_bDepleteBeforeRepeat ) + return; + + if ( idx < 0 || idx >= group.Count() ) + { + Assert( 0 ); + return; + } + + group[ idx ].depletioncount = m_nDepletionCount; + } + + void ResetDepletionCount() + { + if ( !m_bDepleteBeforeRepeat ) + return; + ++m_nDepletionCount; + } + + void Reset() + { + ResetDepletionCount(); + SetEnabled( true ); + SetCurrentIndex( 0 ); + m_nDepletionCount = 1; + + for ( int i = 0; i < group.Count(); ++i ) + { + group[ i ].depletioncount = 0; + } + } + + bool HasUndepletedFirst( int& index ) + { + index = -1; + + if ( !m_bDepleteBeforeRepeat ) + return false; + + int c = group.Count(); + for ( int i = 0; i < c; i++ ) + { + ParserResponse *r = &group[ i ]; + + if ( ( r->depletioncount != m_nDepletionCount ) && r->first ) + { + index = i; + return true; + } + } + + return false; + } + + bool HasUndepletedLast( int& index ) + { + index = -1; + + if ( !m_bDepleteBeforeRepeat ) + return false; + + int c = group.Count(); + for ( int i = 0; i < c; i++ ) + { + ParserResponse *r = &group[ i ]; + + if ( ( r->depletioncount != m_nDepletionCount ) && r->last ) + { + index = i; + return true; + } + } + + return false; + } + + bool ShouldCheckRepeats() const { return m_bDepleteBeforeRepeat; } + int GetDepletionCount() const { return m_nDepletionCount; } + + bool IsSequential() const { return m_bSequential; } + void SetSequential( bool seq ) { m_bSequential = seq; } + + bool IsNoRepeat() const { return m_bNoRepeat; } + void SetNoRepeat( bool norepeat ) { m_bNoRepeat = norepeat; } + + bool IsEnabled() const { return m_bEnabled; } + void SetEnabled( bool enabled ) { m_bEnabled = enabled; } + + int GetCurrentIndex() const { return m_nCurrentIndex; } + void SetCurrentIndex( byte idx ) { m_nCurrentIndex = idx; } + + CUtlVector< ParserResponse > group; + + bool m_bEnabled; + + byte m_nCurrentIndex; + // Invalidation counter + byte m_nDepletionCount; + + // Use all slots before repeating any + bool m_bDepleteBeforeRepeat : 1; + bool m_bHasFirst : 1; + bool m_bHasLast : 1; + bool m_bSequential : 1; + bool m_bNoRepeat : 1; + }; +#pragma pack(pop) + +#pragma pack(push,1) + struct Rule + { + Rule(); + Rule( const Rule& src ); + ~Rule(); + Rule& operator =( const Rule& src ); + + void SetContext( const char *context ); + + const char *GetContext( void ) const { return m_szContext; } + + inline bool IsEnabled() const { return m_bEnabled; } + inline void Disable() { m_bEnabled = false; } + inline bool IsMatchOnce() const { return m_bMatchOnce; } +#ifdef MAPBASE + inline int GetContextFlags() const { return m_iContextFlags; } + inline bool IsApplyContextToWorld() const { return (m_iContextFlags & APPLYCONTEXT_WORLD) != 0; } +#else + inline bool IsApplyContextToWorld() const { return m_bApplyContextToWorld; } +#endif + + const char *GetValueForRuleCriterionByName( CResponseSystem *pSystem, const CUtlSymbol &pCritNameSym ); + const Criteria *GetPointerForRuleCriterionByName( CResponseSystem *pSystem, const CUtlSymbol &pCritNameSym ); + + // Indices into underlying criteria and response dictionaries + CUtlVectorConservative< unsigned short > m_Criteria; + CUtlVectorConservative< unsigned short> m_Responses; + + const char *m_szContext; + uint8 m_nForceWeight; + +#ifdef MAPBASE + // TODO: Could this cause any issues with the code optimization? + uint8 m_iContextFlags; +#else + bool m_bApplyContextToWorld : 1; +#endif + + bool m_bMatchOnce : 1; + bool m_bEnabled : 1; + + private: + // what is this, lisp? + const char *RecursiveGetValueForRuleCriterionByName( CResponseSystem *pSystem, const Criteria *pCrit, const CUtlSymbol &pCritNameSym ); + const Criteria *RecursiveGetPointerForRuleCriterionByName( CResponseSystem *pSystem, const Criteria *pCrit, const CUtlSymbol &pCritNameSym ); + }; +#pragma pack(pop) + + template + class CResponseDict : public CUtlMap + { + public: + CResponseDict() : CUtlMap( DefLessFunc( unsigned int ) ), m_ReverseMap( DefLessFunc( unsigned int ) ) + { + } + + I Insert( const char *pName, const T &element ) + { + extern const char *ResponseCopyString( const char *in ); + char const *pString = ResponseCopyString( pName ); + unsigned int hash = RR_HASH( pString ); + m_ReverseMap.Insert( hash, pString ); + return CUtlMap::Insert( hash, element ); + } + + I Insert( const char *pName ) + { + extern const char *ResponseCopyString( const char *in ); + char const *pString = ResponseCopyString( pName ); + unsigned int hash = RR_HASH( pString ); + m_ReverseMap.Insert( hash, pString ); + return CUtlMap::Insert( hash ); + } + + I Find( char const *pName ) const + { + unsigned int hash = RR_HASH( pName ); + return CUtlMap::Find( hash ); + } + + const char *GetElementName( I i ) + { + int k = this->Key( i ); + int slot = m_ReverseMap.Find( k ); + if ( slot == m_ReverseMap.InvalidIndex() ) + return ""; + return m_ReverseMap[ slot ]; + } + + const char *GetElementName( I i ) const + { + int k = this->Key( i ); + int slot = m_ReverseMap.Find( k ); + if ( slot == m_ReverseMap.InvalidIndex() ) + return ""; + return m_ReverseMap[ slot ]; + } + + private: + CUtlMap< unsigned int, const char * > m_ReverseMap; + + }; + + // define this to 1 to enable printing some occupancy + // information on the response system via concommmand + // rr_dumphashinfo + #define RR_DUMPHASHINFO_ENABLED 0 + // The Rules are partitioned based on a variety of factors (presently, + // speaker and concept) for faster lookup, basically a seperate-chained hash. + struct ResponseRulePartition + { + ResponseRulePartition( void ); + ~ResponseRulePartition(); + + typedef CResponseDict< Rule * > tRuleDict; + typedef uint32 tIndex; // an integer that can be used to find any rule in the dict + + /// get the appropriate m_rules dict for the provided rule + tRuleDict &GetDictForRule( CResponseSystem *pSystem, Rule *pRule ); + + /// get all bucket full of rules that might possibly match the given criteria. + /// (right now they are bucketed such that all rules that can possibly match a + /// criteria are in one of two dictionaries) + void GetDictsForCriteria( CUtlVectorFixed< ResponseRulePartition::tRuleDict *, 2 > *pResult, const CriteriaSet &criteria ); + + // dump everything. + void RemoveAll(); +#ifdef MAPBASE + void PurgeAndDeleteElements(); +#endif + + inline Rule &operator[]( tIndex idx ); + int Count( void ); // number of elements inside, but you can't iterate from 0 to this + char const *GetElementName( const tIndex &i ) const; + Rule *FindByName( char const *name ) const; + + /// given a dictionary and an element number inside that dict, + /// return a tIndex + tIndex IndexFromDictElem( tRuleDict* pDict, int elem ); + + // for iteration: + inline tIndex First( void ); + inline tIndex Next( const tIndex &idx ); + inline bool IsValid( const tIndex &idx ) const; + inline static tIndex InvalidIdx( void ) + { + return ((tIndex) -1); + } + + // used only for debug prints, do not rely on them otherwise + inline unsigned int BucketFromIdx( const tIndex &idx ) const ; + inline unsigned int PartFromIdx( const tIndex &idx ) const ; + + enum { + N_RESPONSE_PARTITIONS = 256, + kIDX_ELEM_MASK = 0xFFF, ///< this is used to mask the element number part of a ResponseRulePartition::tIndex + }; + +#if RR_DUMPHASHINFO_ENABLED + void PrintBucketInfo( CResponseSystem *pSys ); +#endif + + private: + tRuleDict m_RuleParts[N_RESPONSE_PARTITIONS]; + unsigned int GetBucketForSpeakerAndConcept( const char *pszSpeaker, const char *pszConcept, const char *pszSubject ); + }; + + // // // // // inline functions + + inline ResponseRulePartition::tIndex ResponseRulePartition::First( void ) + { + // find the first bucket that has anything + for ( int bucket = 0 ; bucket < N_RESPONSE_PARTITIONS; bucket++ ) + { + if ( m_RuleParts[bucket].Count() > 0 ) + return bucket << 16; + } + return InvalidIdx(); + } + + inline ResponseRulePartition::tIndex ResponseRulePartition::Next( const tIndex &idx ) + { + int bucket = BucketFromIdx( idx ); + unsigned int elem = PartFromIdx( idx ); + Assert( IsValid(idx) ); + AssertMsg( elem < kIDX_ELEM_MASK, "Too many response rules! Overflow! Doom!" ); + if ( elem + 1 < m_RuleParts[bucket].Count() ) + { + return idx+1; + } + else + { + // walk through the other buckets, skipping empty ones, until we find one with responses and give up. + while ( ++bucket < N_RESPONSE_PARTITIONS ) + { + if ( m_RuleParts[bucket].Count() > 0 ) + { + // 0th element in nth bucket + return bucket << 16; + } + } + + // out of buckets + return InvalidIdx(); + + } + } + + inline Rule &ResponseRulePartition::operator[]( tIndex idx ) + { + Assert( IsValid(idx) ); + return *m_RuleParts[ BucketFromIdx(idx) ][ PartFromIdx(idx) ] ; + } + + inline unsigned int ResponseRulePartition::BucketFromIdx( const tIndex &idx ) const + { + return idx >> 16; + } + + inline unsigned int ResponseRulePartition::PartFromIdx( const tIndex &idx ) const + { + return idx & kIDX_ELEM_MASK; + } + + inline bool ResponseRulePartition::IsValid( const tIndex & idx ) const + { + // make sure that the idx type for the dicts is still short + COMPILE_TIME_ASSERT( sizeof(m_RuleParts[0].FirstInorder()) == 2 ); + + if ( idx == -1 ) + return false; + + int bucket = idx >> 16; + unsigned int elem = idx & kIDX_ELEM_MASK; + + return ( bucket < N_RESPONSE_PARTITIONS && + elem < m_RuleParts[bucket].Count() ); + } + + //----------------------------------------------------------------------------- + // PARSER TYPES -- these are internal to the response system, and represent + // the objects as loaded from disk. + //----------------------------------------------------------------------------- + + +} + +#include "response_system.h" + +#endif \ No newline at end of file diff --git a/sp/src/responserules/runtime/rr_convars.cpp b/sp/src/responserules/runtime/rr_convars.cpp new file mode 100644 index 0000000000..986ae0cd41 --- /dev/null +++ b/sp/src/responserules/runtime/rr_convars.cpp @@ -0,0 +1,14 @@ +//========= Copyright © 1996-2010, Valve Corporation, All rights reserved. ============// +// +// Purpose: Convars used by the response rule system. +// +// $NoKeywords: $ +//=============================================================================// + +#include "rrbase.h" +#include + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + diff --git a/sp/src/responserules/runtime/rr_response.cpp b/sp/src/responserules/runtime/rr_response.cpp new file mode 100644 index 0000000000..b652e8b377 --- /dev/null +++ b/sp/src/responserules/runtime/rr_response.cpp @@ -0,0 +1,387 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// +#include "rrbase.h" + +#include +#include "tier1/mapbase_con_groups.h" + +/* +#include "AI_Criteria.h" +#include "ai_speech.h" +#include +#include "engine/IEngineSound.h" +*/ + +// memdbgon must be the last include file in a .cpp file!!! +#include + +using namespace ResponseRules; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CRR_Response::CRR_Response() : m_fMatchScore(0) +{ + m_Type = ResponseRules::RESPONSE_NONE; + m_szResponseName[0] = 0; + m_szMatchingRule[0]=0; + m_szContext = NULL; +#ifdef MAPBASE + m_iContextFlags = 0; +#else + m_bApplyContextToWorld = false; +#endif +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CRR_Response::CRR_Response( const CRR_Response &from ) : m_fMatchScore(0) +{ + // Assert( (void*)(&m_Type) == (void*)this ); + Invalidate(); + memcpy( this, &from, sizeof(*this) ); + m_szContext = NULL; + SetContext( from.m_szContext ); +#ifdef MAPBASE + m_iContextFlags = from.m_iContextFlags; +#else + m_bApplyContextToWorld = from.m_bApplyContextToWorld; +#endif +} + + +//----------------------------------------------------------------------------- +CRR_Response &CRR_Response::operator=( const CRR_Response &from ) +{ + // Assert( (void*)(&m_Type) == (void*)this ); + Invalidate(); + memcpy( this, &from, sizeof(*this) ); + m_szContext = NULL; + SetContext( from.m_szContext ); +#ifdef MAPBASE + m_iContextFlags = from.m_iContextFlags; +#else + m_bApplyContextToWorld = from.m_bApplyContextToWorld; +#endif + return *this; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CRR_Response::~CRR_Response() +{ + if (m_szContext) + delete[] m_szContext; +} + +void CRR_Response::Invalidate() +{ + if (m_szContext) + { + delete[] m_szContext; + m_szContext = NULL; + } + m_Type = ResponseRules::RESPONSE_NONE; + m_szResponseName[0] = 0; + // not really necessary: + /* + m_szMatchingRule[0]=0; + m_szContext = NULL; + m_bApplyContextToWorld = false; + */ +} + +// please do not new or delete CRR_Responses. +void CRR_Response::operator delete(void* p) +{ + AssertMsg(false, "DO NOT new or delete CRR_Response s."); + free(p); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *response - +// *criteria - +//----------------------------------------------------------------------------- +void CRR_Response::Init( ResponseType_t type, const char *responseName, const ResponseParams& responseparams, const char *ruleName, const char *applyContext, bool bApplyContextToWorld ) +{ + m_Type = type; + Q_strncpy( m_szResponseName, responseName, sizeof( m_szResponseName ) ); + // Copy underlying criteria + Q_strncpy( m_szMatchingRule, ruleName ? ruleName : "NULL", sizeof( m_szMatchingRule ) ); + m_Params = responseparams; + SetContext( applyContext ); +#ifdef MAPBASE + bApplyContextToWorld ? m_iContextFlags = APPLYCONTEXT_WORLD : m_iContextFlags = 0; +#else + m_bApplyContextToWorld = bApplyContextToWorld; +#endif +} + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Input : *response - +// *criteria - +//----------------------------------------------------------------------------- +void CRR_Response::Init( ResponseType_t type, const char *responseName, const ResponseParams& responseparams, const char *ruleName, const char *applyContext, int iContextFlags ) +{ + m_Type = type; + Q_strncpy( m_szResponseName, responseName, sizeof( m_szResponseName ) ); + // Copy underlying criteria + Q_strncpy( m_szMatchingRule, ruleName ? ruleName : "NULL", sizeof( m_szMatchingRule ) ); + m_Params = responseparams; + SetContext( applyContext ); + m_iContextFlags = iContextFlags; +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: Debug-print the response. You can optionally pass in the criteria +// used to come up with this response (usually present in the calling function) +// if you want to print that as well. DO NOT store the entire criteria set in +// CRR_Response just to make this debug print cleaner. +//----------------------------------------------------------------------------- +void CRR_Response::Describe( const CriteriaSet *pDebugCriteria ) +{ + if ( pDebugCriteria ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "Search criteria:\n" ); + pDebugCriteria->Describe(); + } + + if ( m_szMatchingRule[ 0 ] ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "Matched rule '%s', ", m_szMatchingRule ); + } + if ( m_szContext ) + { +#ifdef MAPBASE + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "Contexts to set '%s' on ", m_szContext ); + if (m_iContextFlags & APPLYCONTEXT_WORLD) + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "world, " ); + else if (m_iContextFlags & APPLYCONTEXT_SQUAD) + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "squad, " ); + else if (m_iContextFlags & APPLYCONTEXT_ENEMY) + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "enemy, " ); + else + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "speaker, " ); +#else + DevMsg( "Contexts to set '%s' on %s, ", m_szContext, m_bApplyContextToWorld ? "world" : "speaker" ); +#endif + } + + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "response %s = '%s'\n", DescribeResponse( (ResponseType_t)m_Type ), m_szResponseName ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : char const +//----------------------------------------------------------------------------- +void CRR_Response::GetName( char *buf, size_t buflen ) const +{ + Q_strncpy( buf, m_szResponseName, buflen ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : char const +//----------------------------------------------------------------------------- +void CRR_Response::GetResponse( char *buf, size_t buflen ) const +{ + GetName( buf, buflen ); +} + + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Output : char const +//----------------------------------------------------------------------------- +void CRR_Response::GetRule( char *buf, size_t buflen ) const +{ + Q_strncpy( buf, m_szMatchingRule, buflen ); +} +#endif + + +const char* ResponseRules::CRR_Response::GetNamePtr() const +{ + return m_szResponseName; +} +const char* ResponseRules::CRR_Response::GetResponsePtr() const +{ + return m_szResponseName; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : type - +// Output : char const +//----------------------------------------------------------------------------- +const char *CRR_Response::DescribeResponse( ResponseType_t type ) +{ + if ( (int)type < 0 || (int)type >= ResponseRules::NUM_RESPONSES ) + { + Assert( 0 ); + return "???CRR_Response bogus index"; + } + + switch( type ) + { + default: + { + Assert( 0 ); + } + // Fall through + case ResponseRules::RESPONSE_NONE: + return "RESPONSE_NONE"; + case ResponseRules::RESPONSE_SPEAK: + return "RESPONSE_SPEAK"; + case ResponseRules::RESPONSE_SENTENCE: + return "RESPONSE_SENTENCE"; + case ResponseRules::RESPONSE_SCENE: + return "RESPONSE_SCENE"; + case ResponseRules::RESPONSE_RESPONSE: + return "RESPONSE_RESPONSE"; + case ResponseRules::RESPONSE_PRINT: + return "RESPONSE_PRINT"; + case ResponseRules::RESPONSE_ENTITYIO: + return "RESPONSE_ENTITYIO"; +#ifdef MAPBASE + case ResponseRules::RESPONSE_VSCRIPT: + return "RESPONSE_VSCRIPT"; + case ResponseRules::RESPONSE_VSCRIPT_FILE: + return "RESPONSE_VSCRIPT_FILE"; +#endif + } + + return "RESPONSE_NONE"; +} + +/* +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRR_Response::Release() +{ + delete this; +} +*/ + +//----------------------------------------------------------------------------- +// Purpose: +// Output : soundlevel_t +//----------------------------------------------------------------------------- +soundlevel_t CRR_Response::GetSoundLevel() const +{ + if ( m_Params.flags & ResponseParams::RG_SOUNDLEVEL ) + { + return (soundlevel_t)m_Params.soundlevel; + } + + return SNDLVL_TALKING; +} + +float CRR_Response::GetRespeakDelay( void ) const +{ + if ( m_Params.flags & ResponseParams::RG_RESPEAKDELAY ) + { + interval_t temp; + m_Params.respeakdelay.ToInterval( temp ); + return RandomInterval( temp ); + } + + return 0.0f; +} + +float CRR_Response::GetWeaponDelay( void ) const +{ + if ( m_Params.flags & ResponseParams::RG_WEAPONDELAY ) + { + interval_t temp; + m_Params.weapondelay.ToInterval( temp ); + return RandomInterval( temp ); + } + + return 0.0f; +} + +bool CRR_Response::GetSpeakOnce( void ) const +{ + if ( m_Params.flags & ResponseParams::RG_SPEAKONCE ) + { + return true; + } + + return false; +} + +bool CRR_Response::ShouldntUseScene( void ) const +{ + return ( m_Params.flags & ResponseParams::RG_DONT_USE_SCENE ) != 0; +} + +bool CRR_Response::ShouldBreakOnNonIdle( void ) const +{ + return ( m_Params.flags & ResponseParams::RG_STOP_ON_NONIDLE ) != 0; +} + +int CRR_Response::GetOdds( void ) const +{ + if ( m_Params.flags & ResponseParams::RG_ODDS ) + { + return m_Params.odds; + } + return 100; +} + +float CRR_Response::GetDelay() const +{ + if ( m_Params.flags & ResponseParams::RG_DELAYAFTERSPEAK ) + { + interval_t temp; + m_Params.delay.ToInterval( temp ); + return RandomInterval( temp ); + } + return 0.0f; +} + +float CRR_Response::GetPreDelay() const +{ + if ( m_Params.flags & ResponseParams::RG_DELAYBEFORESPEAK ) + { + interval_t temp; + m_Params.predelay.ToInterval( temp ); + return RandomInterval( temp ); + } + return 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets context string +// Output : void +//----------------------------------------------------------------------------- +void CRR_Response::SetContext( const char *context ) +{ + if (m_szContext) + { + delete[] m_szContext; + m_szContext = NULL; + } + + if ( context ) + { + int len = Q_strlen( context ); + m_szContext = new char[ len + 1 ]; + Q_memcpy( m_szContext, context, len ); + m_szContext[ len ] = 0; + } +} diff --git a/sp/src/responserules/runtime/rr_speechconcept.cpp b/sp/src/responserules/runtime/rr_speechconcept.cpp new file mode 100644 index 0000000000..7e1e04abf3 --- /dev/null +++ b/sp/src/responserules/runtime/rr_speechconcept.cpp @@ -0,0 +1,73 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "rrbase.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include + +#if RR_CONCEPTS_ARE_STRINGS +#pragma error("RR_CONCEPTS_ARE_STRINGS no longer supported") +#else + +using namespace ResponseRules; + +// Used to turn ad-hoc concept from strings into numbers. +CRR_ConceptSymbolTable *g_pRRConceptTable = NULL; + +// Q&D hack to defer initialization of concept table until I can figure out where it +// really needs to come from. +static void InitializeRRConceptTable() +{ + if (g_pRRConceptTable == NULL) + { + g_pRRConceptTable = new CRR_ConceptSymbolTable( 64, 64, true ); + } +} + +// construct from string +CRR_Concept::CRR_Concept(const char *fromString) +{ + InitializeRRConceptTable(); + m_iConcept = g_pRRConceptTable->AddString(fromString); +} + +CRR_Concept &CRR_Concept::operator=(const char *fromString) +{ + InitializeRRConceptTable(); + m_iConcept = g_pRRConceptTable->AddString(fromString); + return *this; +} + +bool CRR_Concept::operator==(const char *pszConcept) +{ + int otherConcept = g_pRRConceptTable->Find(pszConcept); + return ( otherConcept != UTL_INVAL_SYMBOL && otherConcept == m_iConcept ); +} + +const char *CRR_Concept::GetStringConcept() const +{ + InitializeRRConceptTable(); + AssertMsg( m_iConcept.IsValid(), "AI Concept has invalid string symbol.\n" ); + const char * retval = g_pRRConceptTable->String(m_iConcept); + AssertMsg( retval, "An RR_Concept couldn't find its string in the symbol table!\n" ); + if (retval == NULL) + { + Warning( "An RR_Concept couldn't find its string in the symbol table!\n" ); + retval = ""; + } + return retval; +} + +const char *CRR_Concept::GetStringForGenericId(tGenericId genericId) +{ + InitializeRRConceptTable(); + return g_pRRConceptTable->String(genericId); +} + +#endif diff --git a/sp/src/responserules/runtime/rrbase.h b/sp/src/responserules/runtime/rrbase.h new file mode 100644 index 0000000000..e7af74deef --- /dev/null +++ b/sp/src/responserules/runtime/rrbase.h @@ -0,0 +1,59 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef RRBASE_H +#define RRBASE_H +#ifdef _WIN32 +#pragma once +#endif + +#ifdef _WIN32 +// Silence certain warnings +// #pragma warning(disable : 4244) // int or float down-conversion +// #pragma warning(disable : 4305) // int or float data truncation +// #pragma warning(disable : 4201) // nameless struct/union +// #pragma warning(disable : 4511) // copy constructor could not be generated +// #pragma warning(disable : 4675) // resolved overload was found by argument dependent lookup +#endif + +#ifdef _DEBUG +#define DEBUG 1 +#endif + +// Misc C-runtime library headers +#include +#include +#include + +// tier 0 +#include "tier0/dbg.h" +#include "tier0/platform.h" +#include "basetypes.h" + +// tier 1 +#include "tier1/strtools.h" +#include "utlvector.h" +#include "utlsymbol.h" + +// tier 2 +#include "string_t.h" + +// Shared engine/DLL constants +#include "const.h" +#include "edict.h" + +// app +#if defined(_X360) +#define DISABLE_DEBUG_HISTORY 1 +#endif + +#include "responserules/response_types.h" +#include "response_types_internal.h" +#include "responserules/response_host_interface.h" + + +#endif // CBASE_H diff --git a/sp/src/responserules/runtime/rrrlib.cpp b/sp/src/responserules/runtime/rrrlib.cpp new file mode 100644 index 0000000000..0d2c49fd7c --- /dev/null +++ b/sp/src/responserules/runtime/rrrlib.cpp @@ -0,0 +1,13 @@ +/// PLACEHOLDER FILE FOR RESPONSE RULES RUNTIME LIBRARY + +#include "rrbase.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +namespace ResponseRules +{ + /// Custom symbol table for the response rules. + CUtlSymbolTable g_RS; +}; \ No newline at end of file diff --git a/sp/src/responserules/runtime/stdafx.cpp b/sp/src/responserules/runtime/stdafx.cpp new file mode 100644 index 0000000000..4199359f20 --- /dev/null +++ b/sp/src/responserules/runtime/stdafx.cpp @@ -0,0 +1,11 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Builds the precompiled header for the game DLL +// +// $NoKeywords: $ +//=============================================================================// + + +#include "rrbase.h" + +// NOTE: DO NOT ADD ANY CODE OR HEADERS TO THIS FILE!!! diff --git a/sp/src/tier1/bitbuf.cpp b/sp/src/tier1/bitbuf.cpp index 52a25e4db3..e723bae916 100644 --- a/sp/src/tier1/bitbuf.cpp +++ b/sp/src/tier1/bitbuf.cpp @@ -1357,13 +1357,13 @@ int64 bf_read::ReadLongLong() float bf_read::ReadFloat() { - float ret; - Assert( sizeof(ret) == 4 ); - ReadBits(&ret, 32); + float retLocl; + Assert( sizeof(retLocl) == 4 ); + ReadBits(&retLocl, 32); // Swap the float, since ReadBits reads raw data - LittleFloat( &ret, &ret ); - return ret; + LittleFloat( &retLocl, &retLocl ); + return retLocl; } bool bf_read::ReadBytes(void *pOut, int nBytes) diff --git a/sp/src/tier1/convar.cpp b/sp/src/tier1/convar.cpp index c49a6efbc4..9d60652073 100644 --- a/sp/src/tier1/convar.cpp +++ b/sp/src/tier1/convar.cpp @@ -594,7 +594,7 @@ void ConCommand::Dispatch( const CCommand &command ) } // Command without callback!!! - AssertMsg( 0, ( "Encountered ConCommand '%s' without a callback!\n", GetName() ) ); + AssertMsg( 0, "Encountered ConCommand '%s' without a callback!\n", GetName() ); } @@ -688,7 +688,10 @@ ConVar::~ConVar( void ) //----------------------------------------------------------------------------- void ConVar::InstallChangeCallback( FnChangeCallback_t callback ) { +#ifndef MAPBASE_VSCRIPT Assert( !m_pParent->m_fnChangeCallback || !callback ); +#endif + m_pParent->m_fnChangeCallback = callback; if ( m_pParent->m_fnChangeCallback ) diff --git a/sp/src/game/shared/interval.cpp b/sp/src/tier1/interval.cpp similarity index 100% rename from sp/src/game/shared/interval.cpp rename to sp/src/tier1/interval.cpp diff --git a/sp/src/tier1/mapbase_con_groups.cpp b/sp/src/tier1/mapbase_con_groups.cpp index cb01280e69..5f23b3f8b2 100644 --- a/sp/src/tier1/mapbase_con_groups.cpp +++ b/sp/src/tier1/mapbase_con_groups.cpp @@ -11,131 +11,157 @@ #include #include #include "basetypes.h" -#include "tier1/tier1.h" -#include "tier1/utldict.h" +#include "tier1.h" +#include "utldict.h" #include "Color.h" -#include "tier1/mapbase_con_groups.h" - -void CV_ColorChanged( IConVar *pConVar, const char *pOldString, float flOldValue ); +#include "mapbase_con_groups.h" +#include "KeyValues.h" +#include "filesystem.h" +#include "mapbase_matchers_base.h" struct ConGroup_t { - ConGroup_t( const char *_pszName, ConVar *pCvar ) + ConGroup_t( const char *_pszName, const char *_pszDescription ) { pszName = _pszName; - cvar = pCvar; + pszDescription = _pszDescription; + _clr.SetColor( 224, 224, 224, 255 ); // Default to a shade of gray } const Color &GetColor() { - if (_clr.a() == 0) - { - // Read the cvar - int clr[3]; - sscanf( cvar->GetString(), "%i %i %i", &clr[0], &clr[1], &clr[2] ); - _clr.SetColor( clr[0], clr[1], clr[2], 255 ); - } return _clr; } const char *pszName; + const char *pszDescription; Color _clr; - ConVar *cvar; - //bool bDisabled; + bool bDisabled; }; -//----------------------------------------------------------------------------- -// To define a console group, -//----------------------------------------------------------------------------- - -#define DEFINE_CON_GROUP_CVAR(name, def, desc) static ConVar con_group_##name##_color( "con_group_" #name "_color", def, FCVAR_ARCHIVE | FCVAR_REPLICATED, desc, CV_ColorChanged ) - -DEFINE_CON_GROUP_CVAR( mapbase_misc, "192 128 224", "Messages from misc. Mapbase functions, like map-specific files." ); -DEFINE_CON_GROUP_CVAR( physics, "159 209 159", "Messages from physics-related events." ); - -DEFINE_CON_GROUP_CVAR( inputoutput, "220 170 220", "Messages from I/O events. (these display in developer 2)" ); -DEFINE_CON_GROUP_CVAR( npc_ai, "240 160 200", "Messages from NPC AI, etc. which display at various verbse levels." ); -DEFINE_CON_GROUP_CVAR( npc_scripts, "255 115 215", "Messages from scripted_sequence, etc. (these display in developer 2)" ); -DEFINE_CON_GROUP_CVAR( choreo, "240 224 180", "Messages from choreographed scenes and response expressers. (these display in developer 1, 2, etc.)" ); - -DEFINE_CON_GROUP_CVAR( vscript, "192 224 240", "Internal messages from VScript not produced by the user's scripts." ); -DEFINE_CON_GROUP_CVAR( vscript_print, "80 186 255", "Messages from VScript's 'print' function." ); +// TODO: Something more reliable? +static bool g_bIncludeConGroupNames = false; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- -#define DEFINE_CON_GROUP(id, name, codename) { name, &con_group_##codename##_color } +//#define DEFINE_CON_GROUP(id, name, codename) { name, &con_group_##codename##_color } +#define DEFINE_CON_GROUP(id, name, description) { name, description } ConGroup_t g_ConGroups[CON_GROUP_MAX] = { // General - DEFINE_CON_GROUP( CON_GROUP_MAPBASE_MISC, "Mapbase misc.", mapbase_misc ), - DEFINE_CON_GROUP( CON_GROUP_PHYSICS, "Physics", physics ), + DEFINE_CON_GROUP( CON_GROUP_MAPBASE_MISC, "Mapbase misc.", "Messages from misc. Mapbase functions, like map-specific files." ), + DEFINE_CON_GROUP( CON_GROUP_PHYSICS, "Physics", "Messages from physics-related events." ), + DEFINE_CON_GROUP( CON_GROUP_IO_SYSTEM, "Entity IO", "Messages from I/O events. (these display in developer 2)" ), + DEFINE_CON_GROUP( CON_GROUP_RESPONSE_SYSTEM, "Response System", "Messages from the Response System, a library primarily used for NPC speech." ), - // Server - DEFINE_CON_GROUP( CON_GROUP_IO_SYSTEM, "Entity IO", inputoutput ), - DEFINE_CON_GROUP( CON_GROUP_NPC_AI, "NPC AI", npc_ai ), - DEFINE_CON_GROUP( CON_GROUP_NPC_SCRIPTS, "NPC scripts", npc_scripts ), - DEFINE_CON_GROUP( CON_GROUP_CHOREO, "Choreo", choreo ), + // Game + DEFINE_CON_GROUP( CON_GROUP_NPC_AI, "NPC AI", "Messages from NPC AI, etc. which display at various verbose levels." ), + DEFINE_CON_GROUP( CON_GROUP_NPC_SCRIPTS, "NPC scripts", "Messages from scripted_sequence, etc. (these display in developer 2)" ), + DEFINE_CON_GROUP( CON_GROUP_SPEECH_AI, "Speech AI", "Messages from response expressers. (these display in developer 1, 2, etc.)" ), + DEFINE_CON_GROUP( CON_GROUP_CHOREO, "Choreo", "Messages from choreographed scenes. (these display in developer 1, 2, etc.)" ), // VScript - DEFINE_CON_GROUP( CON_GROUP_VSCRIPT, "VScript", vscript ), - DEFINE_CON_GROUP( CON_GROUP_VSCRIPT_PRINT, "VScript print", vscript_print ), + DEFINE_CON_GROUP( CON_GROUP_VSCRIPT, "VScript", "Internal messages from VScript not produced by actual scripts." ), + DEFINE_CON_GROUP( CON_GROUP_VSCRIPT_PRINT, "VScript print", "Messages from VScript's 'print' function." ), }; -void CV_ColorChanged( IConVar *pConVar, const char *pOldString, float flOldValue ) +int FindConGroup( const char *pszName ) { for (int i = 0; i < CON_GROUP_MAX; i++) { - // Reset the alpha to indicate it needs to be refreshed - g_ConGroups[i]._clr[3] = 0; + if (Q_stricmp( pszName, g_ConGroups[i].pszName ) == 0) + return i; } + + return -1; } //----------------------------------------------------------------------------- +// Loads console groups //----------------------------------------------------------------------------- +void LoadConsoleGroupsFromFile( IBaseFileSystem *filesystem, const char *pszFileName, const char *pathID ) +{ + KeyValues *pGroupRoot = new KeyValues( "ConsoleGroups" ); + + pGroupRoot->LoadFromFile( filesystem, pszFileName, pathID ); + + KeyValues *pGroup = NULL; + for ( pGroup = pGroupRoot->GetFirstTrueSubKey(); pGroup; pGroup = pGroup->GetNextTrueSubKey() ) + { + int index = FindConGroup( pGroup->GetName() ); + if (index != -1) + { + Color msgClr = pGroup->GetColor( "MessageColor" ); -ConVar con_group_include_name( "con_group_include_name", "0", FCVAR_NONE, "Includes groups when printing." ); + // Make sure the color isn't 0,0,0,0 before assigning + if (msgClr.GetRawColor() != 0) + g_ConGroups[index]._clr = msgClr; -CON_COMMAND( con_group_list, "Prints a list of all console groups." ) + g_ConGroups[index].bDisabled = pGroup->GetBool( "Disabled", false ); + } + else + { + Warning( "Invalid console group %s (new groups should be defined in the code)\n", pGroup->GetName() ); + } + } + + pGroupRoot->deleteThis(); +} + +void InitConsoleGroups( IBaseFileSystem *filesystem ) +{ + LoadConsoleGroupsFromFile( filesystem, "scripts/mapbase_con_groups.txt", "MOD" ); + LoadConsoleGroupsFromFile( filesystem, "scripts/mod_con_groups.txt", "MOD" ); +} + +void PrintAllConsoleGroups() { Msg( "============================================================\n" ); for (int i = 0; i < CON_GROUP_MAX; i++) { - Msg( " # " ); - ConColorMsg( g_ConGroups[i].GetColor(), "%s", g_ConGroups[i].pszName ); - Msg( " - %s ", g_ConGroups[i].cvar->GetHelpText() ); + ConColorMsg( g_ConGroups[i].GetColor(), " # %s", g_ConGroups[i].pszName ); - //if (g_ConGroups[i].bDisabled) - // Msg("(DISABLED)"); + if (g_ConGroups[i].bDisabled) + Msg(" [DISABLED]"); + + Msg( " - %s ", g_ConGroups[i].pszDescription ); Msg("\n"); } Msg( "============================================================\n" ); } -// TODO: Figure out how this can be done without server/client disparity issues -/* -CON_COMMAND( con_group_toggle, "Toggles a console group." ) +void ToggleConsoleGroups( const char *pszQuery ) { - const char *pszGroup = args.Arg( 1 ); + bool bMatched = false; + for (int i = 0; i < ARRAYSIZE( g_ConGroups ); i++) { - if (V_stricmp(pszGroup, g_ConGroups[i].pszName) == 0) + if (Matcher_NamesMatch( pszQuery, g_ConGroups[i].pszName )) { Msg( "%s is now %s\n", g_ConGroups[i].pszName, g_ConGroups[i].bDisabled ? "enabled" : "disabled" ); g_ConGroups[i].bDisabled = !g_ConGroups[i].bDisabled; - return; + bMatched = true; } } - Msg( "No group named \"%s\"\n", pszGroup ); + if (!bMatched) + Msg( "No groups matching \"%s\"\n", pszQuery ); +} + +void SetConsoleGroupIncludeNames( bool bToggle ) +{ + g_bIncludeConGroupNames = bToggle; } -*/ -void CGMsg( int level, int nGroup, const tchar* pMsg, ... ) +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +void CGMsg( int level, ConGroupID_t nGroup, const tchar* pMsg, ... ) { // Return early if we're not at this level if (!IsSpewActive("developer", level)) @@ -152,16 +178,16 @@ void CGMsg( int level, int nGroup, const tchar* pMsg, ... ) ConGroup_t *pGroup = &g_ConGroups[nGroup]; - /*if (pGroup->bDisabled) + if (pGroup->bDisabled) { - // Do nothing + // Do nothing } - else*/ if (con_group_include_name.GetBool()) + else if (g_bIncludeConGroupNames) { ConColorMsg(level, pGroup->GetColor(), "[%s] %s", pGroup->pszName, string); } else { - ConColorMsg(level, pGroup->GetColor(), string); + ConColorMsg(level, pGroup->GetColor(), "%s", string); } } diff --git a/sp/src/tier1/mapbase_matchers_base.cpp b/sp/src/tier1/mapbase_matchers_base.cpp new file mode 100644 index 0000000000..5f10814e77 --- /dev/null +++ b/sp/src/tier1/mapbase_matchers_base.cpp @@ -0,0 +1,217 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ================= +// +// Purpose: General matching functions for things like wildcards and !=. +// +// $NoKeywords: $ +//============================================================================= + +#include "mapbase_matchers_base.h" +#include "convar.h" + +// glibc (Linux) uses these tokens when including , so we must not #define them +#undef max +#undef min +#include +#undef MINMAX_H +#include "minmax.h" + +ConVar mapbase_wildcards_enabled("mapbase_wildcards_enabled", "1", FCVAR_NONE, "Toggles Mapbase's '?' wildcard and true '*' features. Useful for maps that have '?' in their targetnames."); +ConVar mapbase_regex_enabled("mapbase_regex_enabled", "1", FCVAR_NONE, "Toggles Mapbase's regex matching handover."); + +//============================================================================= +// These are the "matchers" that compare with wildcards ("any*" for text starting with "any") +// and operators (<3 for numbers less than 3). +// +// Matcher_Regex - Uses regex functions from the std library. +// Matcher_NamesMatch - Based on Valve's original NamesMatch function, using wildcards and regex. +// +// AppearsToBeANumber - Response System-based function which checks if the string might be a number. +//============================================================================= + +// The recursive part of Mapbase's modified version of Valve's NamesMatch(). +bool Matcher_RunCharCompare(const char *pszQuery, const char *szValue) +{ + // This matching model is based off of the ASW SDK + while ( *szValue && *pszQuery ) + { + char cName = *szValue; + char cQuery = *pszQuery; + if ( cName != cQuery && tolower(cName) != tolower(cQuery) ) // people almost always use lowercase, so assume that first + { + // Now we'll try the new and improved Mapbase wildcards! + switch (*pszQuery) + { + case '*': + { + // Return true at classic trailing * + if ( *(pszQuery+1) == 0 ) + return true; + + if (mapbase_wildcards_enabled.GetBool()) + { + // There's text after this * which we need to test. + // This recursion allows for multiple wildcards + int vlen = Q_strlen(szValue); + ++pszQuery; + for (int i = 0; i < vlen; i++) + { + if (Matcher_RunCharCompare(pszQuery, szValue + i)) + return true; + } + } + return false; + } break; + case '?': + // Just skip if we're capable of lazy wildcards + if (mapbase_wildcards_enabled.GetBool()) + break; + default: + return false; + } + } + ++szValue; + ++pszQuery; + } + + // Include a classic trailing * check for when szValue is something like "value" and pszQuery is "value*" + return ( ( *pszQuery == 0 && *szValue == 0 ) || *pszQuery == '*' ); +} + +// Regular expressions based off of the std library. +// The C++ is strong in this one. +bool Matcher_Regex(const char *pszQuery, const char *szValue) +{ + std::regex regex; + + // Since I can't find any other way to check for valid regex, + // use a try-catch here to see if it throws an exception. + try { regex = std::regex(pszQuery); } + catch (std::regex_error &e) + { + Msg("Invalid regex \"%s\" (%s)\n", pszQuery, e.what()); + return false; + } + + std::match_results results; + bool bMatch = std::regex_match( szValue, results, regex ); + if (!bMatch) + return false; + + // Only match the *whole* string + return Q_strlen(results.str(0).c_str()) == Q_strlen(szValue); +} + +// The entry point for Mapbase's modified version of Valve's NamesMatch(). +bool Matcher_NamesMatch(const char *pszQuery, const char *szValue) +{ + if ( szValue == NULL ) + return (*pszQuery == 0 || *pszQuery == '*'); + + // If the pointers are identical, we're identical + if ( szValue == pszQuery ) + return true; + + // Check for regex + if ( *pszQuery == '@' && mapbase_regex_enabled.GetBool() ) + { + // Make sure it has a forward slash + // (prevents confusion with instance fixup escape) + if (*(pszQuery+1) == '/') + { + return Matcher_Regex( pszQuery+2, szValue ); + } + } + + return Matcher_RunCharCompare( pszQuery, szValue ); +} + +bool Matcher_NamesMatch_Classic(const char *pszQuery, const char *szValue) +{ + if ( szValue == NULL ) + return (!pszQuery || *pszQuery == 0 || *pszQuery == '*'); + + // If the pointers are identical, we're identical + if ( szValue == pszQuery ) + return true; + + while ( *szValue && *pszQuery ) + { + unsigned char cName = *szValue; + unsigned char cQuery = *pszQuery; + // simple ascii case conversion + if ( cName == cQuery ) + ; + else if ( cName - 'A' <= (unsigned char)'Z' - 'A' && cName - 'A' + 'a' == cQuery ) + ; + else if ( cName - 'a' <= (unsigned char)'z' - 'a' && cName - 'a' + 'A' == cQuery ) + ; + else + break; + ++szValue; + ++pszQuery; + } + + if ( *pszQuery == 0 && *szValue == 0 ) + return true; + + // @TODO (toml 03-18-03): Perhaps support real wildcards. Right now, only thing supported is trailing * + if ( *pszQuery == '*' ) + return true; + + return false; +} + +bool Matcher_NamesMatch_MutualWildcard(const char *pszQuery, const char *szValue) +{ + if ( szValue == NULL ) + return (!pszQuery || *pszQuery == 0 || *pszQuery == '*'); + + if ( pszQuery == NULL ) + return (!szValue || *szValue == 0 || *szValue == '*'); + + // If the pointers are identical, we're identical + if ( szValue == pszQuery ) + return true; + + while ( *szValue && *pszQuery ) + { + unsigned char cName = *szValue; + unsigned char cQuery = *pszQuery; + // simple ascii case conversion + if ( cName == cQuery ) + ; + else if ( cName - 'A' <= (unsigned char)'Z' - 'A' && cName - 'A' + 'a' == cQuery ) + ; + else if ( cName - 'a' <= (unsigned char)'z' - 'a' && cName - 'a' + 'A' == cQuery ) + ; + else + break; + ++szValue; + ++pszQuery; + } + + if ( *pszQuery == 0 && *szValue == 0 ) + return true; + + // @TODO (toml 03-18-03): Perhaps support real wildcards. Right now, only thing supported is trailing * + if ( *pszQuery == '*' || *szValue == '*' ) + return true; + + return false; +} + +// Matcher_Compare is a deprecated alias originally used when Matcher_Match didn't support wildcards. +/* +bool Matcher_Compare(const char *pszQuery, const char *szValue) +{ + return Matcher_Match(pszQuery, szValue); +#if 0 + // I have to do this so wildcards could test *before* the response system comparison. + // I know it removes the operators twice, but I won't worry about it. + bool match = Matcher_NamesMatch(Matcher_RemoveOperators(pszQuery), szValue); + if (match) + return Matcher_Match(pszQuery, szValue); + return false; +#endif +} +*/ diff --git a/sp/src/tier1/snappy-stubs-internal.h b/sp/src/tier1/snappy-stubs-internal.h index ec5d10189e..7db8b931f1 100644 --- a/sp/src/tier1/snappy-stubs-internal.h +++ b/sp/src/tier1/snappy-stubs-internal.h @@ -138,7 +138,7 @@ class LogMessage { class LogMessageCrash : public LogMessage { public: LogMessageCrash() { } -#if _MSC_VER == 1700 || _MSC_VER == 1800 +#if _MSC_VER >= 1700 // Bogus warning from VS 2012 and VS 2013: // warning C4722: 'snappy::LogMessageCrash::~LogMessageCrash' : destructor never returns, potential memory leak #pragma warning(push) diff --git a/sp/src/tier1/strtools.cpp b/sp/src/tier1/strtools.cpp index 9b1bfa842f..62d31c159e 100644 --- a/sp/src/tier1/strtools.cpp +++ b/sp/src/tier1/strtools.cpp @@ -1420,7 +1420,7 @@ int V_UCS2ToUnicode( const ucs2 *pUCS2, wchar_t *pUnicode, int cubDestSizeInByte size_t nMaxUTF8 = cubDestSizeInBytes; char *pIn = (char *)pUCS2; char *pOut = (char *)pUnicode; - if ( conv_t > 0 ) + if ( conv_t ) { cchResult = 0; cchResult = iconv( conv_t, &pIn, &nLenUnicde, &pOut, &nMaxUTF8 ); @@ -1461,7 +1461,7 @@ int V_UnicodeToUCS2( const wchar_t *pUnicode, int cubSrcInBytes, char *pUCS2, in size_t nMaxUCS2 = cubDestSizeInBytes; char *pIn = (char*)pUnicode; char *pOut = pUCS2; - if ( conv_t > 0 ) + if ( conv_t ) { cchResult = 0; cchResult = iconv( conv_t, &pIn, &nLenUnicde, &pOut, &nMaxUCS2 ); @@ -1508,7 +1508,7 @@ int V_UCS2ToUTF8( const ucs2 *pUCS2, char *pUTF8, int cubDestSizeInBytes ) size_t nMaxUTF8 = cubDestSizeInBytes - 1; char *pIn = (char *)pUCS2; char *pOut = (char *)pUTF8; - if ( conv_t > 0 ) + if ( conv_t ) { cchResult = 0; const size_t nBytesToWrite = nMaxUTF8; @@ -1554,7 +1554,7 @@ int V_UTF8ToUCS2( const char *pUTF8, int cubSrcInBytes, ucs2 *pUCS2, int cubDest size_t nMaxUTF8 = cubDestSizeInBytes; char *pIn = (char *)pUTF8; char *pOut = (char *)pUCS2; - if ( conv_t > 0 ) + if ( conv_t ) { cchResult = 0; cchResult = iconv( conv_t, &pIn, &nLenUnicde, &pOut, &nMaxUTF8 ); diff --git a/sp/src/tier1/tier1.vpc b/sp/src/tier1/tier1.vpc index d7012f3ee5..3d47526acf 100644 --- a/sp/src/tier1/tier1.vpc +++ b/sp/src/tier1/tier1.vpc @@ -39,6 +39,7 @@ $Project "tier1" $File "generichash.cpp" $File "ilocalize.cpp" $File "interface.cpp" + $File "interval.cpp" $File "KeyValues.cpp" $File "kvpacker.cpp" $File "lzmaDecoder.cpp" @@ -77,6 +78,7 @@ $Project "tier1" $File "snappy-sinksource.cpp" $File "snappy-stubs-internal.cpp" $File "mapbase_con_groups.cpp" [$MAPBASE] + $File "mapbase_matchers_base.cpp" [$MAPBASE] } $Folder "Header Files" @@ -149,6 +151,7 @@ $Project "tier1" $File "$SRCDIR\public\tier1\utlsymbollarge.h" $File "$SRCDIR\public\tier1\utlvector.h" $File "$SRCDIR\public\tier1\mapbase_con_groups.h" [$MAPBASE] + $File "$SRCDIR\public\tier1\mapbase_matchers_base.h" [$MAPBASE] $File "$SRCDIR\common\xbox\xboxstubs.h" [$WINDOWS] } } diff --git a/sp/src/utils/captioncompiler/captioncompiler.vpc b/sp/src/utils/captioncompiler/captioncompiler.vpc index da5a3b4e2e..3d1e24adfb 100644 --- a/sp/src/utils/captioncompiler/captioncompiler.vpc +++ b/sp/src/utils/captioncompiler/captioncompiler.vpc @@ -14,7 +14,8 @@ $Configuration $Compiler { $AdditionalIncludeDirectories "$BASE,..\common,$SRCDIR\game\shared,.\" - $PreprocessorDefinitions "$BASE;captioncompiler" + $PreprocessorDefinitions "$BASE;PROTECTED_THINGS_DISABLE;captioncompiler" [($VS2015||$VS2017||$VS2019||$VS2022)] + $PreprocessorDefinitions "$BASE;captioncompiler" [!($VS2015||$VS2017||$VS2019||$VS2022)] } } diff --git a/sp/src/utils/vbsp/map.cpp b/sp/src/utils/vbsp/map.cpp index f59305badd..c479b27949 100644 --- a/sp/src/utils/vbsp/map.cpp +++ b/sp/src/utils/vbsp/map.cpp @@ -2105,7 +2105,12 @@ void CMapFile::CheckForInstances( const char *pszFileName ) } char FDGPath[ MAX_PATH ]; +#ifdef MAPBASE + // Mapbase's FGD would be in a MOD path + if ( !g_pFullFileSystem->RelativePathToFullPath( GameDataFile, "MOD", FDGPath, sizeof( FDGPath ) ) ) +#else if ( !g_pFullFileSystem->RelativePathToFullPath( GameDataFile, "EXECUTABLE_PATH", FDGPath, sizeof( FDGPath ) ) ) +#endif { if ( !g_pFullFileSystem->RelativePathToFullPath( GameDataFile, NULL, FDGPath, sizeof( FDGPath ) ) ) { @@ -2598,6 +2603,27 @@ void CMapFile::MergeEntities( entity_t *pInstanceEntity, CMapFile *Instance, Vec SetKeyValue( entity, szKey, epInstance->value ); } } + + // If the parent instance is within a relative path and no file relative to the main map exists, change it to be relative to the parent + char *pParentInstanceFile = ValueForKey( pInstanceEntity, "file" ); + if ( pParentInstanceFile[ 0 ] && (strchr( pParentInstanceFile, '\\' ) || strchr( pParentInstanceFile, '/' )) ) + { + char *pInstanceFile = ValueForKey( entity, "file" ); + if ( pInstanceFile[ 0 ] ) + { + char InstancePath[ MAX_PATH ]; + + if ( !DeterminePath( g_MainMapPath, pInstanceFile, InstancePath ) ) + { + strcpy( InstancePath, pParentInstanceFile ); + V_StripFilename( InstancePath ); + V_strncat( InstancePath, "\\", sizeof( InstancePath ) ); + V_strncat( InstancePath, pInstanceFile, sizeof( InstancePath ) ); + + SetKeyValue( entity, "file", InstancePath ); + } + } + } } #endif } @@ -2606,7 +2632,7 @@ void CMapFile::MergeEntities( entity_t *pInstanceEntity, CMapFile *Instance, Vec Msg( "Instance Entity %d remapped to %d\n", i, num_entities + i ); Msg( " FirstBrush: from %d to %d\n", Instance->entities[ i ].firstbrush, entity->firstbrush ); Msg( " KV Pairs:\n" ); - for ( epair_t *ep = entity->epairs; ep->next != NULL; ep = ep->next ) + for ( epair_t *ep = entity->epairs; ep != NULL; ep = ep->next ) { Msg( " %s %s\n", ep->key, ep->value ); } @@ -2801,14 +2827,12 @@ bool LoadMapFile( const char *pszFileName ) #ifdef MAPBASE_VSCRIPT if ( g_pScriptVM ) { - HSCRIPT hFunc = g_pScriptVM->LookupFunction( "OnMapLoaded" ); - if ( hFunc ) + if (CMapFile::g_Hook_OnMapLoaded.CanRunInScope( NULL )) { // Use GetLoadingMap() //g_pScriptVM->SetValue( "map", g_LoadingMap->GetScriptInstance() ); - g_pScriptVM->Call( hFunc ); - g_pScriptVM->ReleaseFunction( hFunc ); + CMapFile::g_Hook_OnMapLoaded.Call( NULL, NULL, NULL ); //g_pScriptVM->ClearValue( "map" ); } diff --git a/sp/src/utils/vbsp/staticprop.cpp b/sp/src/utils/vbsp/staticprop.cpp index 6c643413fd..a08ab4e27f 100644 --- a/sp/src/utils/vbsp/staticprop.cpp +++ b/sp/src/utils/vbsp/staticprop.cpp @@ -565,6 +565,10 @@ static void SetLumpData( ) void EmitStaticProps() { +#ifdef MAPBASE + Msg("Placing static props...\n"); +#endif + CreateInterfaceFn physicsFactory = GetPhysicsFactory(); if ( physicsFactory ) { @@ -588,13 +592,43 @@ void EmitStaticProps() for ( i = 0; i < num_entities; ++i) { char* pEntity = ValueForKey(&entities[i], "classname"); +#ifdef MAPBASE + const int iInsertAsStatic = IntForKey( &entities[i], "insertasstaticprop" ); // If the key is absent, IntForKey will return 0. + bool bInsertAsStatic = g_bPropperInsertAllAsStatic; + + // 1 = No, 2 = Yes; Any other number will just use what g_bPropperInsertAllAsStatic is set as. + if ( iInsertAsStatic == 1 ) { bInsertAsStatic = false; } + else if ( iInsertAsStatic == 2 ) { bInsertAsStatic = true; } + + if ( !strcmp( pEntity, "static_prop" ) || !strcmp( pEntity, "prop_static" ) || ( !strcmp( pEntity, "propper_model" ) && bInsertAsStatic ) ) +#else if (!strcmp(pEntity, "static_prop") || !strcmp(pEntity, "prop_static")) +#endif { StaticPropBuild_t build; GetVectorForKey( &entities[i], "origin", build.m_Origin ); GetAnglesForKey( &entities[i], "angles", build.m_Angles ); +#ifdef MAPBASE + if ( !strcmp( pEntity, "propper_model" ) ) + { + char* pModelName = ValueForKey( &entities[i], "modelname" ); + + // The modelname keyvalue lacks 'models/' at the start and '.mdl' at the end, so we have to add them. + char modelpath[MAX_VALUE]; + sprintf( modelpath, "models/%s.mdl", pModelName ); + + Msg( "Inserting propper_model (%.0f %.0f %.0f) as prop_static: %s\n", build.m_Origin[0], build.m_Origin[1], build.m_Origin[2], modelpath ); + + build.m_pModelName = modelpath; + } + else // Otherwise we just assume it's a normal prop_static + { + build.m_pModelName = ValueForKey( &entities[i], "model" ); + } +#else build.m_pModelName = ValueForKey( &entities[i], "model" ); +#endif build.m_Solid = IntForKey( &entities[i], "solid" ); build.m_Skin = IntForKey( &entities[i], "skin" ); build.m_FadeMaxDist = FloatForKey( &entities[i], "fademaxdist" ); @@ -651,6 +685,13 @@ void EmitStaticProps() // strip this ent from the .bsp file entities[i].epairs = 0; } +#ifdef MAPBASE + else if ( g_bPropperStripEntities && !strncmp( pEntity, "propper_", 8 ) ) // Strip out any entities with 'propper_' in their classname, as they don't actually exist in-game. + { + Warning( "Not including %s in BSP compile due to it being a propper entity that isn't used in-game.\n", pEntity ); + entities[i].epairs = 0; + } +#endif } // Strip out lighting origins; has to be done here because they are used when diff --git a/sp/src/utils/vbsp/vbsp.cpp b/sp/src/utils/vbsp/vbsp.cpp index 21b9db8479..75722bfee2 100644 --- a/sp/src/utils/vbsp/vbsp.cpp +++ b/sp/src/utils/vbsp/vbsp.cpp @@ -69,6 +69,8 @@ bool g_bNoHiddenManifestMaps = false; #ifdef MAPBASE bool g_bNoDefaultCubemaps = true; bool g_bSkyboxCubemaps = false; +bool g_bPropperInsertAllAsStatic = false; +bool g_bPropperStripEntities = false; int g_iDefaultCubemapSize = 32; #endif #ifdef MAPBASE_VSCRIPT @@ -1193,6 +1195,14 @@ int RunVBSP( int argc, char **argv ) Msg( "Default cubemap size = %i\n", g_iDefaultCubemapSize ); i++; } + else if ( !Q_stricmp( argv[i], "-defaultproppermodelsstatic" ) ) + { + g_bPropperInsertAllAsStatic = true; + } + else if ( !Q_stricmp( argv[i], "-strippropperentities" ) ) + { + g_bPropperStripEntities = true; + } #endif #ifdef MAPBASE_VSCRIPT else if ( !Q_stricmp( argv[i], "-scripting" ) ) diff --git a/sp/src/utils/vbsp/vbsp.h b/sp/src/utils/vbsp/vbsp.h index 3e9f44c022..9147876aef 100644 --- a/sp/src/utils/vbsp/vbsp.h +++ b/sp/src/utils/vbsp/vbsp.h @@ -347,6 +347,8 @@ class CMapFile #ifdef MAPBASE_VSCRIPT HSCRIPT GetScriptInstance(); + static ScriptHook_t g_Hook_OnMapLoaded; + // VScript functions ALLOW_SCRIPT_ACCESS(); private: @@ -401,6 +403,10 @@ extern bool g_DisableWaterLighting; extern bool g_bAllowDetailCracks; extern bool g_bNoVirtualMesh; extern bool g_bNoHiddenManifestMaps; +#ifdef MAPBASE +extern bool g_bPropperInsertAllAsStatic; +extern bool g_bPropperStripEntities; +#endif extern char outbase[32]; extern char source[1024]; diff --git a/sp/src/utils/vbsp/vscript_vbsp.cpp b/sp/src/utils/vbsp/vscript_vbsp.cpp index c8295e0205..bcb94845e7 100644 --- a/sp/src/utils/vbsp/vscript_vbsp.cpp +++ b/sp/src/utils/vbsp/vscript_vbsp.cpp @@ -43,6 +43,9 @@ extern ScriptClassDesc_t * GetScriptDesc( CBaseEntity * ); extern int vscript_token; int vscript_token_hack = vscript_token; +// HACKHACK: VScript library relies on developer convar existing +ConVar developer( "developer", "1", 0, "Set developer message level." ); // developer mode + HSCRIPT VScriptCompileScript( const char *pszScriptName, bool bWarnMissing ) { if ( !g_pScriptVM ) @@ -166,6 +169,8 @@ bool VScriptRunScript( const char *pszScriptName, HSCRIPT hScope, bool bWarnMiss return bSuccess; } +ScriptHook_t CMapFile::g_Hook_OnMapLoaded; + BEGIN_SCRIPTDESC_ROOT( CMapFile, "Map file" ) DEFINE_SCRIPTFUNC( GetMins, "Get the map's mins." ) @@ -182,6 +187,11 @@ BEGIN_SCRIPTDESC_ROOT( CMapFile, "Map file" ) DEFINE_SCRIPTFUNC( GetNumEntities, "Get the number of entities in the map." ) + // + // Hooks + // + DEFINE_SIMPLE_SCRIPTHOOK( CMapFile::g_Hook_OnMapLoaded, "OnMapLoaded", FIELD_VOID, "Called when the NPC is deciding whether to hear a CSound or not." ) + END_SCRIPTDESC(); diff --git a/sp/src/vgui2/vgui_controls/AnimationController.cpp b/sp/src/vgui2/vgui_controls/AnimationController.cpp index 6da33a38d8..886b4291a4 100644 --- a/sp/src/vgui2/vgui_controls/AnimationController.cpp +++ b/sp/src/vgui2/vgui_controls/AnimationController.cpp @@ -31,6 +31,11 @@ using namespace vgui; static CUtlSymbolTable g_ScriptSymbols(0, 128, true); +#ifdef MAPBASE +// Allows animation sequences to be overridden by map-specific files +extern bool g_bUsingCustomHudAnimations; +#endif + // singleton accessor for animation controller for use by the vgui controls namespace vgui { @@ -317,11 +322,35 @@ bool AnimationController::ParseScriptFile(char *pMem, int length) return false; } - int seqIndex; + int seqIndex = -1; UtlSymId_t nameIndex = g_ScriptSymbols.AddString(token); - // Create a new sequence - seqIndex = m_Sequences.AddToTail(); +#ifdef MAPBASE + if (g_bUsingCustomHudAnimations) + { + // look through for the sequence + for (seqIndex = 0; seqIndex < m_Sequences.Count(); seqIndex++) + { + if (m_Sequences[seqIndex].name == nameIndex) + break; + } + + if (seqIndex >= m_Sequences.Count()) + seqIndex = -1; + else + { + // Clear some stuff + m_Sequences[seqIndex].cmdList.RemoveAll(); + } + } + + if (seqIndex == -1) +#endif + { + // Create a new sequence + seqIndex = m_Sequences.AddToTail(); + } + AnimSequence_t &seq = m_Sequences[seqIndex]; seq.name = nameIndex; seq.duration = 0.0f; diff --git a/sp/src/vgui2/vgui_controls/Button.cpp b/sp/src/vgui2/vgui_controls/Button.cpp index cceb804354..406c3e1385 100644 --- a/sp/src/vgui2/vgui_controls/Button.cpp +++ b/sp/src/vgui2/vgui_controls/Button.cpp @@ -695,12 +695,12 @@ void Button::SetMouseClickEnabled(MouseCode code,bool state) if(state) { //set bit to 1 - _mouseClickMask|=1<<((int)(code+1)); + _mouseClickMask|=MouseButtonBit(code); } else { //set bit to 0 - _mouseClickMask&=~(1<<((int)(code+1))); + _mouseClickMask&=~MouseButtonBit(code); } } @@ -709,7 +709,7 @@ void Button::SetMouseClickEnabled(MouseCode code,bool state) //----------------------------------------------------------------------------- bool Button::IsMouseClickEnabled(MouseCode code) { - if(_mouseClickMask&(1<<((int)(code+1)))) + if(_mouseClickMask&MouseButtonBit(code)) { return true; } diff --git a/sp/src/vgui2/vgui_controls/Panel.cpp b/sp/src/vgui2/vgui_controls/Panel.cpp index 499296d94e..dd49f97a3a 100644 --- a/sp/src/vgui2/vgui_controls/Panel.cpp +++ b/sp/src/vgui2/vgui_controls/Panel.cpp @@ -64,6 +64,13 @@ static char *CopyString( const char *in ) return n; } +#ifdef MAPBASE +ConVar vgui_mapbase_custom_schemes( "vgui_mapbase_custom_schemes", "1" ); + +// This is used in mapbase_shared.cpp +HScheme g_iCustomClientSchemeOverride; +#endif + #if defined( VGUI_USEDRAGDROP ) //----------------------------------------------------------------------------- // Purpose: @@ -1612,17 +1619,31 @@ void Panel::DeletePanel() //----------------------------------------------------------------------------- HScheme Panel::GetScheme() { + HScheme iScheme; + if (m_iScheme) { - return m_iScheme; // return our internal scheme + iScheme = m_iScheme; // return our internal scheme } - - if (GetVParent()) // recurse down the heirarchy + else if (GetVParent()) // recurse down the heirarchy + { + iScheme = ipanel()->GetScheme(GetVParent()); + } + else + { + iScheme = scheme()->GetDefaultScheme(); + } + +#ifdef MAPBASE + // If a custom client scheme is available, use the custom scheme. + // TODO: Need a better way to detect that this panel actually uses ClientScheme.res + if (g_iCustomClientSchemeOverride != 0 && iScheme == scheme()->GetScheme( "ClientScheme" ) && vgui_mapbase_custom_schemes.GetBool()) { - return ipanel()->GetScheme(GetVParent()); + return g_iCustomClientSchemeOverride; } +#endif - return scheme()->GetDefaultScheme(); + return iScheme; } //----------------------------------------------------------------------------- @@ -8510,7 +8531,7 @@ void VguiPanelGetSortedChildPanelList( Panel *pParentPanel, void *pSortedPanels } } -void VguiPanelGetSortedChildButtonList( Panel *pParentPanel, void *pSortedPanels, char *pchFilter /*= NULL*/, int nFilterType /*= 0*/ ) +void VguiPanelGetSortedChildButtonList( Panel *pParentPanel, void *pSortedPanels, const char *pchFilter /*= NULL*/, int nFilterType /*= 0*/ ) { CUtlSortVector< SortedPanel_t, CSortedPanelYLess > *pList = reinterpret_cast< CUtlSortVector< SortedPanel_t, CSortedPanelYLess >* >( pSortedPanels ); diff --git a/sp/src/vgui2/vgui_controls/ScrollBarSlider.cpp b/sp/src/vgui2/vgui_controls/ScrollBarSlider.cpp index 32df2fae66..6f127558c8 100644 --- a/sp/src/vgui2/vgui_controls/ScrollBarSlider.cpp +++ b/sp/src/vgui2/vgui_controls/ScrollBarSlider.cpp @@ -18,7 +18,9 @@ #include #include +#if _MSC_VER < 1900 #include +#endif // memdbgon must be the last include file in a .cpp file!!! #include diff --git a/sp/src/vgui2/vgui_controls/Tooltip.cpp b/sp/src/vgui2/vgui_controls/Tooltip.cpp index e18427eb96..7f9738d2ae 100644 --- a/sp/src/vgui2/vgui_controls/Tooltip.cpp +++ b/sp/src/vgui2/vgui_controls/Tooltip.cpp @@ -6,8 +6,8 @@ // and implement another button here. //=============================================================================// -#include -#define PROTECTED_THINGS_DISABLE +//#include +//#define PROTECTED_THINGS_DISABLE #include #include diff --git a/sp/src/vgui2/vgui_controls/TreeView.cpp b/sp/src/vgui2/vgui_controls/TreeView.cpp index b7ad4d3b84..c244edebdd 100644 --- a/sp/src/vgui2/vgui_controls/TreeView.cpp +++ b/sp/src/vgui2/vgui_controls/TreeView.cpp @@ -418,7 +418,7 @@ class TreeNode : public Panel public: TreeNode(Panel *parent, TreeView *pTreeView); - ~TreeNode(); + virtual ~TreeNode(); void SetText(const char *pszText); void SetFont(HFont font); void SetKeyValues(KeyValues *data); diff --git a/sp/src/vpc_scripts/groups.vgc b/sp/src/vpc_scripts/groups.vgc index 7354a28ef3..2ab187faaf 100644 --- a/sp/src/vpc_scripts/groups.vgc +++ b/sp/src/vpc_scripts/groups.vgc @@ -23,6 +23,7 @@ $Group "game" "tier1" "vgui_controls" "vscript" + "responserules" } $Group "shaders" @@ -41,6 +42,7 @@ $Group "everything" "mathlib" "motionmapper" "phonemeextractor" + "responserules" "qc_eyes" "raytrace" "server" @@ -66,3 +68,17 @@ $Group "dedicated" "tier1" } +$Group "maptools" +{ + "vbsp" + "vrad_dll" + "vrad_launcher" + "vvis_dll" + "vvis_launcher" + "fgdlib" + "mathlib" + "raytrace" + "tier1" + "vscript" +} + diff --git a/sp/src/vpc_scripts/newer_vs_toolsets.vpc b/sp/src/vpc_scripts/newer_vs_toolsets.vpc new file mode 100644 index 0000000000..950e16772f --- /dev/null +++ b/sp/src/vpc_scripts/newer_vs_toolsets.vpc @@ -0,0 +1,24 @@ +//----------------------------------------------------------------------------- +// NEWER_VS_TOOLSETS.VPC +// +// Additional toolsets added by Mapbase. +// +// NOTE: Enabling any of these makes the solution incompatible with Visual Studio 2013! +// +//----------------------------------------------------------------------------- + +$Conditional VS2015 "0" // Toggles Visual Studio 2015 (v140) toolset +$Conditional VS2017 "0" // Toggles Visual Studio 2017 (v141) toolset +$Conditional VS2019 "0" // Toggles Visual Studio 2019 (v142) toolset +$Conditional VS2022 "0" // Toggles Visual Studio 2022 (v143) toolset + +// +// VPC may still say "Generating for Visual Studio 2013" even when using one of the above toolsets. This message is irrelevant and can be ignored. +// +// The following projects currently do not compile with any of the above toolsets and are not included in their solutions: +// +// - phonemeextractor (may be fixable with modification) +// - qc_eyes (might be fixed by having C++ MFC for v141 build tools and/or C++ ATL for v141 build tools installed) +// + +//----------------------------------------------------------------------------- diff --git a/sp/src/vpc_scripts/projects.vgc b/sp/src/vpc_scripts/projects.vgc index 17f0440e78..8ea011478b 100644 --- a/sp/src/vpc_scripts/projects.vgc +++ b/sp/src/vpc_scripts/projects.vgc @@ -46,6 +46,11 @@ $Project "server" "game\server\server_episodic.vpc" [($WIN32||$X360||$POSIX) && $EPISODIC] } +$Project "responserules" +{ + "responserules\runtime\response_rules.vpc" [($WINDOWS||$X360||$POSIX) && $NEW_RESPONSE_SYSTEM] +} + $Project "mathlib" { "mathlib\mathlib.vpc" [$WINDOWS||$X360||$POSIX] @@ -58,7 +63,7 @@ $Project "motionmapper" $Project "phonemeextractor" { - "utils\phonemeextractor\phonemeextractor.vpc" [$WIN32] + "utils\phonemeextractor\phonemeextractor.vpc" [$WIN32 && !($VS2015||$VS2017||$VS2019||$VS2022)] // Not currently working with newer toolsets; may be fixable with modification } $Project "raytrace" @@ -68,7 +73,7 @@ $Project "raytrace" $Project "qc_eyes" { - "utils\qc_eyes\qc_eyes.vpc" [$WIN32] + "utils\qc_eyes\qc_eyes.vpc" [$WIN32 && !($VS2015||$VS2017||$VS2019||$VS2022)] // Not currently working with newer toolsets; might be fixed by having C++ MFC for v141 build tools and/or C++ ATL for v141 build tools installed } $Project "serverplugin_empty" diff --git a/sp/src/vpc_scripts/source_base.vpc b/sp/src/vpc_scripts/source_base.vpc index acf7c9b85f..fac51e1d45 100644 --- a/sp/src/vpc_scripts/source_base.vpc +++ b/sp/src/vpc_scripts/source_base.vpc @@ -16,14 +16,13 @@ //$Conditional TF_BETA "1" //----------------------------------------------------------------------------- -// Mapbase conditional, equivalent to (and required for) our MAPBASE preprocessor defined below -$Conditional MAPBASE "1" -// Toggles Mapbase's RPC implementation -$Conditional MAPBASE_RPC "1" +// Mapbase functionality conditionals +$Conditional MAPBASE "1" // Equivalent to (and required for) our MAPBASE preprocessor defined below +$Conditional MAPBASE_RPC "1" // Toggles Mapbase's Rich Presence Client implementations +$Conditional MAPBASE_VSCRIPT "1" // Toggles VScript implementation (note: interfaces still exist, just the provided implementation is not present) +$Conditional NEW_RESPONSE_SYSTEM "1" // Toggles the new Response System library based on the Alien Swarm SDK -// Toggles VScript implementation (note: interfaces still exist, just the provided implementation is not present) -$Conditional MAPBASE_VSCRIPT "1" //----------------------------------------------------------------------------- $Configuration "Debug" diff --git a/sp/src/vpc_scripts/source_dll_win32_base.vpc b/sp/src/vpc_scripts/source_dll_win32_base.vpc index 4b2a2852e1..e69456ea77 100644 --- a/sp/src/vpc_scripts/source_dll_win32_base.vpc +++ b/sp/src/vpc_scripts/source_dll_win32_base.vpc @@ -39,12 +39,14 @@ $Configuration $Compiler [$WIN32] { - $EnableEnhancedInstructionSet "Streaming SIMD Extensions (/arch:SSE)" + $EnableEnhancedInstructionSet "Streaming SIMD Extensions (/arch:SSE)" [!($VS2015||$VS2017||$VS2019||$VS2022)] + $EnableEnhancedInstructionSet "Streaming SIMD Extensions 2 (/arch:SSE2)" [($VS2015||$VS2017||$VS2019||$VS2022)] } $Linker { - $AdditionalDependencies "$BASE shell32.lib user32.lib advapi32.lib gdi32.lib comdlg32.lib ole32.lib" [$WIN32||$WIN64] + $AdditionalDependencies "$BASE shell32.lib user32.lib advapi32.lib gdi32.lib comdlg32.lib ole32.lib" [($WIN32||$WIN64) && !($VS2015||$VS2017||$VS2019||$VS2022)] + $AdditionalDependencies "$BASE shell32.lib user32.lib advapi32.lib gdi32.lib comdlg32.lib ole32.lib legacy_stdio_definitions.lib" [($WIN32||$WIN64) && ($VS2015||$VS2017||$VS2019||$VS2022)] $TargetMachine "MachineX86 (/MACHINE:X86)" [$WIN32] $TargetMachine "MachineX64 (/MACHINE:X64)" [$WIN64] // Suppress this pointless warning using the undocumented /ignore linker switch @@ -69,7 +71,7 @@ $Configuration "if ERRORLEVEL 1 goto BuildEventFailed" "\n" \ "if exist $QUOTE$(TargetDir)$(TargetName).map$QUOTE copy $QUOTE$(TargetDir)$(TargetName).map$QUOTE $OUTBINDIR\$(TargetName).map" "\n" $CommandLine "$BASE" "call $SRCDIR\vpc_scripts\valve_p4_edit.cmd $QUOTE$OUTBINDIR\$(TargetName).pdb$QUOTE $SRCDIR" "\n" [!$SOURCESDK] - $CommandLine "$BASE" "copy $QUOTE$(TargetDir)$(TargetName).pdb$QUOTE $OUTBINDIR\$(TargetName).pdb" "\n" \ + $CommandLine "$BASE" "copy $QUOTE$(TargetDir)$(TargetName).pdb$QUOTE $QUOTE$OUTBINDIR\$(TargetName).pdb$QUOTE" "\n" \ "if ERRORLEVEL 1 goto BuildEventFailed" "\n" \ "goto BuildEventOK" "\n" \ ":BuildEventFailed" "\n" \ @@ -114,7 +116,8 @@ $Project $CustomBuildStep { // General - $CommandLine "$QUOTE$(VCInstallDir)bin\ml.exe$QUOTE /safeseh /c /Cp /Zi /Fo$QUOTE$(IntDir)\$(InputName).obj$QUOTE $QUOTE$(InputPath)$QUOTE" + $CommandLine "$QUOTE$(VCInstallDir)bin\ml.exe$QUOTE /safeseh /c /Cp /Zi /Fo$QUOTE$(IntDir)\$(InputName).obj$QUOTE $QUOTE$(InputPath)$QUOTE" [!($VS2015||$VS2017||$VS2019||$VS2022)] + $CommandLine "$QUOTE$(VC_ExecutablePath_x64_x86)\ml.exe$QUOTE /safeseh /c /Cp /Zi /Fo$QUOTE$(IntDir)\$(InputName).obj$QUOTE $QUOTE$(InputPath)$QUOTE" [($VS2015||$VS2017||$VS2019||$VS2022)] $Description "Compiling pointeroverride.asm" $Outputs "$(IntDir)\$(InputName).obj" } diff --git a/sp/src/vpc_scripts/source_exe_win_win32_base.vpc b/sp/src/vpc_scripts/source_exe_win_win32_base.vpc index 47ce40fdad..a6d812bae1 100644 --- a/sp/src/vpc_scripts/source_exe_win_win32_base.vpc +++ b/sp/src/vpc_scripts/source_exe_win_win32_base.vpc @@ -39,7 +39,8 @@ $Configuration $Compiler [$WIN32] { - $EnableEnhancedInstructionSet "Streaming SIMD Extensions (/arch:SSE)" + $EnableEnhancedInstructionSet "Streaming SIMD Extensions (/arch:SSE)" [!($VS2015||$VS2017||$VS2019||$VS2022)] + $EnableEnhancedInstructionSet "Streaming SIMD Extensions 2 (/arch:SSE2)" [($VS2015||$VS2017||$VS2019||$VS2022)] } $Linker @@ -66,7 +67,8 @@ $Configuration $PostBuildEvent [!$ANALYZE] { - $CommandLine "call $SRCDIR\vpc_scripts\valve_p4_edit.cmd $OUTBINDIR\$(TargetFileName) $SRCDIR" "\n" [!$SOURCESDK] + $CommandLine "call $SRCDIR\vpc_scripts\valve_p4_edit.cmd $OUTBINDIR\$(TargetFileName) $SRCDIR" "\n" [!$SOURCESDK && !($VS2015||$VS2017||$VS2019||$VS2022)] + $CommandLine "if not exist $QUOTE$OUTBINDIR$QUOTE mkdir $QUOTE$OUTBINDIR$QUOTE" "\n" [!$SOURCESDK && ($VS2015||$VS2017||$VS2019||$VS2022)] $CommandLine "$BASE" "copy $QUOTE$(TargetDir)$QUOTE$(TargetFileName) $OUTBINDIR\$(TargetFileName) >nul" "\n" \ "if ERRORLEVEL 1 goto BuildEventFailed" "\n" \ "if exist $QUOTE$(TargetDir)$QUOTE$(TargetName).map copy $QUOTE$(TargetDir)$QUOTE$(TargetName).map $OUTBINDIR\$(TargetName).map >nul" "\n" @@ -109,7 +111,8 @@ $Project $CustomBuildStep { // General - $CommandLine "$QUOTE$(VCInstallDir)bin\ml.exe$QUOTE /c /Cp /Zi /Fo$QUOTE$(IntDir)\$(InputName).obj$QUOTE $QUOTE$(InputPath)$QUOTE" + $CommandLine "$QUOTE$(VCInstallDir)bin\ml.exe$QUOTE /c /Cp /Zi /Fo$QUOTE$(IntDir)\$(InputName).obj$QUOTE $QUOTE$(InputPath)$QUOTE" [!($VS2015||$VS2017||$VS2019||$VS2022)] + $CommandLine "$QUOTE$(VC_ExecutablePath_x64_x86)\ml.exe$QUOTE /c /Cp /Zi /Fo$QUOTE$(IntDir)\$(InputName).obj$QUOTE $QUOTE$(InputPath)$QUOTE" [($VS2015||$VS2017||$VS2019||$VS2022)] $Description "Compiling pointeroverride.asm" $Outputs "$(IntDir)\$(InputName).obj" } diff --git a/sp/src/vpc_scripts/source_lib_win32_base.vpc b/sp/src/vpc_scripts/source_lib_win32_base.vpc index 02caa80060..bea9d867b8 100644 --- a/sp/src/vpc_scripts/source_lib_win32_base.vpc +++ b/sp/src/vpc_scripts/source_lib_win32_base.vpc @@ -38,7 +38,8 @@ $Configuration $Compiler [$WIN32] { - $EnableEnhancedInstructionSet "Streaming SIMD Extensions (/arch:SSE)" + $EnableEnhancedInstructionSet "Streaming SIMD Extensions (/arch:SSE)" [!($VS2015||$VS2017||$VS2019||$VS2022)] + $EnableEnhancedInstructionSet "Streaming SIMD Extensions 2 (/arch:SSE2)" [($VS2015||$VS2017||$VS2019||$VS2022)] } $PreBuildEvent diff --git a/sp/src/vpc_scripts/source_win32_base.vpc b/sp/src/vpc_scripts/source_win32_base.vpc index 245366f499..e94ebd7649 100644 --- a/sp/src/vpc_scripts/source_win32_base.vpc +++ b/sp/src/vpc_scripts/source_win32_base.vpc @@ -3,15 +3,22 @@ // builds the analyze.vpc file will not be listed as a dependency. $Include "$SRCDIR\vpc_scripts\source_win32_analyze.vpc" [$ANALYZE] +// Mapbase - Implement any newer toolsets +$Include "$SRCDIR\vpc_scripts\newer_vs_toolsets.vpc" + $Configuration { $General { // Request a specific compiler toolset. - $PlatformToolset "v110_xp" [$VS2012 && !$ANALYZE] // VS 2012 targeting Windows XP - http://msdn.microsoft.com/en-us/library/vstudio/jj851139.aspx - $PlatformToolset "v110" [$VS2012 && $ANALYZE] // VS 2012 for /analyze - $PlatformToolset "v120_xp" [$VS2013 && !$ANALYZE] // VS 2013 targeting Windows XP - http://msdn.microsoft.com/en-us/library/vstudio/jj851139.aspx - $PlatformToolset "v120" [$VS2013 && $ANALYZE] // VS 2013 for /analyze + $PlatformToolset "v110_xp" [$VS2012 && !$ANALYZE && !($VS2015||$VS2017||$VS2019||$VS2022)] // VS 2012 targeting Windows XP - http://msdn.microsoft.com/en-us/library/vstudio/jj851139.aspx + $PlatformToolset "v110" [$VS2012 && $ANALYZE && !($VS2015||$VS2017||$VS2019||$VS2022)] // VS 2012 for /analyze + $PlatformToolset "v120_xp" [$VS2013 && !$ANALYZE && !($VS2015||$VS2017||$VS2019||$VS2022)] // VS 2013 targeting Windows XP - http://msdn.microsoft.com/en-us/library/vstudio/jj851139.aspx + $PlatformToolset "v120" [$VS2013 && $ANALYZE && !($VS2015||$VS2017||$VS2019||$VS2022)] // VS 2013 for /analyze + $PlatformToolset "v140" [$VS2015] // VS 2015 + $PlatformToolset "v141" [$VS2017] // VS 2017 + $PlatformToolset "v142" [$VS2019] // VS 2019 + $PlatformToolset "v143" [$VS2022] // VS 2022 } $General @@ -31,6 +38,10 @@ $Configuration // warning C4316: object allocated on the heap may not be aligned 16 $DisableSpecificWarnings "$BASE;4316" [$VS2013] + + // warning C4838: conversion requires a narrowing conversion + // warning C4456-4459: variable shadowing. TODO: fix those! + $DisableSpecificWarnings "$BASE;4316;4838;4456;4457;4458;4459" [($VS2015||$VS2017||$VS2019||$VS2022)] // Having lots of warnings makes it harder to notice new, and possibly // important warnings, both on buildbot and in the output window. Lots diff --git a/sp/src/vscript/squirrel/doc/source/stdlib/stdstringlib.rst b/sp/src/vscript/squirrel/doc/source/stdlib/stdstringlib.rst index 19c18d7a93..dd92934607 100644 --- a/sp/src/vscript/squirrel/doc/source/stdlib/stdstringlib.rst +++ b/sp/src/vscript/squirrel/doc/source/stdlib/stdstringlib.rst @@ -50,16 +50,20 @@ Global Symbols Strips white-space-only characters that might appear at the end of the given string and returns the new stripped string. -.. js:function:: split(str, separators) +.. js:function:: split(str, separators [, skipempty]) returns an array of strings split at each point where a separator character occurs in `str`. The separator is not returned as part of any array element. The parameter `separators` is a string that specifies the characters as to be used for the splitting. + The parameter `skipempty` is a boolean (default false). If `skipempty` is true, empty strings are not added to array. :: eg. - local a = split("1.2-3;4/5",".-/;"); + local a = split("1.2-3;;4/5",".-/;"); + // the result will be [1,2,3,,4,5] + or + local b = split("1.2-3;;4/5",".-/;",true); // the result will be [1,2,3,4,5] diff --git a/sp/src/vscript/squirrel/sqstdlib/sqstdblob.cpp b/sp/src/vscript/squirrel/sqstdlib/sqstdblob.cpp index 261b938bf5..776a96807e 100644 --- a/sp/src/vscript/squirrel/sqstdlib/sqstdblob.cpp +++ b/sp/src/vscript/squirrel/sqstdlib/sqstdblob.cpp @@ -205,8 +205,8 @@ static SQInteger _g_blob_swap2(HSQUIRRELVM v) { SQInteger i; sq_getinteger(v,2,&i); - short s=(short)i; - sq_pushinteger(v,(s<<8)|((s>>8)&0x00FF)); + unsigned short s = (unsigned short)i; + sq_pushinteger(v, ((s << 8) | ((s >> 8) & 0x00FFu)) & 0xFFFFu); return 1; } diff --git a/sp/src/vscript/squirrel/sqstdlib/sqstdstring.cpp b/sp/src/vscript/squirrel/sqstdlib/sqstdstring.cpp index 919bd9e924..5747d8edfe 100644 --- a/sp/src/vscript/squirrel/sqstdlib/sqstdstring.cpp +++ b/sp/src/vscript/squirrel/sqstdlib/sqstdstring.cpp @@ -12,6 +12,8 @@ #define MAX_WFORMAT_LEN 3 #define ADDITIONAL_FORMAT_SPACE (100*sizeof(SQChar)) +static SQUserPointer rex_typetag = NULL; + static SQBool isfmtchr(SQChar ch) { switch(ch) { @@ -247,16 +249,16 @@ static SQInteger _string_rstrip(HSQUIRRELVM v) static SQInteger _string_split(HSQUIRRELVM v) { const SQChar *str,*seps; - SQChar *stemp; + SQInteger sepsize; + SQBool skipempty = SQFalse; sq_getstring(v,2,&str); - sq_getstring(v,3,&seps); - SQInteger sepsize = sq_getsize(v,3); + sq_getstringandsize(v,3,&seps,&sepsize); if(sepsize == 0) return sq_throwerror(v,_SC("empty separators string")); - SQInteger memsize = (sq_getsize(v,2)+1)*sizeof(SQChar); - stemp = sq_getscratchpad(v,memsize); - memcpy(stemp,str,memsize); - SQChar *start = stemp; - SQChar *end = stemp; + if(sq_gettop(v)>3) { + sq_getbool(v,4,&skipempty); + } + const SQChar *start = str; + const SQChar *end = str; sq_newarray(v,0); while(*end != '\0') { @@ -265,9 +267,10 @@ static SQInteger _string_split(HSQUIRRELVM v) { if(cur == seps[i]) { - *end = 0; - sq_pushstring(v,start,-1); - sq_arrayappend(v,-2); + if(!skipempty || (end != start)) { + sq_pushstring(v,start,end-start); + sq_arrayappend(v,-2); + } start = end + 1; break; } @@ -276,7 +279,7 @@ static SQInteger _string_split(HSQUIRRELVM v) } if(end != start) { - sq_pushstring(v,start,-1); + sq_pushstring(v,start,end-start); sq_arrayappend(v,-2); } return 1; @@ -384,7 +387,9 @@ static SQInteger _string_endswith(HSQUIRRELVM v) #define SETUP_REX(v) \ SQRex *self = NULL; \ - sq_getinstanceup(v,1,(SQUserPointer *)&self,0); + if(SQ_FAILED(sq_getinstanceup(v,1,(SQUserPointer *)&self,rex_typetag))) { \ + return sq_throwerror(v,_SC("invalid type tag")); \ + } static SQInteger _rexobj_releasehook(SQUserPointer p, SQInteger SQ_UNUSED_ARG(size)) { @@ -465,6 +470,13 @@ static SQInteger _regexp_subexpcount(HSQUIRRELVM v) static SQInteger _regexp_constructor(HSQUIRRELVM v) { + SQRex *self = NULL; + if (SQ_FAILED(sq_getinstanceup(v, 1, (SQUserPointer *)&self, rex_typetag))) { + return sq_throwerror(v, _SC("invalid type tag")); + } + if (self != NULL) { + return sq_throwerror(v, _SC("invalid regexp object")); + } const SQChar *error,*pattern; sq_getstring(v,2,&pattern); SQRex *rex = sqstd_rex_compile(pattern,&error); @@ -499,7 +511,7 @@ static const SQRegFunction stringlib_funcs[]={ _DECL_FUNC(strip,2,_SC(".s")), _DECL_FUNC(lstrip,2,_SC(".s")), _DECL_FUNC(rstrip,2,_SC(".s")), - _DECL_FUNC(split,3,_SC(".ss")), + _DECL_FUNC(split,-3,_SC(".ssb")), _DECL_FUNC(escape,2,_SC(".s")), _DECL_FUNC(startswith,3,_SC(".ss")), _DECL_FUNC(endswith,3,_SC(".ss")), @@ -512,6 +524,8 @@ SQInteger sqstd_register_stringlib(HSQUIRRELVM v) { sq_pushstring(v,_SC("regexp"),-1); sq_newclass(v,SQFalse); + rex_typetag = (SQUserPointer)rexobj_funcs; + sq_settypetag(v, -1, rex_typetag); SQInteger i = 0; while(rexobj_funcs[i].name != 0) { const SQRegFunction &f = rexobj_funcs[i]; diff --git a/sp/src/vscript/squirrel/squirrel/sqbaselib.cpp b/sp/src/vscript/squirrel/squirrel/sqbaselib.cpp index fb6545f921..5c03e8393b 100644 --- a/sp/src/vscript/squirrel/squirrel/sqbaselib.cpp +++ b/sp/src/vscript/squirrel/squirrel/sqbaselib.cpp @@ -754,7 +754,7 @@ static SQInteger array_find(HSQUIRRELVM v) } -static bool _sort_compare(HSQUIRRELVM v,SQObjectPtr &a,SQObjectPtr &b,SQInteger func,SQInteger &ret) +static bool _sort_compare(HSQUIRRELVM v, SQArray *arr, SQObjectPtr &a,SQObjectPtr &b,SQInteger func,SQInteger &ret) { if(func < 0) { if(!v->ObjCmp(a,b,ret)) return false; @@ -765,15 +765,21 @@ static bool _sort_compare(HSQUIRRELVM v,SQObjectPtr &a,SQObjectPtr &b,SQInteger sq_pushroottable(v); v->Push(a); v->Push(b); + SQObjectPtr *valptr = arr->_values._vals; + SQUnsignedInteger precallsize = arr->_values.size(); if(SQ_FAILED(sq_call(v, 3, SQTrue, SQFalse))) { if(!sq_isstring( v->_lasterror)) v->Raise_Error(_SC("compare func failed")); return false; } - if(SQ_FAILED(sq_getinteger(v, -1, &ret))) { + if(SQ_FAILED(sq_getinteger(v, -1, &ret))) { v->Raise_Error(_SC("numeric value expected as return value of the compare function")); return false; } + if (precallsize != arr->_values.size() || valptr != arr->_values._vals) { + v->Raise_Error(_SC("array resized during sort operation")); + return false; + } sq_settop(v, top); return true; } @@ -792,7 +798,7 @@ static bool _hsort_sift_down(HSQUIRRELVM v,SQArray *arr, SQInteger root, SQInteg maxChild = root2; } else { - if(!_sort_compare(v,arr->_values[root2],arr->_values[root2 + 1],func,ret)) + if(!_sort_compare(v,arr,arr->_values[root2],arr->_values[root2 + 1],func,ret)) return false; if (ret > 0) { maxChild = root2; @@ -802,7 +808,7 @@ static bool _hsort_sift_down(HSQUIRRELVM v,SQArray *arr, SQInteger root, SQInteg } } - if(!_sort_compare(v,arr->_values[root],arr->_values[maxChild],func,ret)) + if(!_sort_compare(v,arr,arr->_values[root],arr->_values[maxChild],func,ret)) return false; if (ret < 0) { if (root == maxChild) { diff --git a/sp/src/vscript/squirrel/squirrel/sqclass.cpp b/sp/src/vscript/squirrel/squirrel/sqclass.cpp index fc619616c0..53a297630f 100644 --- a/sp/src/vscript/squirrel/squirrel/sqclass.cpp +++ b/sp/src/vscript/squirrel/squirrel/sqclass.cpp @@ -61,6 +61,9 @@ bool SQClass::NewSlot(SQSharedState *ss,const SQObjectPtr &key,const SQObjectPtr _defaultvalues[_member_idx(temp)].val = val; return true; } + if (_members->CountUsed() >= MEMBER_MAX_COUNT) { + return false; + } if(belongs_to_static_table) { SQInteger mmidx; if((sq_type(val) == OT_CLOSURE || sq_type(val) == OT_NATIVECLOSURE) && diff --git a/sp/src/vscript/squirrel/squirrel/sqclass.h b/sp/src/vscript/squirrel/squirrel/sqclass.h index 7d4021721b..60d3d21b1e 100644 --- a/sp/src/vscript/squirrel/squirrel/sqclass.h +++ b/sp/src/vscript/squirrel/squirrel/sqclass.h @@ -17,6 +17,7 @@ typedef sqvector SQClassMemberVec; #define MEMBER_TYPE_METHOD 0x01000000 #define MEMBER_TYPE_FIELD 0x02000000 +#define MEMBER_MAX_COUNT 0x00FFFFFF #define _ismethod(o) (_integer(o)&MEMBER_TYPE_METHOD) #define _isfield(o) (_integer(o)&MEMBER_TYPE_FIELD) diff --git a/sp/src/vscript/squirrel/squirrel/sqtable.h b/sp/src/vscript/squirrel/squirrel/sqtable.h index 59db331796..8ca3ae7cf4 100644 --- a/sp/src/vscript/squirrel/squirrel/sqtable.h +++ b/sp/src/vscript/squirrel/squirrel/sqtable.h @@ -12,7 +12,7 @@ #define hashptr(p) ((SQHash)(((SQInteger)p) >> 3)) -inline SQHash HashObj(const SQObjectPtr &key) +inline SQHash HashObj(const SQObject &key) { switch(sq_type(key)) { case OT_STRING: return _string(key)->_hash; diff --git a/sp/src/vscript/vscript_bindings_base.cpp b/sp/src/vscript/vscript_bindings_base.cpp index 9511efa408..69ca218725 100644 --- a/sp/src/vscript/vscript_bindings_base.cpp +++ b/sp/src/vscript/vscript_bindings_base.cpp @@ -459,6 +459,9 @@ void RegisterBaseBindings( IScriptVM *pVM ) //----------------------------------------------------------------------------- + ScriptRegisterConstant( pVM, MAPBASE_VERSION, "The current Mapbase version according to when the VScript library was last compiled." ); + ScriptRegisterConstant( pVM, MAPBASE_VER_INT, "The current Mapbase version integer according to when the VScript library was last compiled." ); + // // Math/world // diff --git a/sp/src/vscript/vscript_bindings_math.cpp b/sp/src/vscript/vscript_bindings_math.cpp index 2f189265fc..cb1567d549 100644 --- a/sp/src/vscript/vscript_bindings_math.cpp +++ b/sp/src/vscript/vscript_bindings_math.cpp @@ -102,7 +102,6 @@ void ScriptMatrixSetColumn( const Vector& vecset, int column, HSCRIPT hMat1 ) matrix3x4_t *pMat1 = ToMatrix3x4( hMat1 ); - static Vector outvec; MatrixSetColumn( vecset, column, *pMat1 ); } @@ -156,6 +155,49 @@ void ScriptSetScaleMatrix( float x, float y, float z, HSCRIPT hMat1 ) SetScaleMatrix( x, y, z, *pMat1 ); } +void ScriptMatrixScaleBy( float flScale, HSCRIPT hMat1 ) +{ + if (!hMat1) + return; + + matrix3x4_t *pMat1 = ToMatrix3x4( hMat1 ); + + MatrixScaleBy( flScale, *pMat1 ); +} + +void ScriptMatrixScaleByZero( HSCRIPT hMat1 ) +{ + if (!hMat1) + return; + + matrix3x4_t *pMat1 = ToMatrix3x4( hMat1 ); + + MatrixScaleByZero( *pMat1 ); +} + +const Vector& ScriptMatrixGetTranslation( HSCRIPT hMat1 ) +{ + static Vector outvec; + outvec.Zero(); + if (!hMat1) + return outvec; + + matrix3x4_t *pMat1 = ToMatrix3x4( hMat1 ); + + MatrixGetTranslation( *pMat1, outvec ); + return outvec; +} + +void ScriptMatrixSetTranslation( const Vector& vecset, HSCRIPT hMat1 ) +{ + if (!hMat1) + return; + + matrix3x4_t *pMat1 = ToMatrix3x4( hMat1 ); + + MatrixSetTranslation( vecset, *pMat1 ); +} + //============================================================================= // // Quaternion @@ -427,7 +469,11 @@ void RegisterMathBaseBindings( IScriptVM *pVM ) ScriptRegisterFunctionNamed( pVM, ScriptAngleMatrix, "AngleMatrix", "Sets the angles and position of a matrix." ); ScriptRegisterFunctionNamed( pVM, ScriptAngleIMatrix, "AngleIMatrix", "Sets the inverted angles and position of a matrix." ); ScriptRegisterFunctionNamed( pVM, ScriptSetIdentityMatrix, "SetIdentityMatrix", "Turns a matrix into an identity matrix." ); - ScriptRegisterFunctionNamed( pVM, ScriptSetScaleMatrix, "SetScaleMatrix", "Scales a matrix." ); + ScriptRegisterFunctionNamed( pVM, ScriptSetScaleMatrix, "SetScaleMatrix", "Builds a scale matrix." ); + ScriptRegisterFunctionNamed( pVM, ScriptMatrixScaleBy, "MatrixScaleBy", "Scales a matrix." ); + ScriptRegisterFunctionNamed( pVM, ScriptMatrixScaleByZero, "MatrixScaleByZero", "Scales a matrix by zero." ); + ScriptRegisterFunctionNamed( pVM, ScriptMatrixGetTranslation, "MatrixGetTranslation", "Gets a matrix's translation." ); + ScriptRegisterFunctionNamed( pVM, ScriptMatrixSetTranslation, "MatrixSetTranslation", "Sets a matrix's translation." ); // // Quaternion diff --git a/sp/src/vscript/vscript_squirrel.cpp b/sp/src/vscript/vscript_squirrel.cpp index af605ea112..cd5fe6f6ae 100644 --- a/sp/src/vscript/vscript_squirrel.cpp +++ b/sp/src/vscript/vscript_squirrel.cpp @@ -33,11 +33,14 @@ #include "tier1/utlbuffer.h" #include "tier1/mapbase_con_groups.h" +#include "tier1/convar.h" #include "vscript_squirrel.nut" #include +extern ConVar developer; + struct WriteStateMap { CUtlMap cache; @@ -65,8 +68,16 @@ struct WriteStateMap struct ReadStateMap { CUtlMap cache; +#ifdef _DEBUG + CUtlMap allocated; +#endif HSQUIRRELVM vm_; - ReadStateMap(HSQUIRRELVM vm) : cache(DefLessFunc(int)), vm_(vm) + ReadStateMap(HSQUIRRELVM vm) : + cache(DefLessFunc(int)), +#ifdef _DEBUG + allocated(DefLessFunc(int)), +#endif + vm_(vm) {} ~ReadStateMap() @@ -83,6 +94,16 @@ struct ReadStateMap int marker = pBuffer->GetInt(); auto idx = cache.Find(marker); + +#ifdef _DEBUG + auto allocatedIdx = allocated.Find(marker); + bool hasSeen = allocatedIdx != allocated.InvalidIndex(); + if (!hasSeen) + { + allocated.Insert(marker, true); + } +#endif + if (idx != cache.InvalidIndex()) { sq_pushobject(vm, cache[idx]); @@ -90,6 +111,9 @@ struct ReadStateMap } else { +#ifdef _DEBUG + Assert(!hasSeen); +#endif *outmarker = marker; return false; } @@ -161,6 +185,13 @@ class SquirrelVM : public IScriptVM //-------------------------------------------------------- virtual ScriptStatus_t ExecuteFunction(HSCRIPT hFunction, ScriptVariant_t* pArgs, int nArgs, ScriptVariant_t* pReturn, HSCRIPT hScope, bool bWait) override; + //-------------------------------------------------------- + // Hooks + //-------------------------------------------------------- + virtual bool ScopeIsHooked( HSCRIPT hScope, const char *pszEventName ) override; + virtual HSCRIPT LookupHookFunction( const char *pszEventName, HSCRIPT hScope, bool &bLegacy ) override; + virtual ScriptStatus_t ExecuteHookFunction( const char *pszEventName, HSCRIPT hFunction, ScriptVariant_t *pArgs, int nArgs, ScriptVariant_t *pReturn, HSCRIPT hScope, bool bWait ) override; + //-------------------------------------------------------- // External functions //-------------------------------------------------------- @@ -180,6 +211,11 @@ class SquirrelVM : public IScriptVM // External enums //-------------------------------------------------------- virtual void RegisterEnum(ScriptEnumDesc_t *pEnumDesc) override; + + //-------------------------------------------------------- + // External hooks + //-------------------------------------------------------- + virtual void RegisterHook(ScriptHook_t *pHookDesc) override; //-------------------------------------------------------- // External instances. Note class will be auto-registered. @@ -241,7 +277,7 @@ class SquirrelVM : public IScriptVM HSQOBJECT regexpClass_; }; -SQUserPointer TYPETAG_VECTOR = "VectorTypeTag"; +static char TYPETAG_VECTOR[] = "VectorTypeTag"; namespace SQVector { @@ -464,8 +500,26 @@ namespace SQVector return sq_throwerror(vm, "Expected (Vector)"); } - v1->Negate(); + sq_getclass(vm, 1); + sq_createinstance(vm, -1); + SQUserPointer p; + sq_getinstanceup(vm, -1, &p, 0); + new(p) Vector(-v1->x, -v1->y, -v1->z); + sq_remove(vm, -2); + + return 1; + } + + SQInteger weakref(HSQUIRRELVM vm) + { + sq_weakref(vm, 1); + return 1; + } + SQInteger getclass(HSQUIRRELVM vm) + { + sq_getclass(vm, 1); + sq_push(vm, -1); return 1; } @@ -823,11 +877,10 @@ namespace SQVector float x = 0.0f, y = 0.0f, z = 0.0f; - if ( sscanf( szInput, "%f %f %f", &x, &y, &z ) < 3 ) // UTIL_StringToVector + if ( sscanf( szInput, "%f %f %f", &x, &y, &z ) < 3 ) { - // Don't throw, return null while invalidating the input vector. + // Return null while invalidating the input vector. // This allows the user to easily check for input errors without halting. - //return sq_throwerror(vm, "invalid KV string"); sq_pushnull(vm); *v1 = vec3_invalid; @@ -959,6 +1012,8 @@ namespace SQVector {_SC("_mul"), _multiply, 2, _SC("..")}, {_SC("_div"), _divide, 2, _SC("..")}, {_SC("_unm"), _unm, 1, _SC(".")}, + {_SC("weakref"), weakref, 1, _SC(".")}, + {_SC("getclass"), getclass, 1, _SC(".")}, {_SC("Set"), Set, -2, _SC("..nn")}, {_SC("Add"), Add, 2, _SC("..")}, {_SC("Subtract"), Subtract, 2, _SC("..")}, @@ -1089,7 +1144,7 @@ void PushVariant(HSQUIRRELVM vm, const ScriptVariant_t& value) sq_createinstance(vm, -1); SQUserPointer p; sq_getinstanceup(vm, -1, &p, 0); - new(p) Vector(value); + new(p) Vector(static_cast(value)); sq_remove(vm, -2); break; } @@ -1363,6 +1418,9 @@ SQInteger function_stub(HSQUIRRELVM vm) PushVariant(vm, retval); + if (retval.m_type == FIELD_VECTOR) + delete retval.m_pVector; + return pFunc->m_desc.m_ReturnType != FIELD_VOID; } @@ -1620,6 +1678,19 @@ SQInteger IsValid_stub(HSQUIRRELVM vm) return 1; } +SQInteger weakref_stub(HSQUIRRELVM vm) +{ + sq_weakref(vm, 1); + return 1; +} + +SQInteger getclass_stub(HSQUIRRELVM vm) +{ + sq_getclass(vm, 1); + sq_push(vm, -1); + return 1; +} + struct SquirrelSafeCheck { SquirrelSafeCheck(HSQUIRRELVM vm, int outputCount = 0) : @@ -1697,6 +1768,9 @@ const char * ScriptDataTypeToName(ScriptDataType_t datatype) void RegisterDocumentation(HSQUIRRELVM vm, const ScriptFuncDescriptor_t& pFuncDesc, ScriptClassDesc_t* pClassDesc = nullptr) { + if ( !developer.GetInt() ) + return; + SquirrelSafeCheck safeCheck(vm); if (pFuncDesc.m_pszDescription && pFuncDesc.m_pszDescription[0] == SCRIPT_HIDE[0]) @@ -1736,6 +1810,9 @@ void RegisterDocumentation(HSQUIRRELVM vm, const ScriptFuncDescriptor_t& pFuncDe void RegisterClassDocumentation(HSQUIRRELVM vm, const ScriptClassDesc_t* pClassDesc) { + if ( !developer.GetInt() ) + return; + SquirrelSafeCheck safeCheck(vm); const char *name = pClassDesc->m_pszScriptName; @@ -1768,6 +1845,9 @@ void RegisterClassDocumentation(HSQUIRRELVM vm, const ScriptClassDesc_t* pClassD void RegisterEnumDocumentation(HSQUIRRELVM vm, const ScriptEnumDesc_t* pClassDesc) { + if ( !developer.GetInt() ) + return; + SquirrelSafeCheck safeCheck(vm); if (pClassDesc->m_pszDescription && pClassDesc->m_pszDescription[0] == SCRIPT_HIDE[0]) @@ -1785,6 +1865,9 @@ void RegisterEnumDocumentation(HSQUIRRELVM vm, const ScriptEnumDesc_t* pClassDes void RegisterConstantDocumentation( HSQUIRRELVM vm, const ScriptConstantBinding_t* pConstDesc, const char *pszAsString, ScriptEnumDesc_t* pEnumDesc = nullptr ) { + if ( !developer.GetInt() ) + return; + SquirrelSafeCheck safeCheck(vm); if (pConstDesc->m_pszDescription && pConstDesc->m_pszDescription[0] == SCRIPT_HIDE[0]) @@ -1813,6 +1896,9 @@ void RegisterConstantDocumentation( HSQUIRRELVM vm, const ScriptConstantBinding_ void RegisterHookDocumentation(HSQUIRRELVM vm, const ScriptHook_t* pHook, const ScriptFuncDescriptor_t& pFuncDesc, ScriptClassDesc_t* pClassDesc = nullptr) { + if ( !developer.GetInt() ) + return; + SquirrelSafeCheck safeCheck(vm); if (pFuncDesc.m_pszDescription && pFuncDesc.m_pszDescription[0] == SCRIPT_HIDE[0]) @@ -1855,6 +1941,9 @@ void RegisterHookDocumentation(HSQUIRRELVM vm, const ScriptHook_t* pHook, const void RegisterMemberDocumentation(HSQUIRRELVM vm, const ScriptMemberDesc_t& pDesc, ScriptClassDesc_t* pClassDesc = nullptr) { + if ( !developer.GetInt() ) + return; + SquirrelSafeCheck safeCheck(vm); if (pDesc.m_pszDescription && pDesc.m_pszDescription[0] == SCRIPT_HIDE[0]) @@ -1882,6 +1971,12 @@ void RegisterMemberDocumentation(HSQUIRRELVM vm, const ScriptMemberDesc_t& pDesc CallDocumentationRegisterFunction( 3 ); } +SQInteger GetDeveloperLevel(HSQUIRRELVM vm) +{ + sq_pushinteger( vm, developer.GetInt() ); + return 1; +} + bool SquirrelVM::Init() { @@ -1949,6 +2044,11 @@ bool SquirrelVM::Init() sq_pop(vm_, 1); } + sq_pushstring( vm_, "developer", -1 ); + sq_newclosure( vm_, &GetDeveloperLevel, 0 ); + //sq_setnativeclosurename( vm_, -1, "developer" ); + sq_newslot( vm_, -3, SQFalse ); + sq_pop(vm_, 1); } @@ -2248,6 +2348,118 @@ ScriptStatus_t SquirrelVM::ExecuteFunction(HSCRIPT hFunction, ScriptVariant_t* p return SCRIPT_DONE; } +bool SquirrelVM::ScopeIsHooked( HSCRIPT hScope, const char *pszEventName ) +{ + // For now, assume null scope (which is used for global hooks) is always hooked + if (!hScope) + return true; + + Assert(hScope != INVALID_HSCRIPT); + + sq_pushroottable(vm_); + sq_pushstring(vm_, "Hooks", -1); + sq_get(vm_, -2); + sq_pushstring(vm_, "ScopeHookedToEvent", -1); + sq_get(vm_, -2); + sq_push(vm_, -2); + sq_pushobject(vm_, *((HSQOBJECT*)hScope)); + sq_pushstring(vm_, pszEventName, -1); + sq_call(vm_, 3, SQTrue, SQTrue); + + SQBool val; + if (SQ_FAILED(sq_getbool(vm_, -1, &val))) + { + sq_pop(vm_, 3); + return false; + } + + sq_pop(vm_, 3); + return val ? true : false; +} + +HSCRIPT SquirrelVM::LookupHookFunction(const char *pszEventName, HSCRIPT hScope, bool &bLegacy) +{ + HSCRIPT hFunc = hScope ? LookupFunction( pszEventName, hScope ) : nullptr; + if (hFunc) + { + bLegacy = true; + return hFunc; + } + else + { + bLegacy = false; + } + + if (!ScopeIsHooked(hScope, pszEventName)) + return nullptr; + + sq_pushroottable(vm_); + sq_pushstring(vm_, "Hooks", -1); + sq_get(vm_, -2); + sq_pushstring(vm_, "Call", -1); + sq_get(vm_, -2); + + HSQOBJECT obj; + sq_resetobject(&obj); + sq_getstackobj(vm_, -1, &obj); + sq_addref(vm_, &obj); + sq_pop(vm_, 2); + + HSQOBJECT* pObj = new HSQOBJECT; + *pObj = obj; + return (HSCRIPT)pObj; +} + +ScriptStatus_t SquirrelVM::ExecuteHookFunction(const char *pszEventName, HSCRIPT hFunction, ScriptVariant_t* pArgs, int nArgs, ScriptVariant_t* pReturn, HSCRIPT hScope, bool bWait) +{ + SquirrelSafeCheck safeCheck(vm_); + if (!hFunction) + return SCRIPT_ERROR; + + if (hFunction == INVALID_HSCRIPT) + return SCRIPT_ERROR; + + HSQOBJECT* pFunc = (HSQOBJECT*)hFunction; + sq_pushobject(vm_, *pFunc); + + // TODO: Run in hook scope + sq_pushroottable(vm_); + + if (hScope) + sq_pushobject(vm_, *((HSQOBJECT*)hScope)); + else + sq_pushnull(vm_); // global hook + + sq_pushstring(vm_, pszEventName, -1); + + for (int i = 0; i < nArgs; ++i) + { + PushVariant(vm_, pArgs[i]); + } + + bool hasReturn = pReturn != nullptr; + + if (SQ_FAILED(sq_call(vm_, nArgs + 3, hasReturn, SQTrue))) + { + sq_pop(vm_, 1); + return SCRIPT_ERROR; + } + + if (hasReturn) + { + if (!getVariant(vm_, -1, *pReturn)) + { + sq_pop(vm_, 1); + return SCRIPT_ERROR; + } + + sq_pop(vm_, 1); + } + + sq_pop(vm_, 1); + return SCRIPT_DONE; +} + void SquirrelVM::RegisterFunction(ScriptFunctionBinding_t* pScriptFunction) { SquirrelSafeCheck safeCheck(vm_); @@ -2362,6 +2574,14 @@ bool SquirrelVM::RegisterClass(ScriptClassDesc_t* pClassDesc) sq_newclosure(vm_, IsValid_stub, 0); sq_newslot(vm_, -3, SQFalse); + sq_pushstring(vm_, "weakref", -1); + sq_newclosure(vm_, weakref_stub, 0); + sq_newslot(vm_, -3, SQFalse); + + sq_pushstring(vm_, "getclass", -1); + sq_newclosure(vm_, getclass_stub, 0); + sq_newslot(vm_, -3, SQFalse); + for (int i = 0; i < pClassDesc->m_FunctionBindings.Count(); ++i) { @@ -2449,48 +2669,43 @@ void SquirrelVM::RegisterEnum(ScriptEnumDesc_t* pEnumDesc) if (!pEnumDesc) return; - sq_pushconsttable(vm_); - sq_pushstring(vm_, pEnumDesc->m_pszScriptName, -1); - - // Check if class name is already taken - if (sq_get(vm_, -2) == SQ_OK) - { - HSQOBJECT obj; - sq_resetobject(&obj); - sq_getstackobj(vm_, -1, &obj); - if (!sq_isnull(obj)) - { - sq_pop(vm_, 2); - return; - } - } + sq_newtableex(vm_, pEnumDesc->m_ConstantBindings.Count()); - sq_pop(vm_, 1); + sq_pushconsttable(vm_); - // HACKHACK: I have no idea how to declare enums with the current API. - // For now, we'll just cram everything into a script buffer and compile it. (Blixibon) - char szScript[2048]; - V_snprintf( szScript, sizeof(szScript), "enum %s {\n", pEnumDesc->m_pszScriptName ); + sq_pushstring(vm_, pEnumDesc->m_pszScriptName, -1); + sq_push(vm_, -3); + sq_rawset(vm_, -3); for (int i = 0; i < pEnumDesc->m_ConstantBindings.Count(); ++i) { auto& scriptConstant = pEnumDesc->m_ConstantBindings[i]; + sq_pushstring(vm_, scriptConstant.m_pszScriptName, -1); + PushVariant(vm_, scriptConstant.m_data); + sq_rawset(vm_, -4); + char szValue[64]; GetVariantScriptString( scriptConstant.m_data, szValue, sizeof(szValue) ); - - V_snprintf( szScript, sizeof(szScript), "%s%s = %s\n", szScript, scriptConstant.m_pszScriptName, szValue ); - RegisterConstantDocumentation(vm_, &scriptConstant, szValue, pEnumDesc); } - V_strcat_safe( szScript, "}" ); - - Run( szScript ); + sq_pop(vm_, 2); RegisterEnumDocumentation(vm_, pEnumDesc); } +void SquirrelVM::RegisterHook(ScriptHook_t* pHookDesc) +{ + SquirrelSafeCheck safeCheck(vm_); + Assert(pHookDesc); + + if (!pHookDesc) + return; + + RegisterHookDocumentation(vm_, pHookDesc, pHookDesc->m_desc, nullptr); +} + HSCRIPT SquirrelVM::RegisterInstance(ScriptClassDesc_t* pDesc, void* pInstance, bool bAllowDestruct) { SquirrelSafeCheck safeCheck(vm_); @@ -3388,6 +3603,7 @@ void SquirrelVM::WriteState(CUtlBuffer* pBuffer) int count = sq_getsize(vm_, 1); sq_pushnull(vm_); pBuffer->PutInt(count); + while (SQ_SUCCEEDED(sq_next(vm_, -2))) { WriteObject(pBuffer, writeState, -2); @@ -3521,6 +3737,9 @@ void SquirrelVM::ReadObject(CUtlBuffer* pBuffer, ReadStateMap& readState) break; } + vm_->Push(ret); + readState.StoreTopInCache(marker); + int noutervalues = _closure(ret)->_function->_noutervalues; for (int i = 0; i < noutervalues; ++i) { @@ -3542,9 +3761,6 @@ void SquirrelVM::ReadObject(CUtlBuffer* pBuffer, ReadStateMap& readState) _closure(ret)->_defaultparams[i] = obj; sq_poptop(vm_); } - - vm_->Push(ret); - readState.StoreTopInCache(marker); } ReadObject(pBuffer, readState); @@ -3816,16 +4032,17 @@ void SquirrelVM::ReadObject(CUtlBuffer* pBuffer, ReadStateMap& readState) break; } + SQOuter* outer = SQOuter::Create(_ss(vm_), nullptr); + vm_->Push(outer); + readState.StoreTopInCache(marker); + ReadObject(pBuffer, readState); HSQOBJECT inner; sq_resetobject(&inner); sq_getstackobj(vm_, -1, &inner); - SQOuter* outer = SQOuter::Create(_ss(vm_), nullptr); outer->_value = inner; outer->_valptr = &(outer->_value); sq_poptop(vm_); - vm_->Push(outer); - readState.StoreTopInCache(marker); break; } diff --git a/sp/src/vscript/vscript_squirrel.nut b/sp/src/vscript/vscript_squirrel.nut index f12d385b97..334ae92ca7 100644 --- a/sp/src/vscript/vscript_squirrel.nut +++ b/sp/src/vscript/vscript_squirrel.nut @@ -1,68 +1,88 @@ static char g_Script_vscript_squirrel[] = R"vscript( //========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// // -// Purpose: -// +// Purpose: +// //=============================================================================// Warning <- error; -function clamp(val,min,max) +function clamp( val, min, max ) { if ( max < min ) return max; - else if( val < min ) + if ( val < min ) return min; - else if( val > max ) + if ( val > max ) return max; - else - return val; + return val; } -function max(a,b) return a > b ? a : b +function max( a, b ) +{ + if ( a > b ) + return a; + return b; +} -function min(a,b) return a < b ? a : b +function min( a, b ) +{ + if ( a < b ) + return a; + return b; +} -function RemapVal(val, A, B, C, D) +function RemapVal( val, A, B, C, D ) { if ( A == B ) - return val >= B ? D : C; + { + if ( val >= B ) + return D; + return C; + }; return C + (D - C) * (val - A) / (B - A); } -function RemapValClamped(val, A, B, C, D) +function RemapValClamped( val, A, B, C, D ) { if ( A == B ) - return val >= B ? D : C; + { + if ( val >= B ) + return D; + return C; + }; + local cVal = (val - A) / (B - A); - cVal = (cVal < 0.0) ? 0.0 : (1.0 < cVal) ? 1.0 : cVal; + + if ( cVal <= 0.0 ) + return C; + + if ( cVal >= 1.0 ) + return D; + return C + (D - C) * cVal; } function Approach( target, value, speed ) { - local delta = target - value - - if( delta > speed ) - value += speed - else if( delta < (-speed) ) - value -= speed - else - value = target + local delta = target - value; - return value + if ( delta > speed ) + return value + speed; + if ( -speed > delta ) + return value - speed; + return target; } function AngleDistance( next, cur ) { local delta = next - cur - if ( delta < (-180.0) ) - delta += 360.0 - else if ( delta > 180.0 ) - delta -= 360.0 - - return delta + if ( delta > 180.0 ) + return delta - 360.0; + if ( -180.0 > delta ) + return delta + 360.0; + return delta; } function FLerp( f1, f2, i1, i2, x ) @@ -83,7 +103,7 @@ function SimpleSpline( f ) function printl( text ) { - return ::print(text + "\n"); + return print(text + "\n"); } class CSimpleCallChainer @@ -122,234 +142,402 @@ class CSimpleCallChainer chain = null; } -__Documentation <- {} - -local DocumentedFuncs = {} -local DocumentedClasses = {} -local DocumentedEnums = {} -local DocumentedConsts = {} -local DocumentedHooks = {} -local DocumentedMembers = {} +//--------------------------------------------------------- +// Hook handler +//--------------------------------------------------------- +local s_List = {} -local function AddAliasedToTable(name, signature, description, table) +Hooks <- { - // This is an alias function, could use split() if we could guarantee - // that ':' would not occur elsewhere in the description and Squirrel had - // a convience join() function -- It has split() - local colon = description.find(":"); - if (colon == null) - colon = description.len(); - local alias = description.slice(1, colon); - description = description.slice(colon + 1); - name = alias; - signature = null; - - table[name] <- [signature, description]; -} - -function __Documentation::RegisterHelp(name, signature, description) -{ - if (description.len() && description[0] == '#') + // table, string, closure, string + function Add( scope, event, callback, context ) { - AddAliasedToTable(name, signature, description, DocumentedFuncs) + if ( typeof callback != "function" ) + throw "invalid callback param" + + if ( !(scope in s_List) ) + s_List[scope] <- {} + + local t = s_List[scope] + + if ( !(event in t) ) + t[event] <- {} + + t[event][context] <- callback } - else + + function Remove( context, event = null ) { - DocumentedFuncs[name] <- [signature, description]; + if ( event ) + { + foreach( k,scope in s_List ) + { + if ( event in scope ) + { + local t = scope[event] + if ( context in t ) + { + delete t[context] + } + + // cleanup? + if ( !t.len() ) + delete scope[event] + } + + // cleanup? + if ( !scope.len() ) + delete s_List[k] + } + } + else + { + foreach( k,scope in s_List ) + { + foreach( kk,ev in scope ) + { + if ( context in ev ) + { + delete ev[context] + } + + // cleanup? + if ( !ev.len() ) + delete scope[kk] + } + + // cleanup? + if ( !scope.len() ) + delete s_List[k] + } + } } -} -function __Documentation::RegisterClassHelp(name, baseclass, description) -{ - DocumentedClasses[name] <- [baseclass, description]; -} + function Call( scope, event, ... ) + { + local firstReturn = null -function __Documentation::RegisterEnumHelp(name, num_elements, description) -{ - DocumentedEnums[name] <- [num_elements, description]; -} + if ( scope == null ) + { + // null scope = global hook; call all scopes + vargv.insert(0,this) + foreach ( t in s_List ) + { + if ( event in t ) + { + foreach( context, callback in t[event] ) + { + //printf( "(%.4f) Calling hook '%s' of context '%s' in static iteration\n", Time(), event, context ) + + local curReturn = callback.acall(vargv) + if (firstReturn == null) + firstReturn = curReturn + } + } + } + } + else if ( scope in s_List ) + { + local t = s_List[scope] + if ( event in t ) + { + vargv.insert(0,scope) + foreach( context, callback in t[event] ) + { + //printf( "(%.4f) Calling hook '%s' of context '%s'\n", Time(), event, context ) + + local curReturn = callback.acall(vargv) + if (firstReturn == null) + firstReturn = curReturn + } + } + } -function __Documentation::RegisterConstHelp(name, signature, description) -{ - if (description.len() && description[0] == '#') - { - AddAliasedToTable(name, signature, description, DocumentedConsts) + return firstReturn } - else + + function ScopeHookedToEvent( scope, event ) { - DocumentedConsts[name] <- [signature, description]; + if ( scope in s_List ) + { + if (event in s_List[scope]) + return true + } + + return false } } -function __Documentation::RegisterHookHelp(name, signature, description) -{ - DocumentedHooks[name] <- [signature, description]; -} +//--------------------------------------------------------- +// Documentation +//--------------------------------------------------------- +__Documentation <- {} -function __Documentation::RegisterMemberHelp(name, signature, description) -{ - DocumentedMembers[name] <- [signature, description]; -} +local developer = (delete developer)() -local function printdoc( text ) +if (developer) { - return ::printc(200,224,255,text); -} + local DocumentedFuncs = {} + local DocumentedClasses = {} + local DocumentedEnums = {} + local DocumentedConsts = {} + local DocumentedHooks = {} + local DocumentedMembers = {} + + local function AddAliasedToTable(name, signature, description, table) + { + // This is an alias function, could use split() if we could guarantee + // that ':' would not occur elsewhere in the description and Squirrel had + // a convience join() function -- It has split() + local colon = description.find(":"); + if (colon == null) + colon = description.len(); + local alias = description.slice(1, colon); + description = description.slice(colon + 1); + name = alias; + signature = null; + + table[name] <- [signature, description]; + } -local function printdocl( text ) -{ - return printdoc(text + "\n"); -} + function __Documentation::RegisterHelp(name, signature, description) + { + if (description.len() && description[0] == '#') + { + AddAliasedToTable(name, signature, description, DocumentedFuncs) + } + else + { + DocumentedFuncs[name] <- [signature, description]; + } + } -local function PrintClass(name, doc) -{ - local text = "=====================================\n"; - text += ("Class: " + name + "\n"); - text += ("Base: " + doc[0] + "\n"); - if (doc[1].len()) - text += ("Description: " + doc[1] + "\n"); - text += "=====================================\n\n"; - - printdoc(text); -} + function __Documentation::RegisterClassHelp(name, baseclass, description) + { + DocumentedClasses[name] <- [baseclass, description]; + } -local function PrintFunc(name, doc) -{ - local text = "Function: " + name + "\n" + function __Documentation::RegisterEnumHelp(name, num_elements, description) + { + DocumentedEnums[name] <- [num_elements, description]; + } - if (doc[0] == null) + function __Documentation::RegisterConstHelp(name, signature, description) { - // Is an aliased function - text += ("Signature: function " + name + "("); - foreach(k,v in this[name].getinfos().parameters) + if (description.len() && description[0] == '#') + { + AddAliasedToTable(name, signature, description, DocumentedConsts) + } + else { - if (k == 0 && v == "this") continue; - if (k > 1) text += (", "); - text += (v); + DocumentedConsts[name] <- [signature, description]; } - text += (")\n"); } - else + + function __Documentation::RegisterHookHelp(name, signature, description) { - text += ("Signature: " + doc[0] + "\n"); + DocumentedHooks[name] <- [signature, description]; } - if (doc[1].len()) - text += ("Description: " + doc[1] + "\n"); - printdocl(text); -} -local function PrintMember(name, doc) -{ - local text = ("Member: " + name + "\n"); - text += ("Signature: " + doc[0] + "\n"); - if (doc[1].len()) - text += ("Description: " + doc[1] + "\n"); - printdocl(text); -} + function __Documentation::RegisterMemberHelp(name, signature, description) + { + DocumentedMembers[name] <- [signature, description]; + } -local function PrintEnum(name, doc) -{ - local text = "=====================================\n"; - text += ("Enum: " + name + "\n"); - text += ("Elements: " + doc[0] + "\n"); - if (doc[1].len()) - text += ("Description: " + doc[1] + "\n"); - text += "=====================================\n\n"; - - printdoc(text); -} + local function printdoc( text ) + { + return ::printc(200,224,255,text); + } -local function PrintConst(name, doc) -{ - local text = ("Constant: " + name + "\n"); - if (doc[0] == null) + local function printdocl( text ) { - text += ("Value: null\n"); + return printdoc(text + "\n"); } - else + + local function PrintClass(name, doc) { - text += ("Value: " + doc[0] + "\n"); + local text = "=====================================\n"; + text += ("Class: " + name + "\n"); + text += ("Base: " + doc[0] + "\n"); + if (doc[1].len()) + text += ("Description: " + doc[1] + "\n"); + text += "=====================================\n\n"; + + printdoc(text); } - if (doc[1].len()) - text += ("Description: " + doc[1] + "\n"); - printdocl(text); -} -local function PrintHook(name, doc) -{ - local text = ("Hook: " + name + "\n"); - if (doc[0] == null) + local function PrintFunc(name, doc) { - // Is an aliased function - text += ("Signature: function " + name + "("); - foreach(k,v in this[name].getinfos().parameters) + local text = "Function: " + name + "\n" + + if (doc[0] == null) + { + // Is an aliased function + text += ("Signature: function " + name + "("); + foreach(k,v in this[name].getinfos().parameters) + { + if (k == 0 && v == "this") continue; + if (k > 1) text += (", "); + text += (v); + } + text += (")\n"); + } + else { - if (k == 0 && v == "this") continue; - if (k > 1) text += (", "); - text += (v); + text += ("Signature: " + doc[0] + "\n"); } - text += (")\n"); + if (doc[1].len()) + text += ("Description: " + doc[1] + "\n"); + printdocl(text); } - else + + local function PrintMember(name, doc) { + local text = ("Member: " + name + "\n"); text += ("Signature: " + doc[0] + "\n"); + if (doc[1].len()) + text += ("Description: " + doc[1] + "\n"); + printdocl(text); } - if (doc[1].len()) - text += ("Description: " + doc[1] + "\n"); - printdocl(text); -} -local function PrintMatchesInDocList(pattern, list, printfunc) -{ - local foundMatches = 0; + local function PrintEnum(name, doc) + { + local text = "=====================================\n"; + text += ("Enum: " + name + "\n"); + text += ("Elements: " + doc[0] + "\n"); + if (doc[1].len()) + text += ("Description: " + doc[1] + "\n"); + text += "=====================================\n\n"; + + printdoc(text); + } - foreach(name, doc in list) + local function PrintConst(name, doc) { - if (pattern == "*" || name.tolower().find(pattern) != null || (doc[1].len() && doc[1].tolower().find(pattern) != null)) + local text = ("Constant: " + name + "\n"); + if (doc[0] == null) + { + text += ("Value: null\n"); + } + else { - foundMatches = 1; - printfunc(name, doc) + text += ("Value: " + doc[0] + "\n"); } + if (doc[1].len()) + text += ("Description: " + doc[1] + "\n"); + printdocl(text); } - return foundMatches; -} - -function __Documentation::PrintHelp(pattern = "*") -{ - local patternLower = pattern.tolower(); - - // Have a specific order - if (!( - PrintMatchesInDocList( patternLower, DocumentedEnums, PrintEnum ) | - PrintMatchesInDocList( patternLower, DocumentedConsts, PrintConst ) | - PrintMatchesInDocList( patternLower, DocumentedClasses, PrintClass ) | - PrintMatchesInDocList( patternLower, DocumentedFuncs, PrintFunc ) | - PrintMatchesInDocList( patternLower, DocumentedMembers, PrintMember ) | - PrintMatchesInDocList( patternLower, DocumentedHooks, PrintHook ) - )) + local function PrintHook(name, doc) { - printdocl("Pattern " + pattern + " not found"); + local text = ("Hook: " + name + "\n"); + if (doc[0] == null) + { + // Is an aliased function + text += ("Signature: function " + name + "("); + foreach(k,v in this[name].getinfos().parameters) + { + if (k == 0 && v == "this") continue; + if (k > 1) text += (", "); + text += (v); + } + text += (")\n"); + } + else + { + text += ("Signature: " + doc[0] + "\n"); + } + if (doc[1].len()) + text += ("Description: " + doc[1] + "\n"); + printdocl(text); } -} -// Vector documentation -__Documentation.RegisterClassHelp( "Vector", "", "Basic 3-float Vector class." ); -__Documentation.RegisterHelp( "Vector::Length", "float Vector::Length()", "Return the vector's length." ); -__Documentation.RegisterHelp( "Vector::LengthSqr", "float Vector::LengthSqr()", "Return the vector's squared length." ); -__Documentation.RegisterHelp( "Vector::Length2D", "float Vector::Length2D()", "Return the vector's 2D length." ); -__Documentation.RegisterHelp( "Vector::Length2DSqr", "float Vector::Length2DSqr()", "Return the vector's squared 2D length." ); + local function PrintMatches( pattern, docs, printfunc ) + { + local matches = []; + local always = pattern == "*"; + + foreach( name, doc in docs ) + { + if (always || name.tolower().find(pattern) != null || (doc[1].len() && doc[1].tolower().find(pattern) != null)) + { + matches.append( name ); + } + } -__Documentation.RegisterHelp( "Vector::Normalized", "float Vector::Normalized()", "Return a normalized version of the vector." ); -__Documentation.RegisterHelp( "Vector::Norm", "void Vector::Norm()", "Normalize the vector in place." ); -__Documentation.RegisterHelp( "Vector::Scale", "vector Vector::Scale(float)", "Scale the vector's magnitude and return the result." ); -__Documentation.RegisterHelp( "Vector::Dot", "float Vector::Dot(vector)", "Return the dot/scalar product of two vectors." ); -__Documentation.RegisterHelp( "Vector::Cross", "float Vector::Cross(vector)", "Return the vector product of two vectors." ); + if ( !matches.len() ) + return 0; -__Documentation.RegisterHelp( "Vector::ToKVString", "string Vector::ToKVString()", "Return a vector as a string in KeyValue form, without separation commas." ); + matches.sort(); -__Documentation.RegisterMemberHelp( "Vector.x", "float Vector.x", "The vector's X coordinate on the cartesian X axis." ); -__Documentation.RegisterMemberHelp( "Vector.y", "float Vector.y", "The vector's Y coordinate on the cartesian Y axis." ); -__Documentation.RegisterMemberHelp( "Vector.z", "float Vector.z", "The vector's Z coordinate on the cartesian Z axis." ); + foreach( name in matches ) + printfunc( name, docs[name] ); + return 1; + } + + function __Documentation::PrintHelp(pattern = "*") + { + local patternLower = pattern.tolower(); + + // Have a specific order + if (!( + PrintMatches( patternLower, DocumentedEnums, PrintEnum ) | + PrintMatches( patternLower, DocumentedConsts, PrintConst ) | + PrintMatches( patternLower, DocumentedClasses, PrintClass ) | + PrintMatches( patternLower, DocumentedFuncs, PrintFunc ) | + PrintMatches( patternLower, DocumentedMembers, PrintMember ) | + PrintMatches( patternLower, DocumentedHooks, PrintHook ) + )) + { + printdocl("Pattern " + pattern + " not found"); + } + } +} +else +{ + __Documentation.RegisterHelp <- + __Documentation.RegisterClassHelp <- + __Documentation.RegisterEnumHelp <- + __Documentation.RegisterConstHelp <- + __Documentation.RegisterHookHelp <- + __Documentation.RegisterMemberHelp <- dummy + + function __Documentation::PrintHelp( pattern = null ) + { + printcl(200, 224, 255, "Documentation is not enabled. To enable documentation, restart the server with the 'developer' cvar set to 1 or higher."); + } +} + +if (developer) +{ + __Documentation.RegisterClassHelp( "Vector", "", "Basic 3-float Vector class." ); + __Documentation.RegisterHelp( "Vector::Length", "float Vector::Length()", "Return the vector's length." ); + __Documentation.RegisterHelp( "Vector::LengthSqr", "float Vector::LengthSqr()", "Return the vector's squared length." ); + __Documentation.RegisterHelp( "Vector::Length2D", "float Vector::Length2D()", "Return the vector's 2D length." ); + __Documentation.RegisterHelp( "Vector::Length2DSqr", "float Vector::Length2DSqr()", "Return the vector's squared 2D length." ); + + __Documentation.RegisterHelp( "Vector::Normalized", "float Vector::Normalized()", "Return a normalized version of the vector." ); + __Documentation.RegisterHelp( "Vector::Norm", "void Vector::Norm()", "Normalize the vector in place." ); + __Documentation.RegisterHelp( "Vector::Scale", "vector Vector::Scale(float)", "Scale the vector's magnitude and return the result." ); + __Documentation.RegisterHelp( "Vector::Dot", "float Vector::Dot(vector)", "Return the dot/scalar product of two vectors." ); + __Documentation.RegisterHelp( "Vector::Cross", "float Vector::Cross(vector)", "Return the vector product of two vectors." ); + + __Documentation.RegisterHelp( "Vector::ToKVString", "string Vector::ToKVString()", "Return a vector as a string in KeyValue form, without separation commas." ); + + __Documentation.RegisterMemberHelp( "Vector.x", "float Vector.x", "The vector's X coordinate on the cartesian X axis." ); + __Documentation.RegisterMemberHelp( "Vector.y", "float Vector.y", "The vector's Y coordinate on the cartesian Y axis." ); + __Documentation.RegisterMemberHelp( "Vector.z", "float Vector.z", "The vector's Z coordinate on the cartesian Z axis." ); + + __Documentation.RegisterHelp( "clamp", "float clamp(float, float, float)", "" ); + __Documentation.RegisterHelp( "max", "float max(float, float)", "" ); + __Documentation.RegisterHelp( "min", "float min(float, float)", "" ); + __Documentation.RegisterHelp( "RemapVal", "float RemapVal(float, float, float, float, float)", "" ); + __Documentation.RegisterHelp( "RemapValClamped", "float RemapValClamped(float, float, float, float, float)", "" ); + __Documentation.RegisterHelp( "Approach", "float Approach(float, float, float)", "" ); + __Documentation.RegisterHelp( "AngleDistance", "float AngleDistance(float, float)", "" ); + __Documentation.RegisterHelp( "FLerp", "float FLerp(float, float, float, float, float)", "" ); + __Documentation.RegisterHelp( "Lerp", "float Lerp(float, float, float)", "" ); + __Documentation.RegisterHelp( "SimpleSpline", "float SimpleSpline(float)", "" ); +} )vscript"; \ No newline at end of file