diff --git a/.github/workflows/mapbase_build-base.yml b/.github/workflows/mapbase_build-base.yml index 5b791c2f..fac52365 100644 --- a/.github/workflows/mapbase_build-base.yml +++ b/.github/workflows/mapbase_build-base.yml @@ -51,7 +51,7 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Add MSBuild to PATH uses: microsoft/setup-msbuild@v1.1 diff --git a/.github/workflows/mapbase_build-sp-games.yml b/.github/workflows/mapbase_build-sp-games.yml index 3e81ca72..dc5511ae 100644 --- a/.github/workflows/mapbase_build-sp-games.yml +++ b/.github/workflows/mapbase_build-sp-games.yml @@ -13,6 +13,7 @@ on: branches: - develop paths: + - '.github/workflows/mapbase_build-base.yml' - '.github/workflows/mapbase_build-sp-rel-games.yml' - 'sp/src/vpc_scripts/**' - 'sp/src/game/**' diff --git a/.github/workflows/mapbase_pr.yml b/.github/workflows/mapbase_pr.yml index c7c6a3e6..b71506ea 100644 --- a/.github/workflows/mapbase_pr.yml +++ b/.github/workflows/mapbase_pr.yml @@ -6,12 +6,12 @@ # https://github.com/actions/labeler name: Pull Request Automation -on: [pull_request] +on: [pull_request] # pull_request_target jobs: label: - if: github.repository_owner == 'mapbase-source' + if: github.repository_owner == 'mapbase-source' # ${{ vars.MAPBASE_LABELS == 'true' }} runs-on: ubuntu-latest permissions: contents: read diff --git a/README b/README index 6215f047..d9d11024 100644 --- a/README +++ b/README @@ -1,6 +1,6 @@ //========================================================================================================================= - Mapbase v7.1 - Source 2013 + Mapbase v7.2 - Source 2013 https://github.com/mapbase-source/source-sdk-2013 https://www.moddb.com/mods/mapbase @@ -112,6 +112,12 @@ Direct contributions: - https://github.com/mapbase-source/source-sdk-2013/issues/201 (env_projectedtexture shadow filter keyvalue from celisej567) - https://github.com/mapbase-source/source-sdk-2013/pull/193 (RTB:R info_particle_system_coordinate by arbabf and Iridium77) - https://github.com/mapbase-source/source-sdk-2013/pull/193 (Infinite prop_interactable cooldown by arbabf) +- https://github.com/mapbase-source/source-sdk-2013/pull/229 (Extended point_bugbait functionality by arbabf) +- https://github.com/mapbase-source/source-sdk-2013/pull/236 (Toggleable prop sprinting by Crimson-X1) +- https://github.com/mapbase-source/source-sdk-2013/pull/237 (Commander goal trace fix by Agrimar) +- https://github.com/mapbase-source/source-sdk-2013/pull/245 (ViewPunch random fix by Mr0maks) +- https://github.com/mapbase-source/source-sdk-2013/pull/248 (soundlevel_t conversation warning fix by Mechami) +- https://github.com/mapbase-source/mapbase-game-src/pull/1 (Advanced video options duplicate field name fix by arbabf; This is asset-based and not reflected in the code) - Demo autorecord code provided by Klems - cc_emit crash fix provided by 1upD - Custom HL2 ammo crate models created by Rykah (Textures created by Blixibon; This is asset-based and, aside from the SLAM crate, not reflected in the code) @@ -137,6 +143,9 @@ Direct contributions: =-- https://github.com/mapbase-source/source-sdk-2013/pull/192 (VScript hook manager and fixes) =-- https://github.com/mapbase-source/source-sdk-2013/pull/206 (Fix CScriptNetMsgHelper::WriteEntity()) =-- https://github.com/mapbase-source/source-sdk-2013/pull/213 (VScript HUD visibility control, optimizations for 3D skybox angles/fake worldportals) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/229 (VScript VGUI HUD viewport parenting, game_text and vgui_text_display VScript font fallback) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/261 (Misc VScript additions) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/279 (weapon_custom_scripted fixes) == Contributions from z33ky: =-- https://github.com/mapbase-source/source-sdk-2013/pull/21 (Various GCC/Linux compilation fixes) @@ -156,6 +165,9 @@ Direct contributions: =-- https://github.com/mapbase-source/source-sdk-2013/pull/184 (Projected texture horizontal FOV shadow fix) =-- https://github.com/mapbase-source/source-sdk-2013/pull/185 (Fix enemyfinders becoming visible when they wake) =-- https://github.com/mapbase-source/source-sdk-2013/pull/186 (Fix for brightly glowing teeth) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/183 (Enhanced custom weapons support) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/230 (Caption fixes) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/231 (Sentence source bug fix) //------------------------------------------------------------------------------------------------------------------------- diff --git a/mp/src/game/shared/hl2mp/weapon_ar2.cpp b/mp/src/game/shared/hl2mp/weapon_ar2.cpp index 5086db1b..faa5b14c 100644 --- a/mp/src/game/shared/hl2mp/weapon_ar2.cpp +++ b/mp/src/game/shared/hl2mp/weapon_ar2.cpp @@ -208,7 +208,7 @@ void CWeaponAR2::DelayedAttack( void ) // pOwner->SnapEyeAngles( angles ); - pOwner->ViewPunch( QAngle( SharedRandomInt( "ar2pax", -8, -12 ), SharedRandomInt( "ar2pay", 1, 2 ), 0 ) ); + pOwner->ViewPunch( QAngle( SharedRandomInt( "ar2pax", -12, -8 ), SharedRandomInt( "ar2pay", 1, 2 ), 0 ) ); // Decrease ammo pOwner->RemoveAmmo( 1, m_iSecondaryAmmoType ); diff --git a/sp/src/game/client/c_baseanimating.cpp b/sp/src/game/client/c_baseanimating.cpp index 7afc4d22..edde6282 100644 --- a/sp/src/game/client/c_baseanimating.cpp +++ b/sp/src/game/client/c_baseanimating.cpp @@ -4042,6 +4042,92 @@ void C_BaseAnimating::FireEvent( const Vector& origin, const QAngle& angles, int } break; +#ifdef MAPBASE // From Alien Swarm SDK + case AE_CL_STOP_PARTICLE_EFFECT: + { + char token[256]; + char szParticleEffect[256]; + + // Get the particle effect name + const char *p = options; + p = nexttoken(token, p, ' ', sizeof(token)); + if ( token ) + { + Q_strncpy( szParticleEffect, token, sizeof(szParticleEffect) ); + } + + // Get the attachment point index + p = nexttoken(token, p, ' ', sizeof(token)); + bool bStopInstantly = ( token && !Q_stricmp( token, "instantly" ) ); + + ParticleProp()->StopParticlesNamed( szParticleEffect, bStopInstantly ); + } + break; + + case AE_CL_ADD_PARTICLE_EFFECT_CP: + { + int iControlPoint = 1; + int iAttachment = -1; + int iAttachType = PATTACH_ABSORIGIN_FOLLOW; + int iEffectIndex = -1; + char token[256]; + char szParticleEffect[256]; + + // Get the particle effect name + const char *p = options; + p = nexttoken(token, p, ' ', sizeof(token)); + if ( token ) + { + Q_strncpy( szParticleEffect, token, sizeof(szParticleEffect) ); + } + + // Get the control point number + p = nexttoken(token, p, ' ', sizeof(token)); + if ( token ) + { + iControlPoint = atoi( token ); + } + + // Get the attachment type + p = nexttoken(token, p, ' ', sizeof(token)); + if ( token ) + { + iAttachType = GetAttachTypeFromString( token ); + if ( iAttachType == -1 ) + { + Warning("Invalid attach type specified for particle effect anim event. Trying to spawn effect '%s' with attach type of '%s'\n", szParticleEffect, token ); + return; + } + } + + // Get the attachment point index + p = nexttoken(token, p, ' ', sizeof(token)); + if ( token ) + { + iAttachment = atoi(token); + + // See if we can find any attachment points matching the name + if ( token[0] != '0' && iAttachment == 0 ) + { + iAttachment = LookupAttachment( token ); + if ( iAttachment == -1 ) + { + Warning("Failed to find attachment point specified for particle effect anim event. Trying to spawn effect '%s' on attachment named '%s'\n", szParticleEffect, token ); + return; + } + } + } + iEffectIndex = ParticleProp()->FindEffect( szParticleEffect ); + if ( iEffectIndex == -1 ) + { + Warning("Failed to find specified particle effect. Trying to add CP to '%s' on attachment named '%s'\n", szParticleEffect, token ); + return; + } + ParticleProp()->AddControlPoint( iEffectIndex, iControlPoint, this, (ParticleAttachment_t)iAttachType, iAttachment ); + } + break; +#endif + case AE_CL_PLAYSOUND: { CLocalPlayerFilter filter; @@ -4291,6 +4377,22 @@ void C_BaseAnimating::FireEvent( const Vector& origin, const QAngle& angles, int } break; +#ifdef MAPBASE + case AE_VSCRIPT_RUN: + { + if (!RunScript( options )) + Warning( "%s failed to run AE_VSCRIPT_RUN on client with \"%s\"\n", GetDebugName(), options ); + } + break; + + case AE_VSCRIPT_RUN_FILE: + { + if (!RunScriptFile( options )) + Warning( "%s failed to run AE_VSCRIPT_RUN_FILE on client with \"%s\"\n", GetDebugName(), options ); + } + break; +#endif + default: break; } diff --git a/sp/src/game/client/c_basecombatcharacter.cpp b/sp/src/game/client/c_basecombatcharacter.cpp index 1656f567..0afe4cbf 100644 --- a/sp/src/game/client/c_basecombatcharacter.cpp +++ b/sp/src/game/client/c_basecombatcharacter.cpp @@ -181,7 +181,7 @@ END_PREDICTION_DATA() #ifdef MAPBASE_VSCRIPT -BEGIN_ENT_SCRIPTDESC( C_BaseCombatCharacter, CBaseEntity, "" ) +BEGIN_ENT_SCRIPTDESC( C_BaseCombatCharacter, C_BaseAnimating, "" ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetAmmoCount, "GetAmmoCount", "" ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetActiveWeapon, "GetActiveWeapon", "" ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetWeapon, "GetWeapon", "" ) diff --git a/sp/src/game/client/c_baseentity.cpp b/sp/src/game/client/c_baseentity.cpp index 43471e9d..1d3de004 100644 --- a/sp/src/game/client/c_baseentity.cpp +++ b/sp/src/game/client/c_baseentity.cpp @@ -502,6 +502,9 @@ BEGIN_ENT_SCRIPTDESC_ROOT( C_BaseEntity, "Root class of all client-side entities DEFINE_SCRIPTFUNC_NAMED( GetScriptOwnerEntity, "GetOwner", "Gets this entity's owner" ) DEFINE_SCRIPTFUNC_NAMED( SetScriptOwnerEntity, "SetOwner", "Sets this entity's owner" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetGroundEntity, "GetGroundEntity", "Get the entity we're standing on." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetGroundEntity, "SetGroundEntity", "Set the entity we're standing on." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorVector, "GetRenderColorVector", "Get the render color as a vector" ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorR, "GetRenderColorR", "Get the render color's R value" ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorG, "GetRenderColorG", "Get the render color's G value" ) diff --git a/sp/src/game/client/c_baseentity.h b/sp/src/game/client/c_baseentity.h index fe159afe..422a59d4 100644 --- a/sp/src/game/client/c_baseentity.h +++ b/sp/src/game/client/c_baseentity.h @@ -288,6 +288,11 @@ public: HSCRIPT GetScriptOwnerEntity(); virtual void SetScriptOwnerEntity(HSCRIPT pOwner); +#ifdef MAPBASE_VSCRIPT + HSCRIPT ScriptGetGroundEntity(); + void ScriptSetGroundEntity( HSCRIPT hGroundEnt ); +#endif + HSCRIPT GetScriptInstance(); HSCRIPT m_hScriptInstance; diff --git a/sp/src/game/client/client_mapbase.vpc b/sp/src/game/client/client_mapbase.vpc index 4e604314..7b3b28f3 100644 --- a/sp/src/game/client/client_mapbase.vpc +++ b/sp/src/game/client/client_mapbase.vpc @@ -69,6 +69,7 @@ $Project $File "mapbase\c_func_fake_worldportal.h" $File "mapbase\c_point_glow.cpp" $File "mapbase\c_vgui_text_display.cpp" + $File "mapbase\c_weapon_custom_hl2.cpp" $File "mapbase\mapbase_autocubemap.cpp" } diff --git a/sp/src/game/client/hud_closecaption.cpp b/sp/src/game/client/hud_closecaption.cpp index e2d718c9..5fdfdfa1 100644 --- a/sp/src/game/client/hud_closecaption.cpp +++ b/sp/src/game/client/hud_closecaption.cpp @@ -1496,9 +1496,23 @@ void CHudCloseCaption::Process( const wchar_t *stream, float duration, const cha if ( m_Items.Count() > 0 ) { +#ifndef MAPBASE // Get the remaining life span of the last item - CCloseCaptionItem *final = m_Items[ m_Items.Count() - 1 ]; + CCloseCaptionItem* final = m_Items[m_Items.Count() - 1]; float prevlife = final->GetTimeToLive(); +#else + float prevlife = 0.f; + // Get the remaining life span of the last displayed item + for (int i = m_Items.Count() - 1; i >= 0; i--) + { + if (m_Items[i]->GetPreDisplayTime() > cc_predisplay_time.GetFloat()) + continue; + + prevlife = m_Items[i]->GetTimeToLive(); + break; + } +#endif // !MAPBASE + if ( prevlife > lifespan ) { @@ -1532,7 +1546,31 @@ void CHudCloseCaption::Process( const wchar_t *stream, float duration, const cha if ( wcslen( phrase ) > 0 ) { CCloseCaptionItem *item = new CCloseCaptionItem( phrase, lifespan, addedlife, delay, valid, fromplayer ); - m_Items.AddToTail( item ); +#ifdef MAPBASE + if (m_Items.Count()) + { + // Add it where it will appear + for (int i = m_Items.Count() - 1; i >= 0; i--) + { + if (m_Items[i]->GetPreDisplayTime() > delay + cc_predisplay_time.GetFloat()) + { + if (i == 0) + { + m_Items.AddToHead(item); + break; + } + else + continue; + } + + m_Items.InsertAfter(i, item); + break; + } + } + else +#endif // MAPBASE + m_Items.AddToTail(item); + if ( StreamHasCommand( phrase, L"sfx" ) ) { // SFX show up instantly. @@ -1541,6 +1579,9 @@ void CHudCloseCaption::Process( const wchar_t *stream, float duration, const cha if ( GetFloatCommandValue( phrase, L"len", override_duration ) ) { +#ifdef MAPBASE + override_duration += cc_linger_time.GetFloat(); +#endif // MAPBASE item->SetTimeToLive( override_duration ); } } @@ -1569,7 +1610,30 @@ void CHudCloseCaption::Process( const wchar_t *stream, float duration, const cha if ( wcslen( phrase ) > 0 ) { CCloseCaptionItem *item = new CCloseCaptionItem( phrase, lifespan, addedlife, delay, valid, fromplayer ); - m_Items.AddToTail( item ); +#ifdef MAPBASE + if (m_Items.Count()) + { + // Add it where it will appear + for (int i = m_Items.Count() - 1; i >= 0; i--) + { + if (m_Items[i]->GetPreDisplayTime() > delay + cc_predisplay_time.GetFloat()) + { + if (i == 0) + { + m_Items.AddToHead(item); + break; + } + else + continue; + } + + m_Items.InsertAfter(i, item); + break; + } + } + else +#endif // MAPBASE + m_Items.AddToTail(item); if ( StreamHasCommand( phrase, L"sfx" ) ) { @@ -1579,6 +1643,10 @@ void CHudCloseCaption::Process( const wchar_t *stream, float duration, const cha if ( GetFloatCommandValue( phrase, L"len", override_duration ) ) { +#ifdef MAPBASE + override_duration += cc_linger_time.GetFloat(); +#endif // MAPBASE + item->SetTimeToLive( override_duration ); item->SetInitialLifeSpan( override_duration ); } @@ -2614,8 +2682,14 @@ void CHudCloseCaption::InitCaptionDictionary( const char *dbfile ) g_AsyncCaptionResourceManager.Clear(); +#ifdef MAPBASE + int iBufferSize = filesystem->GetSearchPath("GAME", true, nullptr, 0); + char* searchPaths = (char*)stackalloc(iBufferSize); + filesystem->GetSearchPath("GAME", true, searchPaths, iBufferSize); +#else char searchPaths[4096]; filesystem->GetSearchPath( "GAME", true, searchPaths, sizeof( searchPaths ) ); +#endif for ( char *path = strtok( searchPaths, ";" ); path; path = strtok( NULL, ";" ) ) { @@ -2626,8 +2700,13 @@ void CHudCloseCaption::InitCaptionDictionary( const char *dbfile ) } char fullpath[MAX_PATH]; - Q_snprintf( fullpath, sizeof( fullpath ), "%s%s", path, dbfile ); - Q_FixSlashes( fullpath ); +#ifndef MAPBASE + Q_snprintf(fullpath, sizeof(fullpath), "%s%s", path, dbfile); + Q_FixSlashes(fullpath); +#else + V_ComposeFileName(path, dbfile, fullpath, sizeof(fullpath)); +#endif // !MAPBASE + if ( IsX360() ) { @@ -2692,8 +2771,7 @@ void CHudCloseCaption::AddAdditionalCaptionDictionary( const char *dbfile, CUtlV } char fullpath[MAX_PATH]; - Q_snprintf( fullpath, sizeof( fullpath ), "%s%s", path, dbfile ); - Q_FixSlashes( fullpath ); + V_ComposeFileName(path, dbfile, fullpath, sizeof(fullpath)); if ( IsX360() ) { 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 f0d2032d..de8e8450 100644 --- a/sp/src/game/client/mapbase/c_vgui_text_display.cpp +++ b/sp/src/game/client/mapbase/c_vgui_text_display.cpp @@ -215,6 +215,13 @@ void C_TextDisplayPanel::UpdateText() if (pszFontName && pszFontName[0] != '\0') { HFont font = scheme()->GetIScheme( GetScheme() )->GetFont( pszFontName ); + + if ( !font ) + { + extern HFont GetScriptFont( const char *, bool ); + font = GetScriptFont( pszFontName, false ); + } + m_pDisplayTextLabel->SetFont( font ); } diff --git a/sp/src/game/client/mapbase/c_weapon_custom_hl2.cpp b/sp/src/game/client/mapbase/c_weapon_custom_hl2.cpp new file mode 100644 index 00000000..784ba9ea --- /dev/null +++ b/sp/src/game/client/mapbase/c_weapon_custom_hl2.cpp @@ -0,0 +1,91 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Client classes for Half-Life 2 based custom weapons. +// +// Author: Peter Covington (petercov@outlook.com) +// +//==================================================================================// + +#include "cbase.h" +#include "c_weapon__stubs.h" +#include "basehlcombatweapon_shared.h" +#include "c_basehlcombatweapon.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class C_HLCustomWeaponMelee : public C_BaseHLBludgeonWeapon +{ +public: + DECLARE_CLASS(C_HLCustomWeaponMelee, C_BaseHLBludgeonWeapon); + DECLARE_CLIENTCLASS(); + DECLARE_PREDICTABLE(); + + C_HLCustomWeaponMelee(); + + void OnDataChanged( DataUpdateType_t updateType ); + + virtual const char* GetWeaponScriptName() { return m_iszWeaponScriptName; } +private: + char m_iszWeaponScriptName[128]; +}; + +STUB_WEAPON_CLASS_IMPLEMENT(weapon_hlcustommelee, C_HLCustomWeaponMelee); + +IMPLEMENT_CLIENTCLASS_DT(C_HLCustomWeaponMelee, DT_HLCustomWeaponMelee, CHLCustomWeaponMelee) +RecvPropString(RECVINFO(m_iszWeaponScriptName)), +END_RECV_TABLE(); + +C_HLCustomWeaponMelee::C_HLCustomWeaponMelee() +{ + m_iszWeaponScriptName[0] = '\0'; +} + +void C_HLCustomWeaponMelee::OnDataChanged( DataUpdateType_t updateType ) +{ + if (updateType == DATA_UPDATE_CREATED) + { + Precache(); + } + + BaseClass::OnDataChanged( updateType ); +} + + + +class C_HLCustomWeaponGun : public C_BaseHLCombatWeapon +{ +public: + DECLARE_CLASS(C_HLCustomWeaponGun, C_BaseHLCombatWeapon); + DECLARE_CLIENTCLASS(); + DECLARE_PREDICTABLE(); + + C_HLCustomWeaponGun(); + + void OnDataChanged( DataUpdateType_t updateType ); + + virtual const char* GetWeaponScriptName() { return m_iszWeaponScriptName; } +private: + char m_iszWeaponScriptName[128]; +}; + +STUB_WEAPON_CLASS_IMPLEMENT(weapon_hlcustomgun, C_HLCustomWeaponGun); + +IMPLEMENT_CLIENTCLASS_DT(C_HLCustomWeaponGun, DT_HLCustomWeaponGun, CHLCustomWeaponGun) +RecvPropString(RECVINFO(m_iszWeaponScriptName)), +END_RECV_TABLE(); + +C_HLCustomWeaponGun::C_HLCustomWeaponGun() +{ + m_iszWeaponScriptName[0] = '\0'; +} + +void C_HLCustomWeaponGun::OnDataChanged( DataUpdateType_t updateType ) +{ + if (updateType == DATA_UPDATE_CREATED) + { + Precache(); + } + + BaseClass::OnDataChanged( updateType ); +} diff --git a/sp/src/game/client/mapbase/vscript_vgui.cpp b/sp/src/game/client/mapbase/vscript_vgui.cpp index 8d08e8be..805d3305 100644 --- a/sp/src/game/client/mapbase/vscript_vgui.cpp +++ b/sp/src/game/client/mapbase/vscript_vgui.cpp @@ -51,7 +51,7 @@ #include "view.h" #include "hudelement.h" -//#include "iclientmode.h" // g_pClientMode->GetViewport() +#include "iclientmode.h" // g_pClientMode->GetViewport() #include "vscript_vgui.h" #include "vscript_vgui.nut" @@ -92,10 +92,18 @@ // Changing this is not backwards compatible, as existing top level script panel depth would then change relative to non-script panels. #define SCRIPT_ENGINE_ROOT_PANELS 1 -// NOTE: causes rendering issues -#define ALLOW_SCRIPT_HUD_VIEWPORT_ROOT_PANEL 0 +// +// Options to restrict where script panels can be parented to. +// The safest options any game can have are HUD viewport and clientdll. +// -#define ALLOW_SCRIPT_GAMEUI_ROOT_PANEL 0 +#define ALLOW_ROOT_PANEL_PARENT 1 + +#define ALLOW_HUD_VIEWPORT_ROOT_PARENT 1 + +#define ALLOW_CLIENTDLL_ROOT_PARENT 1 + +#define ALLOW_GAMEUI_ROOT_PARENT 0 // On level transitions Restore is called up to 4 times in a row (due to .hl? client state files), each time // trying to restore script panels from pre and post transitions, failing every time because script panels are @@ -353,8 +361,10 @@ public: } }; +#if ALLOW_CLIENTDLL_ROOT_PARENT CScriptRootDLLPanel *g_pScriptClientDLLPanel = NULL; -#if ALLOW_SCRIPT_GAMEUI_ROOT_PANEL +#endif +#if ALLOW_GAMEUI_ROOT_PARENT CScriptRootDLLPanel *g_pScriptGameUIDLLPanel = NULL; #endif #endif @@ -367,12 +377,14 @@ void VGUI_DestroyScriptRootPanels() g_pScriptRootPanel = NULL; } #if SCRIPT_ENGINE_ROOT_PANELS +#if ALLOW_CLIENTDLL_ROOT_PARENT if ( g_pScriptClientDLLPanel ) { delete g_pScriptClientDLLPanel; g_pScriptClientDLLPanel = NULL; } -#if ALLOW_SCRIPT_GAMEUI_ROOT_PANEL +#endif +#if ALLOW_GAMEUI_ROOT_PARENT if ( g_pScriptGameUIDLLPanel ) { delete g_pScriptGameUIDLLPanel; @@ -384,30 +396,29 @@ void VGUI_DestroyScriptRootPanels() VPANEL VGUI_GetScriptRootPanel( VGuiPanel_t type ) { -#if !SCRIPT_ENGINE_ROOT_PANELS - if ( !g_pScriptRootPanel ) - g_pScriptRootPanel = new CScriptRootPanel(); - - return enginevgui->GetPanel( type ); -#else +#if SCRIPT_ENGINE_ROOT_PANELS switch ( type ) { case PANEL_ROOT: +#if ALLOW_ROOT_PANEL_PARENT { if ( !g_pScriptRootPanel ) g_pScriptRootPanel = new CScriptRootPanel(); return g_pScriptRootPanel->GetVPanel(); } +#endif case PANEL_CLIENTDLL: +#if ALLOW_CLIENTDLL_ROOT_PARENT { if ( !g_pScriptClientDLLPanel ) g_pScriptClientDLLPanel = new CScriptRootDLLPanel( PANEL_CLIENTDLL, "VScriptClient" ); return g_pScriptClientDLLPanel->GetVPanel(); } -#if ALLOW_SCRIPT_GAMEUI_ROOT_PANEL +#endif case PANEL_GAMEUIDLL: +#if ALLOW_GAMEUI_ROOT_PARENT { if ( !g_pScriptGameUIDLLPanel ) g_pScriptGameUIDLLPanel = new CScriptRootDLLPanel( PANEL_GAMEUIDLL, "VScriptGameUI" ); @@ -415,8 +426,10 @@ VPANEL VGUI_GetScriptRootPanel( VGuiPanel_t type ) return g_pScriptGameUIDLLPanel->GetVPanel(); } #endif + default: return NULL; } - return NULL; +#else + return enginevgui->GetPanel(type); #endif } @@ -663,6 +676,11 @@ int CScriptSurface::GetCharacterWidth( int font, int ch ) void CScriptSurface::CreateFont( const char *customName, const char *windowsFontName, int tall, int weight, int blur, int scanlines, int flags, int yresMin, int yresMax, bool proportional ) { + // Make sure font invalidation callback is established. + // Not necessary if script fonts are reloaded in engine. + if ( !g_pScriptRootPanel ) + g_pScriptRootPanel = new CScriptRootPanel(); + if ( flags & ISurface::FONTFLAG_BITMAP ) { AssertMsg( 0, "Bitmap fonts are not supported!" ); @@ -681,16 +699,41 @@ void CScriptSurface::CreateFont( const char *customName, const char *windowsFont return; } +#if 0 + bool bProportionalFallbackFont = false; + if ( proportional ) + { + // Find if this is a resolution filtered font alias + const char *fontAlias = GetFixedFontName( customName, false ); + int idx = g_ScriptFonts.Find( fontAlias ); + if ( idx != g_ScriptFonts.InvalidIndex() ) + { + fontalias_t &alias = g_ScriptFonts[idx]; + for ( int i = 0; i < alias.Count(); ++i ) + { + FontData_t &data = alias.Element(i); + if ( data.yres_min && data.yres_max ) + { + bProportionalFallbackFont = true; + + // Save this proportional font in non-proportional alias + proportional = false; + break; + } + } + } + } +#endif + const char *fontAlias = GetFixedFontName( customName, proportional ); int idx = g_ScriptFonts.Find( fontAlias ); if ( idx != g_ScriptFonts.InvalidIndex() ) { fontalias_t &alias = g_ScriptFonts[idx]; - // TODO: One proportional font to fall back to amongst resolution filtered fonts. #ifdef _DEBUG - if ( !yresMin && !yresMax ) + if ( !yresMin && !yresMax /*&& !bProportionalFallbackFont*/ ) { // There must be only one font registered. Assert( alias.Count() == 1 ); @@ -703,7 +746,7 @@ void CScriptSurface::CreateFont( const char *customName, const char *windowsFont // Font changes will not be applied. Assert( oldTall == newTall ); if ( oldName ) // can be null - Assert( !V_stricmp( oldName, windowsFontName ) ); + AssertMsg( !V_stricmp( oldName, windowsFontName ), "'%s' != '%s'", oldName, windowsFontName ); } #endif @@ -716,7 +759,10 @@ void CScriptSurface::CreateFont( const char *customName, const char *windowsFont if ( yresMin == data.yres_min && yresMax == data.yres_max ) return; } - +#if 0 + if ( bProportionalFallbackFont ) + proportional = true; +#endif DebugMsg( "Create font add '%s' [%d %d]\n", fontAlias, yresMin, yresMax ); FontData_t &newFont = alias.Element( alias.AddToTail() ); @@ -731,6 +777,22 @@ void CScriptSurface::CreateFont( const char *customName, const char *windowsFont newFont.yres_max = yresMax; newFont.proportional = proportional; +#if 0 + // Put the proportional font in the very end so that it is loaded only when no resolution is matched + struct L + { + static int __cdecl F( const FontData_t* a, const FontData_t* b ) + { + if ( !a->proportional && b->proportional ) + return -1; + if ( a->proportional && !b->proportional ) + return 1; + return 0; + } + }; + alias.Sort( L::F ); +#endif + LoadFont( newFont DBG_PARAM(, fontAlias) ); } else @@ -1067,7 +1129,7 @@ public:\ class CScript_##panelClass : public panelClass\ {\ DECLARE_SCRIPTVGUI_CLASS( panelClass )\ - void Shutdown() {}\ + void ScriptShutdown() {}\ \ public:\ CScript_##panelClass( Panel *parent, const char *name )\ @@ -1085,7 +1147,7 @@ public:\ class CScript_##panelClass : public panelClass\ {\ DECLARE_SCRIPTVGUI_CLASS( panelClass )\ - void Shutdown() {}\ + void ScriptShutdown() {}\ \ public:\ CScript_##panelClass( Panel *parent, const char *name, const char *text )\ @@ -1292,7 +1354,7 @@ public: if ( GetVPanel() ) { DebugMsg( " Destroy panel '%s' %s\n", _base->GetName(), GetDebugName() ); - _base->Shutdown(); + _base->ScriptShutdown(); ResolveChildren_r( _vpanel ); _base->MarkForDeletion(); } @@ -1339,6 +1401,11 @@ public: } AssertMsg( 0, "invalid parent" ); + + g_ScriptPanels.AddToTail( this ); + + // leave me parentless + return; } g_ScriptPanels.AddToTail( this ); @@ -1348,27 +1415,37 @@ public: // // This parameter is hidden in script, and is defined by the return value of dummy functions. VPANEL vparent = 0; + switch ( root ) { + #if ALLOW_ROOT_PANEL_PARENT case 0: vparent = VGUI_GetScriptRootPanel( PANEL_ROOT ); break; -#if ALLOW_SCRIPT_GAMEUI_ROOT_PANEL + #endif + #if ALLOW_GAMEUI_ROOT_PARENT case 1: vparent = VGUI_GetScriptRootPanel( PANEL_GAMEUIDLL ); break; -#endif + #endif + #if ALLOW_CLIENTDLL_ROOT_PARENT case 2: vparent = VGUI_GetScriptRootPanel( PANEL_CLIENTDLL ); break; -#if ALLOW_SCRIPT_HUD_VIEWPORT_ROOT_PANEL - // Hud viewport - case 10: + #endif + #if ALLOW_HUD_VIEWPORT_ROOT_PARENT + case 10: // Hud viewport Assert( g_pClientMode && g_pClientMode->GetViewport() ); vparent = g_pClientMode->GetViewport()->GetVPanel(); break; -#endif - default: UNREACHABLE(); // Invalid parent panel + #endif + default: + #if SCRIPT_ENGINE_ROOT_PANELS + UNREACHABLE(); // Invalid parent panel + #else + // Allow everything defined in vscript_vgui.nut + vparent = VGUI_GetScriptRootPanel( (VGuiPanel_t)root ); + #endif } _base->SetParent( vparent ); @@ -1400,6 +1477,11 @@ public: { ivgui()->AddTickSignal( this->GetVPanel(), i ); } + + void RemoveTickSignal() + { + ivgui()->RemoveTickSignal( this->GetVPanel() ); + } #if SCRIPT_VGUI_SIGNAL_INTERFACE void AddActionSignalTarget( HSCRIPT messageTarget ) { @@ -1434,10 +1516,12 @@ public: bool bRootParent = false; #if SCRIPT_ENGINE_ROOT_PANELS if ( ( parent == g_pScriptRootPanel->GetVPanel() ) - #if ALLOW_SCRIPT_GAMEUI_ROOT_PANEL + #if ALLOW_GAMEUI_ROOT_PARENT || ( g_pScriptGameUIDLLPanel && parent == g_pScriptGameUIDLLPanel->GetVPanel() ) #endif + #if ALLOW_CLIENTDLL_ROOT_PARENT || ( g_pScriptClientDLLPanel && parent == g_pScriptClientDLLPanel->GetVPanel() ) + #endif ) { bRootParent = true; @@ -1452,7 +1536,7 @@ public: break; } } -#if ALLOW_SCRIPT_HUD_VIEWPORT_ROOT_PANEL +#if ALLOW_HUD_VIEWPORT_ROOT_PARENT if ( g_pClientMode && g_pClientMode->GetViewport() && ( parent == g_pClientMode->GetViewport()->GetVPanel() ) ) bRootParent = true; #endif @@ -1788,6 +1872,7 @@ public: DEFINE_SCRIPTFUNC( MakeReadyForUse, "" )\ DEFINE_SCRIPTFUNC( GetName, "" )\ DEFINE_SCRIPTFUNC( AddTickSignal, "" )\ + DEFINE_SCRIPTFUNC( RemoveTickSignal, "" )\ \ DEFINE_SCRIPTFUNC( GetParent, "" )\ DEFINE_SCRIPTFUNC( SetParent, "" )\ @@ -1829,10 +1914,12 @@ public: \ DEFINE_SCRIPTFUNC( SetCursor, "" )\ DEFINE_SCRIPTFUNC( IsCursorOver, "" )\ +\ DEFINE_SCRIPTFUNC( HasFocus, "" )\ DEFINE_SCRIPTFUNC( RequestFocus, "" )\ DEFINE_SCRIPTFUNC( MakePopup, "" )\ DEFINE_SCRIPTFUNC( MoveToFront, "" )\ +\ DEFINE_SCRIPTFUNC( SetMouseInputEnabled, "" )\ DEFINE_SCRIPTFUNC( SetKeyBoardInputEnabled, "" )\ \ @@ -1842,10 +1929,7 @@ public: //-------------------------------------------------------------- //-------------------------------------------------------------- // These need more testing. -// TODO: IScript_Panel::FindChildByName() // TODO: DECLARE_BUILD_FACTORY_SCRIPT() to create overridable script panels from controls file -// TODO: CScript_EditablePanel::ApplySchemeSettings() callback -// (IScheme parameter can be passed as null until schemes are also tested) #if BUILD_GROUPS_ENABLED CLASS_HELPER_INTERFACE( EditablePanel, Panel ) { @@ -1855,11 +1939,27 @@ public: { __base()->LoadControlSettings( resName ); } + + HSCRIPT FindChildByName( const char *childName ) + { + Panel *pPanel = __base()->FindChildByName( childName, false ); + if ( pPanel ) + { + int i; + IScriptVGUIObject* obj = FindInScriptPanels( child, i ); + if ( obj ) + { + return obj->GetScriptInstance(); + } + } + return NULL; + } }; #define DEFINE_VGUI_SCRIPTFUNC_EditablePanel()\ DEFINE_VGUI_SCRIPTFUNC_Panel()\ - DEFINE_SCRIPTFUNC( LoadControlSettings, "" ) + DEFINE_SCRIPTFUNC( LoadControlSettings, "" )\ + DEFINE_SCRIPTFUNC( FindChildByName, "" ) #endif //-------------------------------------------------------------- //-------------------------------------------------------------- @@ -2106,6 +2206,16 @@ public: { __base()->SetShouldScaleImage( state ); } + + void SetRotation( int rotation ) + { + Assert( rotation == ROTATED_UNROTATED || + rotation == ROTATED_CLOCKWISE_90 || + rotation == ROTATED_ANTICLOCKWISE_90 || + rotation == ROTATED_FLIPPED ); + + __base()->SetRotation( rotation ); + } #if 0 void SetFrame( int nFrame ) { @@ -2120,6 +2230,7 @@ public: DEFINE_SCRIPTFUNC( SetDrawColor, "" )\ DEFINE_SCRIPTFUNC( SetTileImage, "" )\ DEFINE_SCRIPTFUNC( SetShouldScaleImage, "" )\ + DEFINE_SCRIPTFUNC( SetRotation, "" )\ //-------------------------------------------------------------- //-------------------------------------------------------------- @@ -2435,10 +2546,10 @@ public: //-------------------------------------------------------------- //-------------------------------------------------------------- #if VGUI_TGA_IMAGE_PANEL -CLASS_HELPER_INTERFACE( TGAImagePanel, Panel ) +CLASS_HELPER_INTERFACE( TGAImage, Panel ) { public: - void SetTGAImage( const char *p ) + void SetImage( const char *p ) { __base()->SetTGAImage( p ); } @@ -2454,9 +2565,37 @@ public: } }; -#define DEFINE_VGUI_SCRIPTFUNC_TGAImagePanel()\ +#define DEFINE_VGUI_SCRIPTFUNC_TGAImage()\ DEFINE_VGUI_SCRIPTFUNC_Panel()\ - DEFINE_SCRIPTFUNC( SetTGAImage, "" )\ + DEFINE_SCRIPTFUNC( SetImage, "" )\ + DEFINE_SCRIPTFUNC( SetDrawColor, "" )\ + DEFINE_SCRIPTFUNC( SetShouldScaleImage, "" ) +#endif +//-------------------------------------------------------------- +//-------------------------------------------------------------- +#if 0 +CLASS_HELPER_INTERFACE( PNGImage, Panel ) +{ +public: + void SetImage( const char *p ) + { + __base()->SetPNGImage( p ); + } + + void SetDrawColor( int r, int g, int b, int a ) + { + __base()->SetDrawColor( r, g, b, a ); + } + + void SetShouldScaleImage( bool i ) + { + __base()->SetShouldScaleImage( i ); + } +}; + +#define DEFINE_VGUI_SCRIPTFUNC_PNGImage()\ + DEFINE_VGUI_SCRIPTFUNC_Panel()\ + DEFINE_SCRIPTFUNC( SetImage, "" )\ DEFINE_SCRIPTFUNC( SetDrawColor, "" )\ DEFINE_SCRIPTFUNC( SetShouldScaleImage, "" ) #endif @@ -2479,7 +2618,7 @@ static inline void SetHScript( HSCRIPT &var, HSCRIPT val ) } #define CheckCallback(s)\ - if ( FStrEq( cb, #s ) )\ + if ( !V_strcmp( cb, #s ) )\ {\ SetHScript( m_hfn##s, fn );\ return;\ @@ -2515,6 +2654,7 @@ private: HSCRIPT m_hfnOnKeyCodePressed; HSCRIPT m_hfnOnKeyCodeReleased; HSCRIPT m_hfnOnKeyCodeTyped; + #if SCRIPT_VGUI_SIGNAL_INTERFACE HSCRIPT m_hfnOnCommand; #endif @@ -2530,7 +2670,9 @@ public: m_hfnPerformLayout(NULL), m_hfnOnTick(NULL), m_hfnOnScreenSizeChanged(NULL), - +#if SCRIPT_VGUI_SIGNAL_INTERFACE + m_hfnOnCommand(NULL), +#endif m_hfnOnCursorEntered(NULL), m_hfnOnCursorExited(NULL), m_hfnOnCursorMoved(NULL), @@ -2543,13 +2685,9 @@ public: m_hfnOnKeyCodePressed(NULL), m_hfnOnKeyCodeReleased(NULL), m_hfnOnKeyCodeTyped(NULL) -#if SCRIPT_VGUI_SIGNAL_INTERFACE - , - m_hfnOnCommand(NULL) -#endif {} - void Shutdown() + void ScriptShutdown() { ivgui()->RemoveTickSignal( GetVPanel() ); @@ -2573,6 +2711,7 @@ public: SetHScript( m_hfnOnKeyCodePressed, NULL ); SetHScript( m_hfnOnKeyCodeReleased, NULL ); SetHScript( m_hfnOnKeyCodeTyped, NULL ); + #if SCRIPT_VGUI_SIGNAL_INTERFACE SetHScript( m_hfnOnCommand, NULL ); #endif @@ -2775,6 +2914,7 @@ public: CheckCallback( OnKeyCodePressed ); CheckCallback( OnKeyCodeReleased ); CheckCallback( OnKeyCodeTyped ); + #if SCRIPT_VGUI_SIGNAL_INTERFACE CheckCallback( OnCommand ); #endif @@ -2810,6 +2950,7 @@ private: HSCRIPT m_hfnOnKeyCodePressed; HSCRIPT m_hfnOnKeyCodeReleased; HSCRIPT m_hfnOnKeyCodeTyped; + #if SCRIPT_VGUI_SIGNAL_INTERFACE HSCRIPT m_hfnOnCommand; #endif @@ -2826,6 +2967,9 @@ public: m_hfnPerformLayout(NULL), m_hfnOnTick(NULL), m_hfnOnScreenSizeChanged(NULL), +#if SCRIPT_VGUI_SIGNAL_INTERFACE + m_hfnOnCommand(NULL), +#endif m_hfnOnCursorEntered(NULL), m_hfnOnCursorExited(NULL), @@ -2839,15 +2983,11 @@ public: m_hfnOnKeyCodePressed(NULL), m_hfnOnKeyCodeReleased(NULL), m_hfnOnKeyCodeTyped(NULL) -#if SCRIPT_VGUI_SIGNAL_INTERFACE - , - m_hfnOnCommand(NULL) -#endif { SetFadeEffectDisableOverride( true ); } - void Shutdown() + void ScriptShutdown() { ivgui()->RemoveTickSignal( GetVPanel() ); @@ -2866,6 +3006,7 @@ public: SetHScript( m_hfnOnKeyCodePressed, NULL ); SetHScript( m_hfnOnKeyCodeReleased, NULL ); SetHScript( m_hfnOnKeyCodeTyped, NULL ); + #if SCRIPT_VGUI_SIGNAL_INTERFACE SetHScript( m_hfnOnCommand, NULL ); #endif @@ -2898,7 +3039,18 @@ public: g_pScriptVM->ExecuteFunction( m_hfnPerformLayout, NULL, 0, NULL, NULL, true ); } } +#if 0 + void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + if ( m_hfnApplySchemeSettings ) + { + ScriptVariant_t arg; + g_pScriptVM->ExecuteFunction( m_hfnApplySchemeSettings, &arg, 1, NULL, NULL, true ); + } + } +#endif void OnTick() { g_pScriptVM->ExecuteFunction( m_hfnOnTick, NULL, 0, NULL, NULL, true ); @@ -3072,6 +3224,7 @@ public: CheckCallback( OnKeyCodePressed ); CheckCallback( OnKeyCodeReleased ); CheckCallback( OnKeyCodeTyped ); + #if SCRIPT_VGUI_SIGNAL_INTERFACE CheckCallback( OnCommand ); #endif @@ -3102,7 +3255,7 @@ public: m_hfnDoClick(NULL) {} - void Shutdown() + void ScriptShutdown() { SetHScript( m_hfnPaint, NULL ); SetHScript( m_hfnPaintBackground, NULL ); @@ -3173,7 +3326,7 @@ public: m_hfnTextChanged(NULL) {} - void Shutdown() + void ScriptShutdown() { SetHScript( m_hfnTextChanged, NULL ); } @@ -3216,9 +3369,12 @@ public: SetShouldDrawFriendIcon( false ); } - DEBUG_DESTRUCTOR( ~CScript_AvatarImage, CAvatarImagePanel ) + ~CScript_AvatarImage() + { + DebugDestructor( CAvatarImagePanel ); + } - void Shutdown() {} + void ScriptShutdown() {} }; #endif //-------------------------------------------------------------- @@ -3229,7 +3385,7 @@ class CTGAImagePanel : public Panel DECLARE_SCRIPTVGUI_CLASS_EX( CTGAImagePanel, Panel ); private: - int m_iTextureID; + int m_iTexture; int m_nWidth; int m_nHeight; Color m_ImageColor; @@ -3238,7 +3394,7 @@ private: public: CTGAImagePanel( Panel *parent, const char *name ) : BaseClass( parent, name ), - m_iTextureID(-1), + m_iTexture(-1), m_bScaleImage(0), m_ImageColor( 255, 255, 255, 255 ) { @@ -3249,21 +3405,21 @@ public: { DebugDestructor( CTGAImagePanel ); - if ( m_iTextureID != -1 ) + if ( m_iTexture != -1 ) { - surface()->DestroyTextureID( m_iTextureID ); + surface()->DestroyTextureID( m_iTexture ); } } - void Shutdown() {} + void ScriptShutdown() {} public: void Paint() { - if ( m_iTextureID != -1 ) + if ( m_iTexture != -1 ) { surface()->DrawSetColor( m_ImageColor ); - surface()->DrawSetTexture( m_iTextureID ); + surface()->DrawSetTexture( m_iTexture ); if ( m_bScaleImage ) { @@ -3288,19 +3444,21 @@ public: public: void SetTGAImage( const char *fileName ) { - if ( V_stricmp( V_GetFileExtension( fileName ), "tga" ) != 0 ) + const char *ext = V_GetFileExtension( fileName ); + + if ( ext && V_stricmp( ext, "tga" ) != 0 ) return; CUtlMemory< unsigned char > tga; if ( TGALoader::LoadRGBA8888( fileName, tga, m_nWidth, m_nHeight ) ) { - if ( m_iTextureID == -1 ) + if ( m_iTexture == -1 ) { - m_iTextureID = surface()->CreateNewTextureID( true ); + m_iTexture = surface()->CreateNewTextureID( true ); } - surface()->DrawSetTextureRGBA( m_iTextureID, tga.Base(), m_nWidth, m_nHeight, false, false ); + surface()->DrawSetTextureRGBA( m_iTexture, tga.Base(), m_nWidth, m_nHeight, false, false ); } else { @@ -3393,10 +3551,19 @@ END_SCRIPTDESC() //-------------------------------------------------------------- //-------------------------------------------------------------- #if VGUI_TGA_IMAGE_PANEL -BEGIN_VGUI_HELPER_EX( TGAImagePanel, CTGAImagePanel ) +BEGIN_VGUI_HELPER_EX( TGAImage, CTGAImagePanel ) END_VGUI_HELPER() -BEGIN_SCRIPTDESC_VGUI( TGAImagePanel ) +BEGIN_SCRIPTDESC_VGUI( TGAImage ) +END_SCRIPTDESC() +#endif +//-------------------------------------------------------------- +//-------------------------------------------------------------- +#if 0 +BEGIN_VGUI_HELPER_EX( PNGImage, CPNGImagePanel ) +END_VGUI_HELPER() + +BEGIN_SCRIPTDESC_VGUI( PNGImage ) END_SCRIPTDESC() #endif //-------------------------------------------------------------- @@ -3448,7 +3615,7 @@ HSCRIPT CScriptVGUI::CreatePanel( const char* panelClass, HSCRIPT parent, const } #define Check( _name )\ - if ( FStrEq( panelClass, #_name ) )\ + if ( !V_strcmp( panelClass, #_name ) )\ {\ CScript_##_name##_Helper *helper = AllocScriptPanel< CScript_##_name##_Helper >();\ helper->CreateFromScript< CScript_##_name##_Helper >( (HSCRIPT)parent, panelName, root );\ @@ -3467,7 +3634,7 @@ HSCRIPT CScriptVGUI::CreatePanel( const char* panelClass, HSCRIPT parent, const Check( AvatarImage ); #endif #if VGUI_TGA_IMAGE_PANEL - Check( TGAImagePanel ); + Check( TGAImage ); #endif g_pScriptVM->RaiseException("invalid vgui class"); @@ -3480,25 +3647,33 @@ void CScriptVGUI::LevelShutdownPostEntity() { DebugMsg( "LevelShutdownPostEntity()\n" ); - while ( g_ScriptPanels.Count() ) + if ( g_ScriptPanels.Count() ) { - Assert( g_ScriptPanels.Head() != g_ScriptPanels.InvalidIndex() ); + while ( g_ScriptPanels.Count() ) + { + Assert( g_ScriptPanels.Head() != g_ScriptPanels.InvalidIndex() ); - int head = g_ScriptPanels.Head(); - g_ScriptPanels[ head ]->Destroy( head ); + int head = g_ScriptPanels.Head(); + g_ScriptPanels[ head ]->Destroy( head ); + } + + g_ScriptPanels.Purge(); } - g_ScriptPanels.Purge(); - FOR_EACH_VEC( g_ScriptTextureIDs, i ) + if ( int i = g_ScriptTextureIDs.Count() ) { + while ( i-- ) + { #ifdef _DEBUG - char tex[MAX_PATH]; - surface()->DrawGetTextureFile( g_ScriptTextureIDs[i], tex, sizeof(tex)-1 ); - DebugMsg( "Destroy texture [%i]%s\n", g_ScriptTextureIDs[i], tex ); + char tex[MAX_PATH]; + surface()->DrawGetTextureFile( g_ScriptTextureIDs[i], tex, sizeof(tex)-1 ); + DebugMsg( "Destroy texture [%i]%s\n", g_ScriptTextureIDs[i], tex ); #endif - surface()->DestroyTextureID( g_ScriptTextureIDs[i] ); + surface()->DestroyTextureID( g_ScriptTextureIDs[i] ); + } + + g_ScriptTextureIDs.Purge(); } - g_ScriptTextureIDs.Purge(); // // Reset hud element visibility diff --git a/sp/src/game/client/mapbase/vscript_vgui.nut b/sp/src/game/client/mapbase/vscript_vgui.nut index a4452705..c4727eeb 100644 --- a/sp/src/game/client/mapbase/vscript_vgui.nut +++ b/sp/src/game/client/mapbase/vscript_vgui.nut @@ -1,6 +1,6 @@ static const char* g_Script_vgui_init = R"script( local DoCreateFont = ISurface.CreateFont; -ISurface.CreateFont <- function( name, props ) +function ISurface::CreateFont( name, props ) { if ( !("name" in props) || typeof props.name != "string" ) throw "invalid parameter 'name'"; @@ -86,12 +86,12 @@ ISurface.CreateFont <- function( name, props ) return DoCreateFont( name, props.name, props.tall, props.weight, blur, scanlines, flags, yres_min, yres_max, proportional ); } -local _Schemes = {} local _FontTall = {} +local _Schemes = {} local DoGetFont = ISurface.DoGetFont <- ISurface.GetFont; local DoGetFontTall = ISurface.GetFontTall; -ISurface.GetFont <- function( name, proportional, sch = "" ) +function ISurface::GetFont( name, proportional, sch = "" ) { if ( sch in _Schemes ) { @@ -151,33 +151,28 @@ ISurface.GetTextureID <- function( name ) } // Forward compatibility -IVGui.GetRootPanel <- function() { return 1000 } -//IVGui.GetGameUIRootPanel <- function() { return 1001 } -IVGui.GetClientDLLRootPanel <- function() { return 1002 } -//IVGui.GetHudViewportPanel <- function() { return 1010 } +IVGui.GetRootPanel <- function() { return 0x8888 } +//IVGui.GetGameUIRootPanel <- function() { return 0x8888+1 } +IVGui.GetClientDLLRootPanel <- function() { return 0x8888+2 } +IVGui.GetHudViewport <- function() { return 0x8888+10 } local CreatePanel = IVGui.CreatePanel; -IVGui.CreatePanel <- function( type, parent, name ) +function IVGui::CreatePanel( type, parent, name ) { if ( !parent ) throw "invalid parent"; - local root = 0; - + local root = -1; if ( typeof parent == "integer" ) { - switch ( parent ) + root = parent-0x8888; + switch ( root ) { - case 1000: - root = 0; + case 0: + case 2: + case 10: break; - - case 1002: - root = 2; - break; - - default: - throw "invalid parent"; + default: throw "invalid parent"; } parent = null; } @@ -390,5 +385,6 @@ if ( __Documentation.RegisterHelp != dummy ) __Documentation.RegisterHelp( "IVGui::CreatePanel", "handle IVGui::CreatePanel(string, handle, string)", "" ); __Documentation.RegisterHelp( "IVGui::GetRootPanel", "handle IVGui::GetRootPanel()", "" ); __Documentation.RegisterHelp( "IVGui::GetClientDLLRootPanel", "handle IVGui::GetClientDLLRootPanel()", "" ); + __Documentation.RegisterHelp( "IVGui::GetHudViewport", "handle IVGui::GetHudViewport()", "" ); } )script"; diff --git a/sp/src/game/client/message.cpp b/sp/src/game/client/message.cpp index dc41bb00..176b88ff 100644 --- a/sp/src/game/client/message.cpp +++ b/sp/src/game/client/message.cpp @@ -447,6 +447,8 @@ void CHudMessage::MessageScanStart( void ) break; } + // Font was just set in MessageDrawScan() +#ifndef MAPBASE m_parms.font = g_hFontTrebuchet24; if ( m_parms.vguiFontName != NULL && @@ -455,6 +457,7 @@ void CHudMessage::MessageScanStart( void ) SetFont( vgui::scheme()->GetDefaultScheme(), m_parms.vguiFontName ); } +#endif } //----------------------------------------------------------------------------- @@ -497,7 +500,30 @@ void CHudMessage::MessageDrawScan( client_textmessage_t *pMessage, float time ) m_parms.totalWidth = 0; m_parms.vguiFontName = pMessage->pVGuiSchemeFontName; +#ifdef MAPBASE + if ( m_parms.vguiFontName != NULL && + m_parms.vguiFontName[ 0 ] ) + { + SetFont( vgui::scheme()->GetDefaultScheme(), m_parms.vguiFontName ); + + #ifdef MAPBASE_VSCRIPT + if ( m_parms.font == vgui::INVALID_FONT ) + { + extern vgui::HFont GetScriptFont( const char *, bool ); + + vgui::HFont font = GetScriptFont( m_parms.vguiFontName, IsProportional() ); + textmessage->SetFont( font ); + m_parms.font = font; + } + #endif + } + else + { + m_parms.font = g_hFontTrebuchet24; + } +#else m_parms.font = g_hFontTrebuchet24; +#endif while ( *pText ) { diff --git a/sp/src/game/client/prediction.cpp b/sp/src/game/client/prediction.cpp index d7dfbff7..6d3a0439 100644 --- a/sp/src/game/client/prediction.cpp +++ b/sp/src/game/client/prediction.cpp @@ -855,7 +855,7 @@ void CPrediction::RunCommand( C_BasePlayer *player, CUserCmd *ucmd, IMoveHelper C_BaseCombatWeapon *weapon = dynamic_cast< C_BaseCombatWeapon * >( CBaseEntity::Instance( ucmd->weaponselect ) ); if ( weapon ) { - player->SelectItem( weapon->GetName(), ucmd->weaponsubtype ); + player->SelectItem( weapon->GetClassname(), ucmd->weaponsubtype ); } } diff --git a/sp/src/game/client/vscript_client.cpp b/sp/src/game/client/vscript_client.cpp index 6c8c5371..7bece925 100644 --- a/sp/src/game/client/vscript_client.cpp +++ b/sp/src/game/client/vscript_client.cpp @@ -431,7 +431,6 @@ const Vector& CScriptMaterialProxy::GetVarVector( int i ) if (m_MaterialVars[i]->GetType() != MATERIAL_VAR_TYPE_VECTOR) return vec3_origin; - // This is really bad. Too bad! return *(reinterpret_cast(m_MaterialVars[i]->GetVecValue())); } @@ -761,6 +760,8 @@ public: virtual void LevelShutdownPostEntity( void ) { #ifdef MAPBASE_VSCRIPT + g_ScriptEntityIterator.DisableEntityListening(); + g_ScriptNetMsg->LevelShutdownPreVM(); GetScriptHookManager().OnShutdown(); diff --git a/sp/src/game/server/ai_activity.cpp b/sp/src/game/server/ai_activity.cpp index da12eabe..e20bfc48 100644 --- a/sp/src/game/server/ai_activity.cpp +++ b/sp/src/game/server/ai_activity.cpp @@ -76,6 +76,23 @@ int CAI_BaseNPC::GetActivityID(const char* actName) return m_pActivitySR->GetStringID(actName); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Gets an activity ID or registers a new private one if it doesn't exist +//----------------------------------------------------------------------------- +int CAI_BaseNPC::GetOrRegisterActivity( const char *actName ) +{ + int actID = GetActivityID( actName ); + if (actID == ACT_INVALID) + { + actID = ActivityList_RegisterPrivateActivity( actName ); + AddActivityToSR( actName, actID ); + } + + return actID; +} +#endif + #define ADD_ACTIVITY_TO_SR(activityname) AddActivityToSR(#activityname,activityname) //----------------------------------------------------------------------------- diff --git a/sp/src/game/server/ai_basenpc.cpp b/sp/src/game/server/ai_basenpc.cpp index 299aa5c4..002f3f36 100644 --- a/sp/src/game/server/ai_basenpc.cpp +++ b/sp/src/game/server/ai_basenpc.cpp @@ -671,13 +671,27 @@ void CAI_BaseNPC::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bo { BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner ); +#ifdef MAPBASE + // Alyx's enemy ignited code from below can now be run on any NPC as long as + // it's our current enemy. + if ( GetEnemy() && GetEnemy()->IsNPC() ) + { + GetEnemy()->MyNPCPointer()->EnemyIgnited( this ); + } +#endif + #ifdef HL2_EPISODIC CBasePlayer *pPlayer = AI_GetSinglePlayer(); if ( pPlayer && pPlayer->IRelationType( this ) != D_LI ) { CNPC_Alyx *alyx = CNPC_Alyx::GetAlyx(); +#ifdef MAPBASE + // Alyx's code continues to run if Alyx was not this NPC's enemy. + if ( alyx && alyx != GetEnemy() ) +#else if ( alyx ) +#endif { alyx->EnemyIgnited( this ); } @@ -3026,6 +3040,10 @@ void CAI_BaseNPC::PopulatePoseParameters( void ) m_poseAim_Yaw = LookupPoseParameter( "aim_yaw" ); m_poseMove_Yaw = LookupPoseParameter( "move_yaw" ); +#ifdef MAPBASE + m_poseInteractionRelativeYaw = LookupPoseParameter( "interaction_relative_yaw" ); +#endif + BaseClass::PopulatePoseParameters(); } @@ -9526,6 +9544,12 @@ void CAI_BaseNPC::HandleAnimEvent( animevent_t *pEvent ) { m_hCine->FireScriptEvent( atoi( pEvent->options ) ); } +#ifdef MAPBASE + else if ( GetHintNode() ) + { + GetHintNode()->FireScriptEvent( atoi( pEvent->options ) ); + } +#endif else { // FIXME: look so see if it's playing a vcd and fire those instead @@ -12333,6 +12357,7 @@ BEGIN_ENT_SCRIPTDESC( CAI_BaseNPC, CBaseCombatCharacter, "The base class all NPC DEFINE_SCRIPTFUNC( IsCommandable, "Check if the NPC is commandable." ) DEFINE_SCRIPTFUNC( IsInPlayerSquad, "Check if the NPC is in the player's squad." ) + DEFINE_SCRIPTFUNC( IsMedic, "Returns true if this NPC is a medic." ) DEFINE_SCRIPTFUNC_NAMED( VScriptGetCine, "GetCine", "Get the NPC's currently running scripted sequence if it has one." ) DEFINE_SCRIPTFUNC( GetScriptState, "Get the NPC's current scripted sequence state." ) @@ -12443,7 +12468,11 @@ BEGIN_SIMPLE_DATADESC( ScriptedNPCInteraction_t ) DEFINE_FIELD( bValidOnCurrentEnemy, FIELD_BOOLEAN ), DEFINE_FIELD( flNextAttemptTime, FIELD_TIME ), #ifdef MAPBASE - DEFINE_FIELD( MiscCriteria, FIELD_STRING ),//DEFINE_UTLVECTOR( MiscCriteria, FIELD_EMBEDDED ), + DEFINE_EMBEDDED_ARRAY( sTheirPhases, SNPCINT_NUM_PHASES ), + DEFINE_FIELD( bHasSeparateSequenceNames, FIELD_BOOLEAN ), + DEFINE_FIELD( flMaxAngleDiff, FIELD_FLOAT ), + DEFINE_FIELD( iszRelatedInteractions, FIELD_STRING ), + DEFINE_FIELD( MiscCriteria, FIELD_STRING ), #endif END_DATADESC() @@ -13200,17 +13229,14 @@ void CAI_BaseNPC::InputSetEnemyFilter( inputdata_t &inputdata ) //----------------------------------------------------------------------------- void CAI_BaseNPC::InputSetHealthFraction( inputdata_t &inputdata ) { - // npc_helicopter uses SetHealth() instead of the regular NPC practice of TakeHealth() and TakeDamage(). - // It also also uses 50, 75, etc. and scales it by 0.01 for some reason. - // We're using the same model as InputSetHealth() and just letting npc_helicopter override it. No big deal. - // We're also adding support for its "whole number * 0.01" thing too. + // npc_helicopter uses an identically named input and scales down whole numbers instead of using fractions directly. + // This function is overridden by npc_helicopter for other reasons, but support for its differing behavior is also available through this input. float flFactor = inputdata.value.Float(); if ( flFactor > 1.0f ) { flFactor *= 0.01f; } - // Excuse the complication... float flNewHealth = (GetMaxHealth() * flFactor); int iNewHealth = (int)flNewHealth; if (flNewHealth < (GetMaxHealth() / 2)) @@ -13510,6 +13536,10 @@ bool CAI_BaseNPC::CineCleanup() } // Clear interaction partner, because we're not running a scripted sequence anymore +#ifdef MAPBASE + // We need the interaction partner for server ragdoll death cleanup, so don't clear if we're not alive + if (IsAlive()) +#endif m_hInteractionPartner = NULL; CleanupForcedInteraction(); } @@ -14850,32 +14880,66 @@ void CAI_BaseNPC::ParseScriptedNPCInteractions(void) else if (!Q_strncmp(szName, "entry_sequence", 14)) sInteraction.sPhases[SNPCINT_ENTRY].iszSequence = AllocPooledString(szValue); else if (!Q_strncmp(szName, "entry_activity", 14)) - sInteraction.sPhases[SNPCINT_ENTRY].iActivity = GetActivityID(szValue); + sInteraction.sPhases[SNPCINT_ENTRY].iActivity = GetOrRegisterActivity(szValue); else if (!Q_strncmp(szName, "sequence", 8)) sInteraction.sPhases[SNPCINT_SEQUENCE].iszSequence = AllocPooledString(szValue); else if (!Q_strncmp(szName, "activity", 8)) - sInteraction.sPhases[SNPCINT_SEQUENCE].iActivity = GetActivityID(szValue); + sInteraction.sPhases[SNPCINT_SEQUENCE].iActivity = GetOrRegisterActivity(szValue); else if (!Q_strncmp(szName, "exit_sequence", 13)) sInteraction.sPhases[SNPCINT_EXIT].iszSequence = AllocPooledString(szValue); else if (!Q_strncmp(szName, "exit_activity", 13)) - sInteraction.sPhases[SNPCINT_EXIT].iActivity = GetActivityID(szValue); + sInteraction.sPhases[SNPCINT_EXIT].iActivity = GetOrRegisterActivity(szValue); + + else if (!Q_strncmp(szName, "their_", 6)) + { + const char *szTheirName = szName + 6; + sInteraction.bHasSeparateSequenceNames = true; + + if (!Q_strncmp(szTheirName, "entry_sequence", 14)) + sInteraction.sTheirPhases[SNPCINT_ENTRY].iszSequence = AllocPooledString(szValue); + else if (!Q_strncmp(szTheirName, "entry_activity", 14)) + sInteraction.sTheirPhases[SNPCINT_ENTRY].iActivity = GetOrRegisterActivity(szValue); + + else if (!Q_strncmp(szTheirName, "sequence", 8)) + sInteraction.sTheirPhases[SNPCINT_SEQUENCE].iszSequence = AllocPooledString(szValue); + else if (!Q_strncmp(szTheirName, "activity", 8)) + sInteraction.sTheirPhases[SNPCINT_SEQUENCE].iActivity = GetOrRegisterActivity(szValue); + + else if (!Q_strncmp(szTheirName, "exit_sequence", 13)) + sInteraction.sTheirPhases[SNPCINT_EXIT].iszSequence = AllocPooledString(szValue); + else if (!Q_strncmp(szTheirName, "exit_activity", 13)) + sInteraction.sTheirPhases[SNPCINT_EXIT].iActivity = GetOrRegisterActivity(szValue); + + // Add anything else to our miscellaneous criteria + else + { + szCriteria = UTIL_VarArgs("%s,%s:%s", szCriteria, szName, szValue); + } + } else if (!Q_strncmp(szName, "delay", 5)) sInteraction.flDelay = atof(szValue); else if (!Q_strncmp(szName, "origin_max_delta", 16)) sInteraction.flDistSqr = atof(szValue); + else if (!Q_strncmp(szName, "angles_max_diff", 15)) + sInteraction.flMaxAngleDiff = atof(szValue); else if (!Q_strncmp(szName, "loop_in_action", 14) && !FStrEq(szValue, "0")) sInteraction.iFlags |= SCNPC_FLAG_LOOP_IN_ACTION; else if (!Q_strncmp(szName, "dont_teleport_at_end", 20)) { - if (!Q_stricmp(szValue, "me") || !Q_stricmp(szValue, "both")) + if (!Q_stricmp(szValue, "me")) sInteraction.iFlags |= SCNPC_FLAG_DONT_TELEPORT_AT_END_ME; - else if (!Q_stricmp(szValue, "them") || !Q_stricmp(szValue, "both")) + else if (!Q_stricmp(szValue, "them")) sInteraction.iFlags |= SCNPC_FLAG_DONT_TELEPORT_AT_END_THEM; + else if (!Q_stricmp( szValue, "both" )) + { + sInteraction.iFlags |= SCNPC_FLAG_DONT_TELEPORT_AT_END_ME; + sInteraction.iFlags |= SCNPC_FLAG_DONT_TELEPORT_AT_END_THEM; + } } else if (!Q_strncmp(szName, "needs_weapon", 12)) @@ -14902,6 +14966,11 @@ void CAI_BaseNPC::ParseScriptedNPCInteractions(void) sInteraction.iszTheirWeapon = AllocPooledString(szValue); } + else if (!Q_strncmp(szName, "related_interactions", 20)) + { + sInteraction.iszRelatedInteractions = AllocPooledString(szValue); + } + // Add anything else to our miscellaneous criteria else { @@ -15133,8 +15202,23 @@ void CAI_BaseNPC::AddScriptedNPCInteraction( ScriptedNPCInteraction_t *pInteract //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -const char *CAI_BaseNPC::GetScriptedNPCInteractionSequence( ScriptedNPCInteraction_t *pInteraction, int iPhase ) +const char *CAI_BaseNPC::GetScriptedNPCInteractionSequence( ScriptedNPCInteraction_t *pInteraction, int iPhase, bool bOtherNPC ) { +#ifdef MAPBASE + if (bOtherNPC && pInteraction->bHasSeparateSequenceNames) + { + // Check unique phases + if ( pInteraction->sTheirPhases[iPhase].iActivity != ACT_INVALID ) + { + int iSequence = SelectWeightedSequence( (Activity)pInteraction->sTheirPhases[iPhase].iActivity ); + return GetSequenceName( iSequence ); + } + + if ( pInteraction->sTheirPhases[iPhase].iszSequence != NULL_STRING ) + return STRING(pInteraction->sTheirPhases[iPhase].iszSequence); + } +#endif + if ( pInteraction->sPhases[iPhase].iActivity != ACT_INVALID ) { int iSequence = SelectWeightedSequence( (Activity)pInteraction->sPhases[iPhase].iActivity ); @@ -15228,6 +15312,37 @@ void CAI_BaseNPC::StartScriptedNPCInteraction( CAI_BaseNPC *pOtherNPC, ScriptedN // Setup next attempt pInteraction->flNextAttemptTime = gpGlobals->curtime + pInteraction->flDelay + RandomFloat(-2,2); +#ifdef MAPBASE + if (pInteraction->iszRelatedInteractions != NULL_STRING) + { + // Delay related interactions as well + char szRelatedInteractions[256]; + Q_strncpy( szRelatedInteractions, STRING( pInteraction->iszRelatedInteractions ), sizeof( szRelatedInteractions ) ); + + char *pszInteraction = strtok( szRelatedInteractions, "," ); + while (pszInteraction) + { + bool bWildCard = Matcher_ContainsWildcard( pszInteraction ); + + for ( int i = 0; i < m_ScriptedInteractions.Count(); i++ ) + { + ScriptedNPCInteraction_t *pOtherInteraction = &m_ScriptedInteractions[i]; + + if ( Matcher_NamesMatch( pszInteraction, STRING( pOtherInteraction->iszInteractionName ) ) && pOtherInteraction != pInteraction ) + { + if (pOtherInteraction->flNextAttemptTime < pInteraction->flNextAttemptTime) + pOtherInteraction->flNextAttemptTime = pInteraction->flNextAttemptTime; + + // Not looking for multiple + if (!bWildCard) + break; + } + } + + pszInteraction = strtok( NULL, "," ); + } + } +#endif // Spawn a scripted sequence for this NPC to play the interaction anim CAI_ScriptedSequence *pMySequence = (CAI_ScriptedSequence*)CreateEntityByName( "scripted_sequence" ); @@ -15259,6 +15374,15 @@ void CAI_BaseNPC::StartScriptedNPCInteraction( CAI_BaseNPC *pOtherNPC, ScriptedN CAI_ScriptedSequence *pTheirSequence = NULL; if ( pOtherNPC ) { +#ifdef MAPBASE + if (pInteraction->bHasSeparateSequenceNames) + { + pszEntrySequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_ENTRY, true ); + pszSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_SEQUENCE, true ); + pszExitSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_EXIT, true ); + } +#endif + pTheirSequence = (CAI_ScriptedSequence*)CreateEntityByName( "scripted_sequence" ); pTheirSequence->KeyValue( "m_iszEntry", pszEntrySequence ); pTheirSequence->KeyValue( "m_iszPlay", pszSequence ); @@ -15282,6 +15406,26 @@ void CAI_BaseNPC::StartScriptedNPCInteraction( CAI_BaseNPC *pOtherNPC, ScriptedN // Tell their sequence to keep their position relative to me pTheirSequence->SetupInteractionPosition( this, pInteraction->matDesiredLocalToWorld ); + +#ifdef MAPBASE + if ( !(pInteraction->iFlags & SCNPC_FLAG_TEST_OTHER_ANGLES) ) + { + // Set up interaction yaw pose if it exists + float flYaw = AngleDistance( angDesired.y, angOtherAngles.y ); + + int nInteractionPose = LookupPoseInteractionRelativeYaw(); + if (nInteractionPose > -1) + { + SetPoseParameter( nInteractionPose, flYaw ); + } + + nInteractionPose = pOtherNPC->LookupPoseInteractionRelativeYaw(); + if (nInteractionPose > -1) + { + pOtherNPC->SetPoseParameter( nInteractionPose, flYaw ); + } + } +#endif } // Spawn both sequences at once @@ -15370,7 +15514,7 @@ bool CAI_BaseNPC::CanRunAScriptedNPCInteraction( bool bForced ) return false; // Default AI prevents interactions while melee attacking, but not ranged attacking - if ( IsCurSchedule( SCHED_MELEE_ATTACK1 ) || IsCurSchedule( SCHED_MELEE_ATTACK2 ) ) + if ( ( IsCurSchedule( SCHED_MELEE_ATTACK1 ) || IsCurSchedule( SCHED_MELEE_ATTACK2 ) ) && !CanStartDynamicInteractionDuringMelee() ) return false; } @@ -15559,8 +15703,14 @@ void CAI_BaseNPC::CalculateValidEnemyInteractions( void ) if (bSame) continue; -#endif + // Resolve the activity or sequence, and make sure our enemy has it + const char *pszSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_SEQUENCE, true ); + if ( !pszSequence ) + continue; + if ( pNPC->LookupSequence( pszSequence ) == -1 ) + continue; +#else // Use sequence? or activity? if ( pInteraction->sPhases[SNPCINT_SEQUENCE].iActivity != ACT_INVALID ) { @@ -15576,53 +15726,6 @@ void CAI_BaseNPC::CalculateValidEnemyInteractions( void ) if ( pNPC->LookupSequence( STRING(pInteraction->sPhases[SNPCINT_SEQUENCE].iszSequence) ) == -1 ) continue; } - -#ifdef MAPBASE - if (pInteraction->MiscCriteria != NULL_STRING) - { - // Test against response system criteria - AI_CriteriaSet set; - ModifyOrAppendCriteria( set ); - CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); - if( pPlayer ) - pPlayer->ModifyOrAppendPlayerCriteria( set ); - ReAppendContextCriteria( set ); - - DevMsg("Testing %s misc criteria\n", STRING(pInteraction->MiscCriteria)); - - int index; - const char *criteriavalue; - char key[128]; - char value[128]; - 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) - { - criteriavalue = set.GetValue(index); - if (!Matcher_Match(value, criteriavalue)) - { - continue; - } - } - else - { - // Test with empty string in case our criteria is != or something - criteriavalue = ""; - if (!Matcher_Match(value, criteriavalue)) - { - continue; - } - } - } - } #endif pInteraction->bValidOnCurrentEnemy = true; @@ -15792,7 +15895,95 @@ bool CAI_BaseNPC::InteractionIsAllowed( CAI_BaseNPC *pOtherNPC, ScriptedNPCInter return true; // m_iDynamicInteractionsAllowed == TRS_FALSE case is already handled in CanRunAScriptedNPCInteraction(). - return !(pInteraction->iFlags & SCNPC_FLAG_MAPBASE_ADDITION && m_iDynamicInteractionsAllowed == TRS_NONE); + if (pInteraction->iFlags & SCNPC_FLAG_MAPBASE_ADDITION && m_iDynamicInteractionsAllowed == TRS_NONE) + return false; + + // Test misc. criteria here since some of it may not have been valid on initial calculation, but could be now + if (pInteraction->MiscCriteria != NULL_STRING) + { + // Test against response system criteria + AI_CriteriaSet set; + ModifyOrAppendCriteria( set ); + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + if (pPlayer) + pPlayer->ModifyOrAppendPlayerCriteria( set ); + + // Get criteria from target if we want it + if ( V_strstr( STRING( pInteraction->MiscCriteria ), "their_" ) ) + { + // Currently, in order to get everything which might be desired, we call the other NPC's ModifyOrAppendCriteria. + // We put it in a separate criteria set, then assign a prefix and append it to the main set, similar to how contexts are appended. + // This includes a few global criterions which we might not need, so we throw them out before they're merged. + // This isn't a very efficient solution, but there are no better options available without rewriting parts of the response criteria system. + AI_CriteriaSet theirSet; + pOtherNPC->ModifyOrAppendCriteria( theirSet ); + + set.EnsureCapacity( (theirSet.GetCount()-2) + set.GetCount() ); // We know we'll be throwing out 2 global criterions + + char sz[ 128 ]; + for ( int i = 0; i < theirSet.GetCount(); i++ ) + { + const char *name = theirSet.GetName( i ); + const char *value = theirSet.GetValue( i ); + + if (FStrEq( name, "map" ) || FStrEq( name, "episodic" ) || FStrEq( name, "is_console" ) + || FStrEq( name, "month" ) || FStrEq( name, "day" ) + || FStrEq( name, "is_console" ) || FStrEq( name, "is_pc" ) + || V_strnicmp( name, "world", 5 ) == 0) + { + // Global criterion, ignore + continue; + } + + Q_snprintf( sz, sizeof( sz ), "their_%s", name ); + + if (ai_debug_dyninteractions.GetInt() == 3) + Msg( "%i: %s -> %s:%s\n", i, name, sz, value ); + + set.AppendCriteria( sz, value ); + } + + // Append this afterwards because it has its own prefix system + pOtherNPC->AppendContextToCriteria( set, "their_" ); + } + + ReAppendContextCriteria( set ); + + int index; + const char *criteriavalue; + char key[128]; + char value[128]; + 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) + { + criteriavalue = set.GetValue( index ); + if (!Matcher_Match( value, criteriavalue )) + { + return false; + } + } + else + { + // Test with empty string in case our criteria is != or something + criteriavalue = ""; + if (!Matcher_Match( value, criteriavalue )) + { + return false; + } + } + } + } + + return true; } #endif @@ -15830,6 +16021,11 @@ bool CAI_BaseNPC::InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInte { Msg(" %s distsqr: %0.2f (%0.2f %0.2f %0.2f), desired: <%0.2f (%0.2f %0.2f %0.2f)\n", GetDebugName(), flDistSqr, pOtherNPC->GetAbsOrigin().x, pOtherNPC->GetAbsOrigin().y, pOtherNPC->GetAbsOrigin().z, pInteraction->flDistSqr, vecOrigin.x, vecOrigin.y, vecOrigin.z ); +#ifdef MAPBASE + Vector vecForward, vecRight; + GetVectors( &vecForward, &vecRight, NULL ); + NDebugOverlay::Circle( vecOrigin + Vector(0,0,2), vecForward, vecRight, FastSqrt(pInteraction->flDistSqr), 255, 0, 0, 255, true, 0.1f ); +#endif } } } @@ -15842,6 +16038,11 @@ bool CAI_BaseNPC::InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInte Msg(" %s is at: %0.2f %0.2f %0.2f\n", GetDebugName(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z ); Msg(" %s distsqr: %0.2f (%0.2f %0.2f %0.2f), desired: (%0.2f %0.2f %0.2f)\n", GetDebugName(), flDistSqr, pOtherNPC->GetAbsOrigin().x, pOtherNPC->GetAbsOrigin().y, pOtherNPC->GetAbsOrigin().z, vecOrigin.x, vecOrigin.y, vecOrigin.z ); +#ifdef MAPBASE + Vector vecForward, vecRight; + GetVectors( &vecForward, &vecRight, NULL ); + NDebugOverlay::Circle( vecOrigin + Vector( 0, 0, 2 ), vecForward, vecRight, FastSqrt( pInteraction->flDistSqr ), 255, 0, 0, 255, true, 0.1f ); +#endif if ( pOtherNPC ) { @@ -15858,14 +16059,28 @@ bool CAI_BaseNPC::InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInte for ( int ang = 0; ang < 3; ang++ ) { float flAngDiff = AngleDiff( angEnemyAngles[ang], angAngles[ang] ); +#ifdef MAPBASE + if ( fabs(flAngDiff) > pInteraction->flMaxAngleDiff ) +#else if ( fabs(flAngDiff) > DSS_MAX_ANGLE_DIFF ) +#endif { bMatches = false; break; } } if ( !bMatches ) + { +#ifdef MAPBASE + if ( bDebug ) + { + Msg(" %s angle not matched: (%0.2f %0.2f %0.2f), desired (%0.2f, %0.2f, %0.2f)\n", GetDebugName(), + anglemod(angEnemyAngles.x), anglemod(angEnemyAngles.y), anglemod(angEnemyAngles.z), anglemod(angAngles.x), anglemod(angAngles.y), anglemod(angAngles.z) ); + Msg(" diff: (%0.2f, %0.2f, %0.2f)\n", AngleDiff( angEnemyAngles.x, angAngles.x ), AngleDiff( angEnemyAngles.y, angAngles.y ), AngleDiff( angEnemyAngles.z, angAngles.z ) ); + } +#endif return false; + } if ( bDebug ) { @@ -15873,6 +16088,13 @@ bool CAI_BaseNPC::InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInte anglemod(angEnemyAngles.x), anglemod(angEnemyAngles.y), anglemod(angEnemyAngles.z), anglemod(angAngles.x), anglemod(angAngles.y), anglemod(angAngles.z) ); } } +#ifdef MAPBASE + else + { + // If we're not using angles, then use the NPC's current angles + angAngles = pOtherNPC->GetAbsAngles(); + } +#endif // TODO: Velocity check, if we're supposed to if ( pInteraction->iFlags & SCNPC_FLAG_TEST_OTHER_VELOCITY ) @@ -15952,6 +16174,7 @@ bool CAI_BaseNPC::InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInte if ( bDebug ) { NDebugOverlay::Box( vecPos, GetHullMins(), GetHullMaxs(), 255,0,0, 100, 1.0 ); + NDebugOverlay::HorzArrow( GetAbsOrigin(), vecPos, 16.0f, 255, 0, 0, 255, true, 1.0f ); } return false; } @@ -15959,7 +16182,39 @@ bool CAI_BaseNPC::InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInte { //NDebugOverlay::Box( vecPos, GetHullMins(), GetHullMaxs(), 0,255,0, 100, 1.0 ); - NDebugOverlay::Axis( vecPos, angAngles, 20, true, 10.0 ); + NDebugOverlay::Axis( vecPos, angAngles, 20, true, 1.0 ); + } + } + else + { + // Instead, make sure we fit into where the sequence movement ends at + const char *pszSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_SEQUENCE ); + int nSeq = LookupSequence( pszSequence ); + if ( pszSequence && nSeq != -1 ) + { + Vector vecDeltaPos; + QAngle angDeltaAngles; + GetSequenceMovement( nSeq, 0.0f, 1.0f, vecDeltaPos, angDeltaAngles ); + if (!vecDeltaPos.IsZero()) + { + QAngle angInteraction = GetAbsAngles(); + angInteraction[YAW] = m_flInteractionYaw; + + Vector vecPos; + VectorRotate( vecDeltaPos, angInteraction, vecPos ); + vecPos += GetAbsOrigin(); + + AI_TraceHull( vecPos, vecPos, GetHullMins(), GetHullMaxs(), MASK_SOLID, &traceFilter, &tr); + if ( tr.fraction != 1.0 ) + { + if ( bDebug ) + { + NDebugOverlay::Box( vecPos, GetHullMins(), GetHullMaxs(), 255,0,0, 100, 1.0 ); + NDebugOverlay::HorzArrow( GetAbsOrigin(), vecPos, 16.0f, 255, 0, 0, 255, true, 1.0f ); + } + return false; + } + } } } #endif @@ -15983,6 +16238,25 @@ bool CAI_BaseNPC::HasInteractionCantDie( void ) return ( m_bCannotDieDuringInteraction && IsRunningDynamicInteraction() ); } +//----------------------------------------------------------------------------- +// Purpose: Return true if this NPC has valid interactions on the current enemy. +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::HasValidInteractionsOnCurrentEnemy( void ) +{ + if ( !GetEnemy() || !GetEnemy()->IsNPC() ) + return false; + + for ( int i = 0; i < m_ScriptedInteractions.Count(); i++ ) + { + ScriptedNPCInteraction_t *pInteraction = &m_ScriptedInteractions[i]; + + if ( pInteraction->bValidOnCurrentEnemy ) + return true; + } + + return false; +} + //----------------------------------------------------------------------------- // Purpose: // Input : &inputdata - @@ -16216,6 +16490,21 @@ void CAI_BaseNPC::ModifyOrAppendEnemyCriteria( AI_CriteriaSet& set, CBaseEntity set.AppendCriteria( "enemyclass", g_pGameRules->AIClassText( pEnemy->Classify() ) ); // UTIL_VarArgs("%i", pEnemy->Classify()) set.AppendCriteria( "distancetoenemy", UTIL_VarArgs( "%f", EnemyDistance(pEnemy) ) ); set.AppendCriteria( "timesincecombat", "-1" ); + + CAI_BaseNPC *pNPC = pEnemy->MyNPCPointer(); + if (pNPC) + { + set.AppendCriteria("enemy_is_npc", "1"); + + set.AppendCriteria( "enemy_activity", CAI_BaseNPC::GetActivityName( pNPC->GetActivity() ) ); + set.AppendCriteria( "enemy_weapon", pNPC->GetActiveWeapon() ? pNPC->GetActiveWeapon()->GetClassname() : "0" ); + } + else + { + set.AppendCriteria("enemy_is_npc", "0"); + } + + pEnemy->AppendContextToCriteria( set, "enemy_" ); } else { diff --git a/sp/src/game/server/ai_basenpc.h b/sp/src/game/server/ai_basenpc.h index 878a6f81..dbc229e2 100644 --- a/sp/src/game/server/ai_basenpc.h +++ b/sp/src/game/server/ai_basenpc.h @@ -425,6 +425,9 @@ struct ScriptedNPCInteraction_t iszTheirWeapon = NULL_STRING; #ifdef MAPBASE vecRelativeEndPos = vec3_origin; + bHasSeparateSequenceNames = false; + flMaxAngleDiff = DSS_MAX_ANGLE_DIFF; + iszRelatedInteractions = NULL_STRING; MiscCriteria = NULL_STRING; #endif @@ -432,6 +435,10 @@ struct ScriptedNPCInteraction_t { sPhases[i].iszSequence = NULL_STRING; sPhases[i].iActivity = ACT_INVALID; +#ifdef MAPBASE + sTheirPhases[i].iszSequence = NULL_STRING; + sTheirPhases[i].iActivity = ACT_INVALID; +#endif } } @@ -459,10 +466,14 @@ struct ScriptedNPCInteraction_t float flNextAttemptTime; #ifdef MAPBASE - // Unrecognized keyvalues are tested against response criteria later. - // This was originally a CUtlVector that carries response contexts, but I couldn't get it working due to some CUtlVector-struct shenanigans. - // It works when we use a single string_t that's split and read each time the code runs, but feel free to improve on this. - string_t MiscCriteria; // CUtlVector + ScriptedNPCInteraction_Phases_t sTheirPhases[SNPCINT_NUM_PHASES]; // The animations played by the target NPC, if they are different + bool bHasSeparateSequenceNames; + + float flMaxAngleDiff; + string_t iszRelatedInteractions; // These interactions will be delayed as well when this interaction is used. + + // Unrecognized keyvalues which are tested against response criteria later. + string_t MiscCriteria; #endif DECLARE_SIMPLE_DATADESC(); @@ -838,6 +849,9 @@ protected: // pose parameters int m_poseAim_Pitch; int m_poseAim_Yaw; int m_poseMove_Yaw; +#ifdef MAPBASE + int m_poseInteractionRelativeYaw; +#endif virtual void PopulatePoseParameters( void ); public: @@ -845,6 +859,10 @@ public: // Return the stored pose parameter for "move_yaw" inline int LookupPoseMoveYaw() { return m_poseMove_Yaw; } + +#ifdef MAPBASE + inline int LookupPoseInteractionRelativeYaw() { return m_poseInteractionRelativeYaw; } +#endif //----------------------------------------------------- @@ -1304,10 +1322,14 @@ private: public: float GetInteractionYaw( void ) const { return m_flInteractionYaw; } + bool IsRunningDynamicInteraction( void ) { return (m_iInteractionState != NPCINT_NOT_RUNNING && (m_hCine != NULL)); } + bool IsActiveDynamicInteraction( void ) { return (m_iInteractionState == NPCINT_RUNNING_ACTIVE && (m_hCine != NULL)); } + CAI_BaseNPC *GetInteractionPartner( void ); + protected: void ParseScriptedNPCInteractions( void ); void AddScriptedNPCInteraction( ScriptedNPCInteraction_t *pInteraction ); - const char *GetScriptedNPCInteractionSequence( ScriptedNPCInteraction_t *pInteraction, int iPhase ); + const char *GetScriptedNPCInteractionSequence( ScriptedNPCInteraction_t *pInteraction, int iPhase, bool bOtherNPC = false ); void StartRunningInteraction( CAI_BaseNPC *pOtherNPC, bool bActive ); void StartScriptedNPCInteraction( CAI_BaseNPC *pOtherNPC, ScriptedNPCInteraction_t *pInteraction, Vector vecOtherOrigin, QAngle angOtherAngles ); void CheckForScriptedNPCInteractions( void ); @@ -1320,17 +1342,16 @@ protected: #endif bool InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInteraction_t *pInteraction, Vector &vecOrigin, QAngle &angAngles ); virtual bool CanRunAScriptedNPCInteraction( bool bForced = false ); - bool IsRunningDynamicInteraction( void ) { return (m_iInteractionState != NPCINT_NOT_RUNNING && (m_hCine != NULL)); } - bool IsActiveDynamicInteraction( void ) { return (m_iInteractionState == NPCINT_RUNNING_ACTIVE && (m_hCine != NULL)); } ScriptedNPCInteraction_t *GetRunningDynamicInteraction( void ) { return &(m_ScriptedInteractions[m_iInteractionPlaying]); } void SetInteractionCantDie( bool bCantDie ) { m_bCannotDieDuringInteraction = bCantDie; } bool HasInteractionCantDie( void ); + bool HasValidInteractionsOnCurrentEnemy( void ); + virtual bool CanStartDynamicInteractionDuringMelee() { return false; } void InputForceInteractionWithNPC( inputdata_t &inputdata ); void StartForcedInteraction( CAI_BaseNPC *pNPC, int iInteraction ); void CleanupForcedInteraction( void ); void CalculateForcedInteractionPosition( void ); - CAI_BaseNPC *GetInteractionPartner( void ); private: // Forced interactions @@ -1974,6 +1995,9 @@ public: //--------------------------------- virtual void Ignite( float flFlameLifetime, bool bNPCOnly = true, float flSize = 0.0f, bool bCalledByLevelDesigner = false ); +#ifdef MAPBASE + virtual void EnemyIgnited( CAI_BaseNPC *pVictim ) {} +#endif virtual bool PassesDamageFilter( const CTakeDamageInfo &info ); //--------------------------------- @@ -2228,6 +2252,9 @@ public: static const char* GetActivityName (int actID); static void AddActivityToSR(const char *actName, int conID); +#ifdef MAPBASE + static int GetOrRegisterActivity( const char *actName ); +#endif static void AddEventToSR(const char *eventName, int conID); static const char* GetEventName (int actID); diff --git a/sp/src/game/server/ai_basenpc_schedule.cpp b/sp/src/game/server/ai_basenpc_schedule.cpp index 61fefb3f..5f57b51d 100644 --- a/sp/src/game/server/ai_basenpc_schedule.cpp +++ b/sp/src/game/server/ai_basenpc_schedule.cpp @@ -1610,6 +1610,12 @@ void CAI_BaseNPC::StartTask( const Task_t *pTask ) // as this should only run with the NPC "receiving" the interaction ScriptedNPCInteraction_t *pInteraction = m_hForcedInteractionPartner->GetRunningDynamicInteraction(); + if ( !(pInteraction->iFlags & SCNPC_FLAG_TEST_OTHER_ANGLES) ) + { + TaskComplete(); + return; + } + // Get our target's origin Vector vecTarget = m_hForcedInteractionPartner->GetAbsOrigin(); @@ -1617,7 +1623,7 @@ void CAI_BaseNPC::StartTask( const Task_t *pTask ) float angInteractionAngle = pInteraction->angRelativeAngles.y; angInteractionAngle += 180.0f; - GetMotor()->SetIdealYaw( CalcIdealYaw( vecTarget ) + angInteractionAngle ); + GetMotor()->SetIdealYaw( AngleNormalize( CalcIdealYaw( vecTarget ) + angInteractionAngle ) ); if (FacingIdeal()) TaskComplete(); @@ -4113,6 +4119,15 @@ void CAI_BaseNPC::RunTask( const Task_t *pTask ) m_hCine->SynchronizeSequence( this ); } } + +#ifdef MAPBASE + if ( IsRunningDynamicInteraction() && m_poseInteractionRelativeYaw > -1 ) + { + // Animations in progress require pose parameters to be set every frame, so keep setting the interaction relative yaw pose. + // The random value is added to help it pass server transmit checks. + SetPoseParameter( m_poseInteractionRelativeYaw, GetPoseParameter( m_poseInteractionRelativeYaw ) + RandomFloat( -0.1f, 0.1f ) ); + } +#endif break; } diff --git a/sp/src/game/server/ai_concommands.cpp b/sp/src/game/server/ai_concommands.cpp index e004dca1..6c4370d9 100644 --- a/sp/src/game/server/ai_concommands.cpp +++ b/sp/src/game/server/ai_concommands.cpp @@ -477,6 +477,20 @@ public: AI_TraceHull( baseNPC->GetAbsOrigin(), vUpBit, baseNPC->GetHullMins(), baseNPC->GetHullMaxs(), MASK_NPCSOLID, baseNPC, COLLISION_GROUP_NONE, &tr ); + + // NEW: For vphysics/flying entities, do a second attempt which teleports based on bounding box + if ( (baseNPC->GetMoveType() == MOVETYPE_VPHYSICS || baseNPC->CapabilitiesGet() & bits_CAP_MOVE_FLY) && (tr.startsolid || tr.fraction < 1.0) ) + { + vUpBit.z += baseNPC->BoundingRadius(); + baseNPC->Teleport( &vUpBit, NULL, NULL ); + UTIL_DropToFloor( baseNPC, MASK_NPCSOLID ); + + Vector vUpBit2 = vUpBit; + vUpBit2.z += 1; + AI_TraceHull( vUpBit, vUpBit2, baseNPC->CollisionProp()->OBBMins(), baseNPC->CollisionProp()->OBBMaxs(), + MASK_NPCSOLID, baseNPC, COLLISION_GROUP_NONE, &tr ); + } + if ( tr.startsolid || (tr.fraction < 1.0) ) { baseNPC->SUB_Remove(); diff --git a/sp/src/game/server/ai_expresserfollowup.cpp b/sp/src/game/server/ai_expresserfollowup.cpp index 65575709..38a83a3d 100644 --- a/sp/src/game/server/ai_expresserfollowup.cpp +++ b/sp/src/game/server/ai_expresserfollowup.cpp @@ -88,6 +88,15 @@ static void DispatchComeback( CAI_ExpresserWithFollowup *pExpress, CBaseEntity * // add in any provided contexts from the parameters onto the ones stored in the followup criteria.Merge( followup.followup_contexts ); +#ifdef MAPBASE + if (CAI_ExpresserSink *pSink = dynamic_cast(pRespondent)) + { + criteria.AppendCriteria( "dist_from_issuer", UTIL_VarArgs( "%f", (pRespondent->GetAbsOrigin() - pSpeaker->GetAbsOrigin()).Length() ) ); + g_ResponseQueueManager.GetQueue()->AppendFollowupCriteria( followup.followup_concept, criteria, pSink->GetSinkExpresser(), pSink, pRespondent, pSpeaker, kDRT_SPECIFIC ); + + pSink->Speak( followup.followup_concept, &criteria ); + } +#else // 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)) @@ -99,6 +108,7 @@ static void DispatchComeback( CAI_ExpresserWithFollowup *pExpress, CBaseEntity * { pActor->Speak( followup.followup_concept, &criteria ); } +#endif } #if 0 diff --git a/sp/src/game/server/ai_hint.cpp b/sp/src/game/server/ai_hint.cpp index 2e8e90af..10d9d728 100644 --- a/sp/src/game/server/ai_hint.cpp +++ b/sp/src/game/server/ai_hint.cpp @@ -902,6 +902,17 @@ BEGIN_DATADESC( CAI_Hint ) DEFINE_OUTPUT( m_OnNPCStartedUsing, "OnNPCStartedUsing" ), DEFINE_OUTPUT( m_OnNPCStoppedUsing, "OnNPCStoppedUsing" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnScriptEvent[0], "OnScriptEvent01" ), + DEFINE_OUTPUT( m_OnScriptEvent[1], "OnScriptEvent02" ), + DEFINE_OUTPUT( m_OnScriptEvent[2], "OnScriptEvent03" ), + DEFINE_OUTPUT( m_OnScriptEvent[3], "OnScriptEvent04" ), + DEFINE_OUTPUT( m_OnScriptEvent[4], "OnScriptEvent05" ), + DEFINE_OUTPUT( m_OnScriptEvent[5], "OnScriptEvent06" ), + DEFINE_OUTPUT( m_OnScriptEvent[6], "OnScriptEvent07" ), + DEFINE_OUTPUT( m_OnScriptEvent[7], "OnScriptEvent08" ), +#endif + END_DATADESC( ); #ifdef MAPBASE_VSCRIPT @@ -1325,7 +1336,7 @@ bool CAI_Hint::HintMatchesCriteria( CAI_BaseNPC *pNPC, const CHintCriteria &hint if ( distance > nRadius * nRadius ) { - REPORTFAILURE( "NPC is not within the node's radius." ); + REPORTFAILURE( "Not within the node's radius." ); return false; } } @@ -1705,6 +1716,19 @@ void CAI_Hint::NPCStoppedUsing( CAI_BaseNPC *pNPC ) m_OnNPCStoppedUsing.Set( pNPC, pNPC, this ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_Hint::FireScriptEvent( int nEvent ) +{ + if ( ( nEvent >= 1 ) && ( nEvent <= 8 ) ) + { + m_OnScriptEvent[nEvent - 1].FireOutput( m_hHintOwner, this ); + } +} +#endif + CON_COMMAND(ai_dump_hints, "") { @@ -1794,6 +1818,11 @@ hinttypedescs_t g_pszHintDescriptions[] = { HINT_HL1_WORLD_ALIEN_BLOOD, "HL1: World: Alien Blood" }, { HINT_CSTRIKE_HOSTAGE_ESCAPE, "CS Port: Hostage Escape" }, + +#ifdef MAPBASE + { HINT_TACTICAL_COVER_CUSTOM, "Mapbase: Custom Cover" }, + { HINT_TACTICAL_GRENADE_THROW, "Mapbase: Grenade Throw Hint" }, +#endif }; //----------------------------------------------------------------------------- diff --git a/sp/src/game/server/ai_hint.h b/sp/src/game/server/ai_hint.h index 570f85ef..b18be429 100644 --- a/sp/src/game/server/ai_hint.h +++ b/sp/src/game/server/ai_hint.h @@ -118,6 +118,7 @@ enum Hint_e // (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) + HINT_TACTICAL_GRENADE_THROW, // Pre-determined position for NPCs to throw grenades at when their target in combat is near it #endif }; const char *GetHintTypeDescription( Hint_e iHintType ); @@ -322,6 +323,9 @@ public: void FixupTargetNode(); void NPCStartedUsing( CAI_BaseNPC *pNPC ); void NPCStoppedUsing( CAI_BaseNPC *pNPC ); +#ifdef MAPBASE + void FireScriptEvent( int nEvent ); +#endif HintIgnoreFacing_t GetIgnoreFacing() const { return m_NodeData.fIgnoreFacing; } @@ -384,6 +388,10 @@ private: float m_nodeFOV; Vector m_vecForward; +#ifdef MAPBASE + COutputEvent m_OnScriptEvent[8]; +#endif + // The next hint in list of all hints friend class CAI_HintManager; diff --git a/sp/src/game/server/ai_playerally.cpp b/sp/src/game/server/ai_playerally.cpp index f7594feb..af969868 100644 --- a/sp/src/game/server/ai_playerally.cpp +++ b/sp/src/game/server/ai_playerally.cpp @@ -128,6 +128,12 @@ ConceptInfo_t g_ConceptInfos[] = // Passenger behaviour { TLK_PASSENGER_NEW_RADAR_CONTACT, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, }, + +#ifdef MAPBASE + { TLK_TAKING_FIRE, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, }, + { TLK_NEW_ENEMY, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, }, + { TLK_COMBAT_IDLE, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, }, +#endif }; //----------------------------------------------------------------------------- @@ -1259,6 +1265,38 @@ void CAI_PlayerAlly::OnKilledNPC( CBaseCombatCharacter *pKilled ) } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_PlayerAlly::OnEnemyRangeAttackedMe( CBaseEntity *pEnemy, const Vector &vecDir, const Vector &vecEnd ) +{ + BaseClass::OnEnemyRangeAttackedMe( pEnemy, vecDir, vecEnd ); + + if ( IRelationType( pEnemy ) <= D_FR ) + { + AI_CriteriaSet modifiers; + ModifyOrAppendEnemyCriteria( modifiers, pEnemy ); + + Vector vecEntDir = (pEnemy->EyePosition() - EyePosition()); + float flDot = DotProduct( vecEntDir.Normalized(), vecDir ); + modifiers.AppendCriteria( "shot_dot", CNumStr( flDot ) ); + + if (GetLastDamageTime() == gpGlobals->curtime) + modifiers.AppendCriteria( "missed", "0" ); + else + modifiers.AppendCriteria( "missed", "1" ); + + // Check if they're out of ammo + if ( pEnemy->IsCombatCharacter() && pEnemy->MyCombatCharacterPointer()->GetActiveWeapon() && pEnemy->MyCombatCharacterPointer()->GetActiveWeapon()->Clip1() <= 0 ) + modifiers.AppendCriteria( "last_attack", "1" ); + else + modifiers.AppendCriteria( "last_attack", "0" ); + + SpeakIfAllowed( TLK_TAKING_FIRE, modifiers ); + } +} +#endif + //----------------------------------------------------------------------------- void CAI_PlayerAlly::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) { @@ -1762,6 +1800,54 @@ bool CAI_PlayerAlly::IsAllowedToSpeak( AIConcept_t concept, bool bRespondingToPl return true; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Specifically for player allies handling followup responses. +// Better-accounts for unknown concepts so that users are free in what they use. +//----------------------------------------------------------------------------- +bool CAI_PlayerAlly::IsAllowedToSpeakFollowup( AIConcept_t concept, CBaseEntity *pIssuer, bool bSpecific ) +{ + CAI_AllySpeechManager * pSpeechManager = GetAllySpeechManager(); + ConceptInfo_t * pInfo = pSpeechManager->GetConceptInfo( concept ); + ConceptCategory_t category = SPEECH_PRIORITY; // Must be SPEECH_PRIORITY to get around semaphore + + if ( !IsOkToSpeak( category, true ) ) + return false; + + // If this followup is specifically targeted towards us, speak if we're not already speaking + // If it's meant to be spoken by anyone, respect speech delay and semaphore + if ( bSpecific ) + { + if ( !GetExpresser()->CanSpeakAfterMyself() ) + return false; + } + else + { + if ( !GetExpresser()->CanSpeak() ) + return false; + + CAI_TimedSemaphore *pSemaphore = GetExpresser()->GetMySpeechSemaphore( this ); + if ( pSemaphore && !pSemaphore->IsAvailable( this ) ) + { + // Only if the semaphore holder isn't the one dispatching the followup + if ( pSemaphore->GetOwner() != pIssuer ) + return false; + } + } + + if ( !pSpeechManager->ConceptDelayExpired( concept ) ) + return false; + + if ( ( pInfo && pInfo->flags & AICF_SPEAK_ONCE ) && GetExpresser()->SpokeConcept( concept ) ) + return false; + + if ( !GetExpresser()->CanSpeakConcept( concept ) ) + return false; + + return true; +} +#endif + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CAI_PlayerAlly::SpeakIfAllowed( AIConcept_t concept, const char *modifiers, bool bRespondingToPlayer, char *pszOutResponseChosen, size_t bufsize ) diff --git a/sp/src/game/server/ai_playerally.h b/sp/src/game/server/ai_playerally.h index 93448ec7..47ddf916 100644 --- a/sp/src/game/server/ai_playerally.h +++ b/sp/src/game/server/ai_playerally.h @@ -132,6 +132,13 @@ #define TLK_TGCATCHUP "TLK_TGCATCHUP" #define TLK_TGENDTOUR "TLK_TGENDTOUR" +#ifdef MAPBASE +// Additional concepts for companions in mods +#define TLK_TAKING_FIRE "TLK_TAKING_FIRE" // Someone fired at me (regardless of whether I was hit) +#define TLK_NEW_ENEMY "TLK_NEW_ENEMY" // A new enemy appeared while combat was already in progress +#define TLK_COMBAT_IDLE "TLK_COMBAT_IDLE" // Similar to TLK_ATTACKING, but specifically for when *not* currently attacking (e.g. when in cover or reloading) +#endif + //----------------------------------------------------------------------------- #define TALKRANGE_MIN 500.0 // don't talk to anyone farther away than this @@ -315,6 +322,10 @@ public: //--------------------------------- void OnKilledNPC( CBaseCombatCharacter *pKilled ); +#ifdef MAPBASE + void OnEnemyRangeAttackedMe( CBaseEntity *pEnemy, const Vector &vecDir, const Vector &vecEnd ); +#endif + //--------------------------------- // Damage handling //--------------------------------- @@ -392,6 +403,9 @@ public: bool ShouldSpeakRandom( AIConcept_t concept, int iChance ); bool IsAllowedToSpeak( AIConcept_t concept, bool bRespondingToPlayer = false ); +#ifdef MAPBASE + bool IsAllowedToSpeakFollowup( AIConcept_t concept, CBaseEntity *pIssuer, bool bSpecific ); +#endif virtual bool SpeakIfAllowed( AIConcept_t concept, const char *modifiers = NULL, bool bRespondingToPlayer = false, char *pszOutResponseChosen = NULL, size_t bufsize = 0 ); #ifdef MAPBASE virtual bool SpeakIfAllowed( AIConcept_t concept, AI_CriteriaSet& modifiers, bool bRespondingToPlayer = false, char *pszOutResponseChosen = NULL, size_t bufsize = 0 ); diff --git a/sp/src/game/server/ai_relationship.cpp b/sp/src/game/server/ai_relationship.cpp index a700552a..e5f12d62 100644 --- a/sp/src/game/server/ai_relationship.cpp +++ b/sp/src/game/server/ai_relationship.cpp @@ -532,6 +532,7 @@ public: // Must override CAI_Relationship void Spawn() { m_bIsActive = false; } + void Activate(); bool KeyValue( const char *szKeyName, const char *szValue ); @@ -557,6 +558,19 @@ BEGIN_DATADESC( CAI_ClassRelationship ) END_DATADESC() +//--------------------------------------------------------- +//--------------------------------------------------------- +void CAI_ClassRelationship::Activate() +{ + BaseClass::Activate(); + + // Must re-apply every time a save is loaded + if ( m_bIsActive ) + { + ApplyRelationship(); + } +} + //----------------------------------------------------------------------------- // Purpose: Caches entity key values until spawn is called. // Input : szKeyName - @@ -615,6 +629,9 @@ void CAI_ClassRelationship::ChangeRelationships( int disposition, int iReverting return; } + if ( !CBaseCombatCharacter::DefaultRelationshipsLoaded() ) + return; + if ( m_iPreviousDisposition == -1 && iReverting == NOT_REVERTING ) { // Set previous disposition. diff --git a/sp/src/game/server/ai_speech_new.h b/sp/src/game/server/ai_speech_new.h index c61f6f03..8290d471 100644 --- a/sp/src/game/server/ai_speech_new.h +++ b/sp/src/game/server/ai_speech_new.h @@ -126,6 +126,13 @@ public: virtual void OnSpokeConcept( AIConcept_t concept, AI_Response *response ) {}; virtual void OnStartSpeaking() {} virtual bool UseSemaphore() { return true; } + +#ifdef MAPBASE + // Works around issues with CAI_ExpresserHost<> class hierarchy + virtual CAI_Expresser *GetSinkExpresser() { return NULL; } + virtual bool IsAllowedToSpeakFollowup( AIConcept_t concept, CBaseEntity *pIssuer, bool bSpecific ) { return true; } + virtual bool Speak( AIConcept_t concept, AI_CriteriaSet *pCriteria, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL ) { return false; } +#endif }; struct ConceptHistory_t @@ -244,9 +251,15 @@ public: static bool RunScriptResponse( CBaseEntity *pTarget, const char *response, AI_CriteriaSet *criteria, bool file ); #endif +#ifdef MAPBASE +public: +#else protected: +#endif CAI_TimedSemaphore *GetMySpeechSemaphore( CBaseEntity *pNpc ); +protected: + 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 #ifdef MAPBASE @@ -311,11 +324,15 @@ private: // template -class CAI_ExpresserHost : public BASE_NPC, protected CAI_ExpresserSink +class CAI_ExpresserHost : public BASE_NPC, public CAI_ExpresserSink { DECLARE_CLASS_NOFRIEND( CAI_ExpresserHost, BASE_NPC ); public: +#ifdef MAPBASE + CAI_Expresser *GetSinkExpresser() { return this->GetExpresser(); } +#endif + 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 ); diff --git a/sp/src/game/server/ai_speechqueue.cpp b/sp/src/game/server/ai_speechqueue.cpp index 7e8bf055..58fccde6 100644 --- a/sp/src/game/server/ai_speechqueue.cpp +++ b/sp/src/game/server/ai_speechqueue.cpp @@ -11,6 +11,9 @@ #include "ai_baseactor.h" #include "ai_speech.h" //#include "flex_expresser.h" +#ifdef MAPBASE +#include "sceneentity.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include @@ -170,15 +173,25 @@ void CResponseQueue::RemoveExpresserHost(CBaseEntity *host) } } +#ifdef MAPBASE +/// Get the expresser for a base entity. +static CAI_Expresser *InferExpresserFromBaseEntity(CBaseEntity * RESTRICT pEnt, CAI_ExpresserSink **ppSink = NULL) +{ + if ( CAI_ExpresserSink *pSink = dynamic_cast(pEnt) ) + { + if (ppSink) + *ppSink = pSink; + return pSink->GetSinkExpresser(); + } + + return NULL; +} +#else /// 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(); } @@ -197,6 +210,7 @@ static CAI_Expresser *InferExpresserFromBaseEntity(CBaseEntity * RESTRICT pEnt) return NULL; } } +#endif void CResponseQueue::CDeferredResponse::Quash() @@ -205,6 +219,23 @@ void CResponseQueue::CDeferredResponse::Quash() m_fDispatchTime = 0; } +#ifdef MAPBASE +void CResponseQueue::AppendFollowupCriteria( AIConcept_t concept, AI_CriteriaSet &set, CAI_Expresser *pEx, + CAI_ExpresserSink *pSink, CBaseEntity *pTarget, CBaseEntity *pIssuer, DeferredResponseTarget_t nTargetType ) +{ + // Allows control over which followups interrupt speech routines + set.AppendCriteria( "followup_allowed_to_speak", (pSink->IsAllowedToSpeakFollowup( concept, pIssuer, nTargetType == kDRT_SPECIFIC )) ? "1" : "0" ); + + set.AppendCriteria( "followup_target_type", UTIL_VarArgs( "%i", (int)nTargetType ) ); + + // NOTE: This assumes any expresser entity derived from CBaseFlex is also derived from CBaseCombatCharacter + if (pTarget->IsCombatCharacter()) + set.AppendCriteria( "is_speaking", (pEx->IsSpeaking() || IsRunningScriptedSceneWithSpeechAndNotPaused( assert_cast(pTarget) )) ? "1" : "0" ); + else + set.AppendCriteria( "is_speaking", "0" ); +} +#endif + bool CResponseQueue::DispatchOneResponse(CDeferredResponse &response) { // find the target. @@ -272,9 +303,15 @@ bool CResponseQueue::DispatchOneResponse(CDeferredResponse &response) continue; // too far } +#ifdef MAPBASE + CAI_ExpresserSink *pSink = NULL; + pEx = InferExpresserFromBaseEntity( pTarget, &pSink ); +#else pEx = InferExpresserFromBaseEntity(pTarget); +#endif if ( !pEx || pTarget == pIssuer ) continue; + AI_CriteriaSet characterCriteria; pEx->GatherCriteria(&characterCriteria, response.m_concept, NULL); characterCriteria.Merge(&deferredCriteria); @@ -282,6 +319,11 @@ bool CResponseQueue::DispatchOneResponse(CDeferredResponse &response) { characterCriteria.AppendCriteria( "dist_from_issuer", UTIL_VarArgs( "%f", sqrt(distIssuerToTargetSq) ) ); } + +#ifdef MAPBASE + AppendFollowupCriteria( response.m_concept, characterCriteria, pEx, pSink, pTarget, pIssuer, kDRT_ALL ); +#endif + AI_Response prospectiveResponse; if ( pEx->FindResponse( prospectiveResponse, response.m_concept, &characterCriteria ) ) { @@ -304,14 +346,26 @@ bool CResponseQueue::DispatchOneResponse(CDeferredResponse &response) return false; // we're done right here. // Get the expresser for the target. +#ifdef MAPBASE + CAI_ExpresserSink *pSink = NULL; + pEx = InferExpresserFromBaseEntity( pTarget, &pSink ); +#else pEx = InferExpresserFromBaseEntity(pTarget); +#endif if (!pEx) return false; - AI_CriteriaSet characterCriteria; pEx->GatherCriteria(&characterCriteria, response.m_concept, NULL); characterCriteria.Merge(&deferredCriteria); +#ifdef MAPBASE + if ( pIssuer ) + { + characterCriteria.AppendCriteria( "dist_from_issuer", UTIL_VarArgs( "%f", (pTarget->GetAbsOrigin() - pIssuer->GetAbsOrigin()).Length() ) ); + } + + AppendFollowupCriteria( response.m_concept, characterCriteria, pEx, pSink, pTarget, pIssuer, kDRT_SPECIFIC ); +#endif pEx->Speak( response.m_concept, &characterCriteria ); return true; @@ -364,7 +418,12 @@ bool CResponseQueue::DispatchOneResponse_ThenANY( CDeferredResponse &response, A continue; // too far } +#ifdef MAPBASE + CAI_ExpresserSink *pSink = NULL; + pEx = InferExpresserFromBaseEntity( pTarget, &pSink ); +#else pEx = InferExpresserFromBaseEntity(pTarget); +#endif if ( !pEx ) continue; @@ -376,6 +435,11 @@ bool CResponseQueue::DispatchOneResponse_ThenANY( CDeferredResponse &response, A { characterCriteria.AppendCriteria( "dist_from_issuer", UTIL_VarArgs( "%f", sqrt(distIssuerToTargetSq) ) ); } + +#ifdef MAPBASE + AppendFollowupCriteria( response.m_concept, characterCriteria, pEx, pSink, pTarget, pIssuer, kDRT_ANY ); +#endif + AI_Response prospectiveResponse; #ifdef MAPBASE diff --git a/sp/src/game/server/ai_speechqueue.h b/sp/src/game/server/ai_speechqueue.h index 15101b70..87ab064f 100644 --- a/sp/src/game/server/ai_speechqueue.h +++ b/sp/src/game/server/ai_speechqueue.h @@ -116,6 +116,11 @@ public: inline int GetNumExpresserTargets() const; inline CBaseEntity *GetExpresserHost(int which) const; +#ifdef MAPBASE + void AppendFollowupCriteria( AIConcept_t concept, AI_CriteriaSet &set, CAI_Expresser *pEx, + CAI_ExpresserSink *pSink, CBaseEntity *pTarget, CBaseEntity *pIssuer, DeferredResponseTarget_t nTargetType ); +#endif + protected: /// Actually send off one response to a consumer /// Return true if dispatch succeeded diff --git a/sp/src/game/server/baseanimating.cpp b/sp/src/game/server/baseanimating.cpp index 38b1606a..0e226209 100644 --- a/sp/src/game/server/baseanimating.cpp +++ b/sp/src/game/server/baseanimating.cpp @@ -335,6 +335,8 @@ BEGIN_ENT_SCRIPTDESC( CBaseAnimating, CBaseEntity, "Animating models" ) DEFINE_SCRIPTFUNC( FindBodygroupByName, "Finds a bodygroup by name" ) DEFINE_SCRIPTFUNC( GetBodygroupCount, "Gets the number of models in a bodygroup" ) DEFINE_SCRIPTFUNC( GetNumBodyGroups, "Gets the number of bodygroups" ) + DEFINE_SCRIPTFUNC( GetModelScale, "Gets the model's scale" ) + DEFINE_SCRIPTFUNC( SetModelScale, "Sets the model's scale with the specified change duration" ) DEFINE_SCRIPTFUNC( Dissolve, "Use 'sprites/blueglow1.vmt' for the default material, Time() for the default start time, false for npcOnly if you don't want it to check if the entity is a NPC first, 0 for the default dissolve type, Vector(0,0,0) for the default dissolver origin, and 0 for the default magnitude." ) DEFINE_SCRIPTFUNC( Ignite, "'NPCOnly' only lets this fall through if the entity is a NPC and 'CalledByLevelDesigner' determines whether to treat this like the Ignite input or just an internal ignition call." ) @@ -1331,7 +1333,7 @@ void CBaseAnimating::HandleAnimEvent( animevent_t *pEvent ) #ifdef MAPBASE else if ( pEvent->event == AE_NPC_RESPONSE ) { - if (!MyNPCPointer()->GetExpresser()->IsSpeaking()) + if (MyNPCPointer() && MyNPCPointer()->GetExpresser() && !MyNPCPointer()->GetExpresser()->IsSpeaking()) { DispatchResponse( pEvent->options ); } @@ -1342,6 +1344,18 @@ void CBaseAnimating::HandleAnimEvent( animevent_t *pEvent ) DispatchResponse( pEvent->options ); return; } + else if ( pEvent->event == AE_VSCRIPT_RUN ) + { + if (!RunScript( pEvent->options )) + Warning( "%s failed to run AE_VSCRIPT_RUN on server with \"%s\"\n", GetDebugName(), pEvent->options ); + return; + } + else if ( pEvent->event == AE_VSCRIPT_RUN_FILE ) + { + if (!RunScriptFile( pEvent->options )) + Warning( "%s failed to run AE_VSCRIPT_RUN_FILE on server with \"%s\"\n", GetDebugName(), pEvent->options ); + return; + } #endif else if ( pEvent->event == AE_RAGDOLL ) { diff --git a/sp/src/game/server/basebludgeonweapon.cpp b/sp/src/game/server/basebludgeonweapon.cpp index 57683a19..c755e2ef 100644 --- a/sp/src/game/server/basebludgeonweapon.cpp +++ b/sp/src/game/server/basebludgeonweapon.cpp @@ -28,6 +28,15 @@ IMPLEMENT_SERVERCLASS_ST( CBaseHLBludgeonWeapon, DT_BaseHLBludgeonWeapon ) END_SEND_TABLE() +#ifdef MAPBASE +BEGIN_DATADESC(CBaseHLBludgeonWeapon) + +DEFINE_FIELD(m_flDelayedFire, FIELD_TIME), +DEFINE_FIELD(m_bShotDelayed, FIELD_BOOLEAN), + +END_DATADESC() +#endif // MAPBASE + #define BLUDGEON_HULL_DIM 16 static const Vector g_bludgeonMins(-BLUDGEON_HULL_DIM,-BLUDGEON_HULL_DIM,-BLUDGEON_HULL_DIM); @@ -39,6 +48,9 @@ static const Vector g_bludgeonMaxs(BLUDGEON_HULL_DIM,BLUDGEON_HULL_DIM,BLUDGEON_ CBaseHLBludgeonWeapon::CBaseHLBludgeonWeapon() { m_bFiresUnderwater = true; +#ifdef MAPBASE + m_bShotDelayed = false; +#endif // MAPBASE } //----------------------------------------------------------------------------- @@ -96,11 +108,19 @@ void CBaseHLBludgeonWeapon::ItemPostFrame( void ) #ifdef MAPBASE if (pOwner->HasSpawnFlags( SF_PLAYER_SUPPRESS_FIRING )) { + m_bShotDelayed = false; WeaponIdle(); return; } -#endif + // See if we need to fire off our secondary round + if (m_bShotDelayed) + { + if (gpGlobals->curtime > m_flDelayedFire) + DelayedAttack(); + } + else +#endif if ( (pOwner->m_nButtons & IN_ATTACK) && (m_flNextPrimaryAttack <= gpGlobals->curtime) ) { PrimaryAttack(); @@ -162,7 +182,12 @@ void CBaseHLBludgeonWeapon::Hit( trace_t &traceHit, Activity nHitActivity, bool pPlayer->EyeVectors( &hitDirection, NULL, NULL ); VectorNormalize( hitDirection ); - CTakeDamageInfo info( GetOwner(), GetOwner(), GetDamageForActivity( nHitActivity ), DMG_CLUB ); +#ifdef MAPBASE + CTakeDamageInfo info(GetOwner(), GetOwner(), GetDamageForActivity(nHitActivity), GetDamageType()); +#else + CTakeDamageInfo info(GetOwner(), GetOwner(), GetDamageForActivity(nHitActivity), DMG_CLUB); +#endif // MAPBASE + if( pPlayer && pHitEntity->IsNPC() ) { @@ -234,7 +259,7 @@ Activity CBaseHLBludgeonWeapon::ChooseIntersectionPointAndActivity( trace_t &hit } - return ACT_VM_HITCENTER; + return GetPrimaryAttackActivity(); } //----------------------------------------------------------------------------- @@ -292,7 +317,6 @@ void CBaseHLBludgeonWeapon::ImpactEffect( trace_t &traceHit ) UTIL_ImpactTrace( &traceHit, DMG_CLUB ); } - //------------------------------------------------------------------------------ // Purpose : Starts the swing of the weapon and determines the animation // Input : bIsSecondary - is this a secondary attack? @@ -315,10 +339,14 @@ void CBaseHLBludgeonWeapon::Swing( int bIsSecondary ) Vector swingEnd = swingStart + forward * GetRange(); UTIL_TraceLine( swingStart, swingEnd, MASK_SHOT_HULL, pOwner, COLLISION_GROUP_NONE, &traceHit ); - Activity nHitActivity = ACT_VM_HITCENTER; + Activity nHitActivity = GetPrimaryAttackActivity(); // Like bullets, bludgeon traces have to trace against triggers. - CTakeDamageInfo triggerInfo( GetOwner(), GetOwner(), GetDamageForActivity( nHitActivity ), DMG_CLUB ); +#ifdef MAPBASE + CTakeDamageInfo triggerInfo(GetOwner(), GetOwner(), GetDamageForActivity(nHitActivity), GetDamageType()); +#else + CTakeDamageInfo triggerInfo(GetOwner(), GetOwner(), GetDamageForActivity(nHitActivity), DMG_CLUB); +#endif // MAPBASE triggerInfo.SetDamagePosition( traceHit.startpos ); triggerInfo.SetDamageForce( forward ); TraceAttackToTriggers( triggerInfo, traceHit.startpos, traceHit.endpos, forward ); @@ -369,31 +397,20 @@ void CBaseHLBludgeonWeapon::Swing( int bIsSecondary ) { nHitActivity = bIsSecondary ? ACT_VM_MISSCENTER2 : ACT_VM_MISSCENTER; +#ifndef MAPBASE // We want to test the first swing again Vector testEnd = swingStart + forward * GetRange(); -#ifdef MAPBASE - // Sound has been moved here since we're using the other melee sounds now - WeaponSound( SINGLE ); -#endif - // See if we happened to hit water - ImpactWater( swingStart, testEnd ); + ImpactWater(swingStart, testEnd); +#endif // !MAPBASE } +#ifndef MAPBASE else { -#ifdef MAPBASE - // Other melee sounds - if (traceHit.m_pEnt && traceHit.m_pEnt->IsWorld()) - WeaponSound(MELEE_HIT_WORLD); - else if (traceHit.m_pEnt && !traceHit.m_pEnt->PassesDamageFilter(triggerInfo)) - WeaponSound(MELEE_MISS); - else - WeaponSound(MELEE_HIT); -#endif - Hit( traceHit, nHitActivity, bIsSecondary ? true : false ); } +#endif // Send the anim SendWeaponAnim( nHitActivity ); @@ -409,5 +426,125 @@ void CBaseHLBludgeonWeapon::Swing( int bIsSecondary ) #ifdef MAPBASE pOwner->SetAnimation( PLAYER_ATTACK1 ); + + if (GetHitDelay() > 0.f) + { + //Play swing sound + WeaponSound(SINGLE); + + m_flDelayedFire = gpGlobals->curtime + GetHitDelay(); + m_bShotDelayed = true; + } + else + { + if (traceHit.fraction == 1.0f) + { + // We want to test the first swing again + Vector testEnd = swingStart + forward * GetRange(); + + //Play swing sound + WeaponSound(SINGLE); + + // See if we happened to hit water + ImpactWater(swingStart, testEnd); + } + else + { + // Other melee sounds + if (traceHit.m_pEnt && traceHit.m_pEnt->IsWorld()) + WeaponSound(MELEE_HIT_WORLD); + else if (traceHit.m_pEnt && !traceHit.m_pEnt->PassesDamageFilter(triggerInfo)) + WeaponSound(MELEE_MISS); + else + WeaponSound(MELEE_HIT); + + Hit(traceHit, nHitActivity, bIsSecondary ? true : false); + } + } #endif } + +#ifdef MAPBASE +void CBaseHLBludgeonWeapon::DelayedAttack(void) +{ + m_bShotDelayed = false; + + trace_t traceHit; + + // Try a ray + CBasePlayer* pOwner = ToBasePlayer(GetOwner()); + if (!pOwner) + return; + + pOwner->RumbleEffect(RUMBLE_CROWBAR_SWING, 0, RUMBLE_FLAG_RESTART); + + Vector swingStart = pOwner->Weapon_ShootPosition(); + Vector forward; + + forward = pOwner->GetAutoaimVector(AUTOAIM_SCALE_DEFAULT, GetRange()); + + Vector swingEnd = swingStart + forward * GetRange(); + UTIL_TraceLine(swingStart, swingEnd, MASK_SHOT_HULL, pOwner, COLLISION_GROUP_NONE, &traceHit); + + if (traceHit.fraction == 1.0) + { + float bludgeonHullRadius = 1.732f * BLUDGEON_HULL_DIM; // hull is +/- 16, so use cuberoot of 2 to determine how big the hull is from center to the corner point + + // Back off by hull "radius" + swingEnd -= forward * bludgeonHullRadius; + + UTIL_TraceHull(swingStart, swingEnd, g_bludgeonMins, g_bludgeonMaxs, MASK_SHOT_HULL, pOwner, COLLISION_GROUP_NONE, &traceHit); + if (traceHit.fraction < 1.0 && traceHit.m_pEnt) + { + Vector vecToTarget = traceHit.m_pEnt->GetAbsOrigin() - swingStart; + VectorNormalize(vecToTarget); + + float dot = vecToTarget.Dot(forward); + + // YWB: Make sure they are sort of facing the guy at least... + if (dot < 0.70721f) + { + // Force amiss + traceHit.fraction = 1.0f; + } + else + { + ChooseIntersectionPointAndActivity(traceHit, g_bludgeonMins, g_bludgeonMaxs, pOwner); + } + } + } + + if (traceHit.fraction == 1.0f) + { + // We want to test the first swing again + Vector testEnd = swingStart + forward * GetRange(); + + // See if we happened to hit water + ImpactWater(swingStart, testEnd); + } + else + { + CTakeDamageInfo triggerInfo(GetOwner(), GetOwner(), GetDamageForActivity(GetActivity()), GetDamageType()); + triggerInfo.SetDamagePosition(traceHit.startpos); + triggerInfo.SetDamageForce(forward); + + // Other melee sounds + if (traceHit.m_pEnt && traceHit.m_pEnt->IsWorld()) + WeaponSound(MELEE_HIT_WORLD); + else if (traceHit.m_pEnt && !traceHit.m_pEnt->PassesDamageFilter(triggerInfo)) + WeaponSound(MELEE_MISS); + else + WeaponSound(MELEE_HIT); + + Hit(traceHit, GetActivity(), false); + } +} + +bool CBaseHLBludgeonWeapon::CanHolster(void) +{ + if (m_bShotDelayed) + return false; + + return BaseClass::CanHolster(); +} +#endif // MAPBASE diff --git a/sp/src/game/server/basebludgeonweapon.h b/sp/src/game/server/basebludgeonweapon.h index 6f2a7eaf..57f0ed11 100644 --- a/sp/src/game/server/basebludgeonweapon.h +++ b/sp/src/game/server/basebludgeonweapon.h @@ -23,6 +23,9 @@ public: CBaseHLBludgeonWeapon(); DECLARE_SERVERCLASS(); +#ifdef MAPBASE + DECLARE_DATADESC(); +#endif // MAPBASE virtual void Spawn( void ); virtual void Precache( void ); @@ -30,6 +33,9 @@ public: //Attack functions virtual void PrimaryAttack( void ); virtual void SecondaryAttack( void ); +#ifdef MAPBASE + void DelayedAttack(void); +#endif // MAPBASE virtual void ItemPostFrame( void ); @@ -44,6 +50,12 @@ public: virtual int CapabilitiesGet( void ); virtual int WeaponMeleeAttack1Condition( float flDot, float flDist ); +#ifdef MAPBASE + virtual int GetDamageType() { return DMG_CLUB; } + virtual float GetHitDelay() { return 0.f; } + virtual bool CanHolster(void); +#endif // MAPBASE + protected: virtual void ImpactEffect( trace_t &trace ); @@ -52,6 +64,11 @@ private: void Swing( int bIsSecondary ); void Hit( trace_t &traceHit, Activity nHitActivity, bool bIsSecondary ); Activity ChooseIntersectionPointAndActivity( trace_t &hitTrace, const Vector &mins, const Vector &maxs, CBasePlayer *pOwner ); + +#ifdef MAPBASE + float m_flDelayedFire; + bool m_bShotDelayed; +#endif // MAPBASE }; #endif \ No newline at end of file diff --git a/sp/src/game/server/basecombatcharacter.cpp b/sp/src/game/server/basecombatcharacter.cpp index 7d45ec4e..a1033586 100644 --- a/sp/src/game/server/basecombatcharacter.cpp +++ b/sp/src/game/server/basecombatcharacter.cpp @@ -175,6 +175,7 @@ BEGIN_ENT_SCRIPTDESC( CBaseCombatCharacter, CBaseFlex, "The base class shared by DEFINE_SCRIPTFUNC_NAMED( ScriptRelationType, "GetRelationship", "Get a character's relationship to a specific entity." ) DEFINE_SCRIPTFUNC_NAMED( ScriptRelationPriority, "GetRelationPriority", "Get a character's relationship priority for a specific entity." ) DEFINE_SCRIPTFUNC_NAMED( ScriptSetRelationship, "SetRelationship", "Set a character's relationship with a specific entity." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetClassRelationship, "SetClassRelationship", "Set a character's relationship with a specific Classify() class." ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetVehicleEntity, "GetVehicleEntity", "Get the entity for a character's current vehicle if they're in one." ) @@ -1264,6 +1265,11 @@ bool CTraceFilterMelee::ShouldHitEntity( IHandleEntity *pHandleEntity, int conte if ( pEntity->m_takedamage == DAMAGE_NO ) return false; +#ifdef MAPBASE // Moved from CheckTraceHullAttack() + if( m_pPassEnt && !pEntity->CanBeHitByMeleeAttack( const_cast(EntityFromEntityHandle( m_pPassEnt ) ) ) ) + return false; +#endif + // FIXME: Do not translate this to the driver because the driver only accepts damage from the vehicle // Translate the vehicle into its driver for damage /* @@ -1311,6 +1317,10 @@ bool CTraceFilterMelee::ShouldHitEntity( IHandleEntity *pHandleEntity, int conte } else { +#ifdef MAPBASE + // Do not override an existing hit entity + if (!m_pHit) +#endif m_pHit = pEntity; // Make sure if the player is holding this, he drops it @@ -1386,11 +1396,13 @@ CBaseEntity *CBaseCombatCharacter::CheckTraceHullAttack( const Vector &vStart, c pEntity = traceFilter.m_pHit; } +#ifndef MAPBASE // Moved to CTraceFilterMelee if( pEntity && !pEntity->CanBeHitByMeleeAttack(this) ) { // If we touched something, but it shouldn't be hit, return nothing. pEntity = NULL; } +#endif return pEntity; @@ -3239,6 +3251,16 @@ void CBaseCombatCharacter::SetDefaultRelationship(Class_T nClass, Class_T nClass } #ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Determine whether or not default relationships are loaded +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CBaseCombatCharacter::DefaultRelationshipsLoaded() +{ + return m_DefaultRelationship != NULL; +} + //----------------------------------------------------------------------------- // Purpose: Fetch the default (ignore ai_relationship changes) relationship // Input : @@ -3466,7 +3488,7 @@ void CBaseCombatCharacter::AddRelationship( const char *pszRelationship, CBaseEn } else { -#ifdef MAPBASE // I know the extra #ifdef is pointless, but it's there so you know this is new + // NEW: Classify class relationships if (!Q_strnicmp(entityString, "CLASS_", 5)) { // Go through all of the classes and find which one this is @@ -3487,8 +3509,7 @@ void CBaseCombatCharacter::AddRelationship( const char *pszRelationship, CBaseEn } if (!bFoundEntity) -#endif - DevWarning( "Couldn't set relationship to unknown entity or class (%s)!\n", entityString ); + DevWarning( "Couldn't set relationship to unknown entity or class (%s)!\n", entityString ); } } } @@ -3594,7 +3615,7 @@ CBaseEntity *CBaseCombatCharacter::Weapon_FindUsable( const Vector &range ) else if (hl2_episodic.GetBool() && !GetActiveWeapon()) { // Unarmed citizens are conservative in their weapon finding...in Episode One - if (Classify() != CLASS_PLAYER_ALLY_VITAL && Q_strncmp(STRING(gpGlobals->mapname), "ep1_", 4)) + if (Classify() != CLASS_PLAYER_ALLY_VITAL && Q_strncmp(STRING(gpGlobals->mapname), "ep1_", 4) == 0) bConservative = true; } #endif @@ -4643,6 +4664,13 @@ void CBaseCombatCharacter::ScriptSetRelationship( HSCRIPT pTarget, int dispositi AddEntityRelationship( ToEnt( pTarget ), (Disposition_t)disposition, priority ); } +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::ScriptSetClassRelationship( int classify, int disposition, int priority ) +{ + AddClassRelationship( (Class_T)classify, (Disposition_t)disposition, priority); +} + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- HSCRIPT CBaseCombatCharacter::ScriptGetVehicleEntity() diff --git a/sp/src/game/server/basecombatcharacter.h b/sp/src/game/server/basecombatcharacter.h index c7eb9960..cdae243d 100644 --- a/sp/src/game/server/basecombatcharacter.h +++ b/sp/src/game/server/basecombatcharacter.h @@ -263,6 +263,10 @@ public: virtual bool CanBecomeServerRagdoll( void ) { return true; } +#ifdef MAPBASE + virtual void OnEnemyRangeAttackedMe( CBaseEntity *pEnemy, const Vector &vecDir, const Vector &vecEnd ) {} +#endif + // ----------------------- // Damage // ----------------------- @@ -440,6 +444,7 @@ public: int ScriptRelationType( HSCRIPT pTarget ); int ScriptRelationPriority( HSCRIPT pTarget ); void ScriptSetRelationship( HSCRIPT pTarget, int disposition, int priority ); + void ScriptSetClassRelationship( int classify, int disposition, int priority ); HSCRIPT ScriptGetVehicleEntity(); @@ -462,6 +467,7 @@ public: static void AllocateDefaultRelationships( ); static void SetDefaultRelationship( Class_T nClass, Class_T nClassTarget, Disposition_t nDisposition, int nPriority ); #ifdef MAPBASE + static bool DefaultRelationshipsLoaded(); static Disposition_t GetDefaultRelationshipDisposition( Class_T nClassSource, Class_T nClassTarget ); static int GetDefaultRelationshipPriority( Class_T nClassSource, Class_T nClassTarget ); int GetDefaultRelationshipPriority( Class_T nClassTarget ); diff --git a/sp/src/game/server/baseentity.cpp b/sp/src/game/server/baseentity.cpp index 67c3b17e..9df5cc09 100644 --- a/sp/src/game/server/baseentity.cpp +++ b/sp/src/game/server/baseentity.cpp @@ -2378,6 +2378,9 @@ BEGIN_ENT_SCRIPTDESC_ROOT( CBaseEntity, "Root class of all server-side entities" DEFINE_SCRIPTFUNC( GetContextExpireTime, "Get a response context's expiration time" ) DEFINE_SCRIPTFUNC( GetContextCount, "Get the number of response contexts" ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetContextIndex, "GetContextIndex", "Get a response context at a specific index in the form of a table" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetGroundEntity, "GetGroundEntity", "Get the entity we're standing on." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetGroundEntity, "SetGroundEntity", "Set the entity we're standing on." ) DEFINE_SCRIPTFUNC_NAMED( ScriptFollowEntity, "FollowEntity", "Begin following the specified entity. This makes this entity non-solid, parents it to the target entity, and teleports it to the specified entity's origin. The second parameter is whether or not to use bonemerging while following." ) DEFINE_SCRIPTFUNC( StopFollowingEntity, "Stops following an entity if we're following one." ) @@ -10297,8 +10300,9 @@ bool CBaseEntity::ScriptAddOutput( const char *pszOutputName, const char *pszTar const char *CBaseEntity::ScriptGetKeyValue( const char *pszKeyName ) { static char szValue[128]; - GetKeyValue( pszKeyName, szValue, sizeof(szValue) ); - return szValue; + if ( GetKeyValue( pszKeyName, szValue, sizeof(szValue) ) ) + return szValue; + return NULL; } //----------------------------------------------------------------------------- diff --git a/sp/src/game/server/baseentity.h b/sp/src/game/server/baseentity.h index 1f2f75e1..acbffdb2 100644 --- a/sp/src/game/server/baseentity.h +++ b/sp/src/game/server/baseentity.h @@ -1445,6 +1445,11 @@ public: void SetGroundEntity( CBaseEntity *ground ); CBaseEntity *GetGroundEntity( void ); CBaseEntity *GetGroundEntity( void ) const { return const_cast(this)->GetGroundEntity(); } + +#ifdef MAPBASE_VSCRIPT + HSCRIPT ScriptGetGroundEntity(); + void ScriptSetGroundEntity( HSCRIPT hGroundEnt ); +#endif // Gets the velocity we impart to a player standing on us virtual void GetGroundVelocityToApply( Vector &vecGroundVel ) { vecGroundVel = vec3_origin; } @@ -1575,7 +1580,7 @@ public: 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 #ifdef MAPBASE - , int iSpecialDSP = 0, int iSpeakerIndex = 0 // Needed for env_microphone + , int iSpecialDSP = 0, int iSpeakerIndex = -1 // Needed for env_microphone #endif ); diff --git a/sp/src/game/server/entitylist.cpp b/sp/src/game/server/entitylist.cpp index 55d5614b..29d87f50 100644 --- a/sp/src/game/server/entitylist.cpp +++ b/sp/src/game/server/entitylist.cpp @@ -736,6 +736,12 @@ CBaseEntity *CGlobalEntityList::FindEntityCustomProcedural( CBaseEntity *pStartE g_pScriptVM->ExecuteFunction( g_CustomProcedurals[i].hFunc, args, 5, &functionReturn, NULL, true ); + if (pStartEntity && ToEnt( functionReturn.m_hScript ) == pStartEntity) + { + Warning( "WARNING: Custom procedural %s returned entity identical to start entity (%s), returning null\n", g_CustomProcedurals[i].szName, pStartEntity->GetDebugName() ); + return NULL; + } + return ToEnt( functionReturn.m_hScript ); } } diff --git a/sp/src/game/server/filters.cpp b/sp/src/game/server/filters.cpp index c15f8eee..e95ba310 100644 --- a/sp/src/game/server/filters.cpp +++ b/sp/src/game/server/filters.cpp @@ -162,10 +162,10 @@ void CBaseFilter::InputSetField( inputdata_t& inputdata ) #ifdef MAPBASE_VSCRIPT bool CBaseFilter::ScriptPassesFilter( HSCRIPT pCaller, HSCRIPT pEntity ) { return PassesFilter( ToEnt(pCaller), ToEnt(pEntity) ); } -bool CBaseFilter::ScriptPassesDamageFilter( HSCRIPT pCaller, HSCRIPT pInfo ) { return (pInfo) ? PassesDamageFilter( ToEnt( pCaller ), *const_cast(HScriptToClass( pInfo )) ) : NULL; } -bool CBaseFilter::ScriptPassesFinalDamageFilter( HSCRIPT pCaller, HSCRIPT pInfo ) { return (pInfo) ? PassesFinalDamageFilter( ToEnt( pCaller ), *const_cast(HScriptToClass( pInfo )) ) : NULL; } -bool CBaseFilter::ScriptBloodAllowed( HSCRIPT pCaller, HSCRIPT pInfo ) { return (pInfo) ? BloodAllowed( ToEnt( pCaller ), *const_cast(HScriptToClass( pInfo )) ) : NULL; } -bool CBaseFilter::ScriptDamageMod( HSCRIPT pCaller, HSCRIPT pInfo ) { return (pInfo) ? DamageMod( ToEnt( pCaller ), *HScriptToClass( pInfo ) ) : NULL; } +bool CBaseFilter::ScriptPassesDamageFilter( HSCRIPT pCaller, HSCRIPT pInfo ) { return (pInfo) ? PassesDamageFilter( ToEnt( pCaller ), *const_cast(HScriptToClass( pInfo )) ) : false; } +bool CBaseFilter::ScriptPassesFinalDamageFilter( HSCRIPT pCaller, HSCRIPT pInfo ) { return (pInfo) ? PassesFinalDamageFilter( ToEnt( pCaller ), *const_cast(HScriptToClass( pInfo )) ) : false; } +bool CBaseFilter::ScriptBloodAllowed( HSCRIPT pCaller, HSCRIPT pInfo ) { return (pInfo) ? BloodAllowed( ToEnt( pCaller ), *const_cast(HScriptToClass( pInfo )) ) : false; } +bool CBaseFilter::ScriptDamageMod( HSCRIPT pCaller, HSCRIPT pInfo ) { return (pInfo) ? DamageMod( ToEnt( pCaller ), *HScriptToClass( pInfo ) ) : false; } #endif diff --git a/sp/src/game/server/hl2/ai_behavior_functank.cpp b/sp/src/game/server/hl2/ai_behavior_functank.cpp index 89f79228..105bf6e2 100644 --- a/sp/src/game/server/hl2/ai_behavior_functank.cpp +++ b/sp/src/game/server/hl2/ai_behavior_functank.cpp @@ -190,6 +190,24 @@ int CAI_FuncTankBehavior::SelectSchedule() return SCHED_IDLE_STAND; } +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_FuncTankBehavior::ModifyOrAppendCriteria( AI_CriteriaSet &set ) +{ + BaseClass::ModifyOrAppendCriteria( set ); + +#ifdef MAPBASE + set.AppendCriteria( "ft_mounted", m_bMounted ? "1" : "0" ); + + if (m_hFuncTank) + { + set.AppendCriteria( "ft_classname", m_hFuncTank->GetClassname() ); + m_hFuncTank->AppendContextToCriteria( set, "ft_" ); + } +#endif +} + //----------------------------------------------------------------------------- // Purpose: // Input : activity - diff --git a/sp/src/game/server/hl2/ai_behavior_functank.h b/sp/src/game/server/hl2/ai_behavior_functank.h index 75d5b8df..54f91d1f 100644 --- a/sp/src/game/server/hl2/ai_behavior_functank.h +++ b/sp/src/game/server/hl2/ai_behavior_functank.h @@ -55,6 +55,8 @@ public: bool CanManTank( CFuncTank *pTank, bool bForced ); #endif + void ModifyOrAppendCriteria( AI_CriteriaSet &set ); + Activity NPC_TranslateActivity( Activity activity ); // Conditions: diff --git a/sp/src/game/server/hl2/grenade_bugbait.cpp b/sp/src/game/server/hl2/grenade_bugbait.cpp index 7d5d7a47..feddcd56 100644 --- a/sp/src/game/server/hl2/grenade_bugbait.cpp +++ b/sp/src/game/server/hl2/grenade_bugbait.cpp @@ -35,6 +35,9 @@ CBugBaitSensor* GetBugBaitSensorList() CBugBaitSensor::CBugBaitSensor( void ) { g_BugBaitSensorList.Insert( this ); +#ifdef MAPBASE + m_bUseRadius = true; +#endif } CBugBaitSensor::~CBugBaitSensor( void ) @@ -50,10 +53,24 @@ BEGIN_DATADESC( CBugBaitSensor ) DEFINE_KEYFIELD( m_bEnabled, FIELD_BOOLEAN, "Enabled" ), DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bUseRadius, FIELD_BOOLEAN, "useradius" ), + DEFINE_KEYFIELD( m_vecMins, FIELD_VECTOR, "bmins" ), + DEFINE_KEYFIELD( m_vecMaxs, FIELD_VECTOR, "bmaxs" ), +#endif + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "EnableRadius", InputEnableRadius ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisableRadius", InputDisableRadius ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetRadius", InputSetRadius ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetMins", InputSetMins ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetMaxs", InputSetMaxs ), +#endif + // Function Pointers DEFINE_OUTPUT( m_OnBaited, "OnBaited" ), @@ -267,18 +284,42 @@ bool CGrenadeBugBait::ActivateBugbaitTargets( CBaseEntity *pOwner, Vector vecOri continue; //Make sure we're within range of the sensor - if ( pSensor->GetRadius() > ( pSensor->GetAbsOrigin() - vecOrigin ).Length() ) - { - //Tell the sensor it's been hit - if ( pSensor->Baited( pOwner ) ) +#ifdef MAPBASE + if ( pSensor->UsesRadius() ){ +#endif + if ( pSensor->GetRadius() > (pSensor->GetAbsOrigin() - vecOrigin).Length() ) { - //If we're suppressing the call to antlions, then don't make a bugbait sound - if ( pSensor->SuppressCall() ) + //Tell the sensor it's been hit + if ( pSensor->Baited( pOwner ) ) { - suppressCall = true; + //If we're suppressing the call to antlions, then don't make a bugbait sound + if ( pSensor->SuppressCall() ) + { + suppressCall = true; + } + } + } +#ifdef MAPBASE + } + else{ + Vector vMins = pSensor->GetAbsMins(); + Vector vMaxs = pSensor->GetAbsMaxs(); + bool inBox = ((vecOrigin.x >= vMins.x && vecOrigin.x <= vMaxs.x) && + (vecOrigin.y >= vMins.y && vecOrigin.y <= vMaxs.y) && + (vecOrigin.z >= vMins.z && vecOrigin.z <= vMaxs.z)); + if ( inBox ){ + //Tell the sensor it's been hit + if ( pSensor->Baited( pOwner ) ) + { + //If we're suppressing the call to antlions, then don't make a bugbait sound + if ( pSensor->SuppressCall() ) + { + suppressCall = true; + } } } } +#endif } return suppressCall; diff --git a/sp/src/game/server/hl2/grenade_bugbait.h b/sp/src/game/server/hl2/grenade_bugbait.h index 7a5df993..fb2a68cc 100644 --- a/sp/src/game/server/hl2/grenade_bugbait.h +++ b/sp/src/game/server/hl2/grenade_bugbait.h @@ -63,6 +63,28 @@ public: m_bEnabled = !m_bEnabled; } +#ifdef MAPBASE + void InputEnableRadius( inputdata_t &data ){ + m_bUseRadius = true; + } + + void InputDisableRadius( inputdata_t &data ){ + m_bUseRadius = false; + } + + void InputSetRadius( inputdata_t &data ){ + m_flRadius = data.value.Int(); + } + + void InputSetMins( inputdata_t &data ){ + data.value.Vector3D( m_vecMins ); + } + + void InputSetMaxs( inputdata_t &data ){ + data.value.Vector3D( m_vecMaxs ); + } +#endif + bool SuppressCall( void ) { return ( HasSpawnFlags( SF_BUGBAIT_SUPPRESS_CALL ) ); @@ -91,10 +113,28 @@ public: return !m_bEnabled; } +#ifdef MAPBASE + bool UsesRadius( void ) const { + return m_bUseRadius; + } + + Vector GetAbsMins( void ) const { + return GetAbsOrigin() + m_vecMins; + } + Vector GetAbsMaxs( void ) const { + return GetAbsOrigin() + m_vecMaxs; + } +#endif + protected: float m_flRadius; bool m_bEnabled; +#ifdef MAPBASE + bool m_bUseRadius; + Vector m_vecMins; + Vector m_vecMaxs; +#endif COutputEvent m_OnBaited; public: diff --git a/sp/src/game/server/hl2/hl2_player.cpp b/sp/src/game/server/hl2/hl2_player.cpp index 1cd610bb..cefdeeb4 100644 --- a/sp/src/game/server/hl2/hl2_player.cpp +++ b/sp/src/game/server/hl2/hl2_player.cpp @@ -1814,7 +1814,7 @@ bool CHL2_Player::CommanderFindGoal( commandgoal_t *pGoal ) // Get either our +USE entity or the gravity gun entity CBaseEntity *pHeldEntity = GetPlayerHeldEntity(this); if ( !pHeldEntity ) - PhysCannonGetHeldEntity( GetActiveWeapon() ); + pHeldEntity = PhysCannonGetHeldEntity( GetActiveWeapon() ); CTraceFilterSkipTwoEntities filter( this, pHeldEntity, COLLISION_GROUP_INTERACTIVE_DEBRIS ); #else diff --git a/sp/src/game/server/hl2/npc_BaseZombie.cpp b/sp/src/game/server/hl2/npc_BaseZombie.cpp index fee51dd7..f751d09a 100644 --- a/sp/src/game/server/hl2/npc_BaseZombie.cpp +++ b/sp/src/game/server/hl2/npc_BaseZombie.cpp @@ -159,6 +159,10 @@ ConVar zombie_decaymax( "zombie_decaymax", "0.4" ); ConVar zombie_ambushdist( "zombie_ambushdist", "16000" ); +#ifdef MAPBASE +ConVar zombie_no_flinch_during_unique_anim( "zombie_no_flinch_during_unique_anim", "1", FCVAR_NONE, "Prevents zombies from flinching during actbusies and scripted sequences." ); +#endif + //========================================================= // For a couple of reasons, we keep a running count of how // many zombies in the world are angry at any given time. @@ -1745,7 +1749,11 @@ void CNPC_BaseZombie::HandleAnimEvent( animevent_t *pEvent ) dmgInfo.SetDamagePosition( vecHeadCrabPosition ); +#ifdef MAPBASE + ReleaseHeadcrab( vecHeadCrabPosition, vVelocity *iSpeed, true, false, true ); +#else ReleaseHeadcrab( EyePosition(), vVelocity * iSpeed, true, false, true ); +#endif GuessDamageForce( &dmgInfo, vVelocity, vecHeadCrabPosition, 0.5f ); TakeDamage( dmgInfo ); @@ -1927,6 +1935,31 @@ void CNPC_BaseZombie::OnScheduleChange( void ) } +//--------------------------------------------------------- +//--------------------------------------------------------- + +bool CNPC_BaseZombie::CanFlinch( void ) +{ + if (!BaseClass::CanFlinch()) + return false; + +#ifdef MAPBASE + if (zombie_no_flinch_during_unique_anim.GetBool()) + { + // Don't flinch if currently playing actbusy animation (navigating to or from one is fine) + if (m_ActBusyBehavior.IsInsideActBusy()) + return false; + + // Don't flinch if currently playing scripted sequence (navigating to or from one is fine) + if (m_NPCState == NPC_STATE_SCRIPT && (IsCurSchedule( SCHED_SCRIPTED_WAIT, false ) || IsCurSchedule( SCHED_SCRIPTED_FACE, false ))) + return false; + } +#endif + + return true; +} + + //--------------------------------------------------------- //--------------------------------------------------------- int CNPC_BaseZombie::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode ) @@ -2435,6 +2468,20 @@ void CNPC_BaseZombie::RemoveHead( void ) } +//--------------------------------------------------------- +//--------------------------------------------------------- +void CNPC_BaseZombie::SetModel( const char *szModelName ) +{ +#ifdef MAPBASE + // Zombies setting the same model again is a problem when they should maintain their current sequence (e.g. during dynamic interactions) + if ( IsRunningDynamicInteraction() && GetModelIndex() != 0 && FStrEq( szModelName, STRING(GetModelName()) ) ) + return; +#endif + + BaseClass::SetModel( szModelName ); +} + + bool CNPC_BaseZombie::ShouldPlayFootstepMoan( void ) { if( random->RandomInt( 1, zombie_stepfreq.GetInt() * s_iAngryZombies ) == 1 ) @@ -2450,9 +2497,15 @@ bool CNPC_BaseZombie::ShouldPlayFootstepMoan( void ) #define CRAB_HULL_EXPAND 1.1f //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- -bool CNPC_BaseZombie::HeadcrabFits( CBaseAnimating *pCrab ) +bool CNPC_BaseZombie::HeadcrabFits( CBaseAnimating *pCrab, const Vector *vecOrigin ) { - Vector vecSpawnLoc = pCrab->GetAbsOrigin(); + Vector vecSpawnLoc; +#ifdef MAPBASE + if (vecOrigin) + vecSpawnLoc = *vecOrigin; + else +#endif + vecSpawnLoc = pCrab->GetAbsOrigin(); CTraceFilterSimpleList traceFilter( COLLISION_GROUP_NONE ); traceFilter.AddEntityToIgnore( pCrab ); @@ -2535,7 +2588,12 @@ void CNPC_BaseZombie::ReleaseHeadcrab( const Vector &vecOrigin, const Vector &ve SetHeadcrabSpawnLocation( iCrabAttachment, pAnimatingGib ); } +#ifdef MAPBASE + // Server ragdolls don't have a valid origin on spawn, so we have to use the origin originally passed + if( !HeadcrabFits( pAnimatingGib, m_bForceServerRagdoll ? &vecOrigin : NULL ) ) +#else if( !HeadcrabFits(pAnimatingGib) ) +#endif { UTIL_Remove(pGib); return; @@ -2552,11 +2610,20 @@ void CNPC_BaseZombie::ReleaseHeadcrab( const Vector &vecOrigin, const Vector &ve if( UTIL_ShouldShowBlood(BLOOD_COLOR_YELLOW) ) { - UTIL_BloodImpact( pGib->WorldSpaceCenter(), Vector(0,0,1), BLOOD_COLOR_YELLOW, 1 ); + Vector vecGibCenter; +#ifdef MAPBASE + // Server ragdolls don't have a valid origin on spawn, so we have to use the origin originally passed + if (m_bForceServerRagdoll) + vecGibCenter = vecOrigin; + else +#endif + vecGibCenter = pGib->WorldSpaceCenter(); + + UTIL_BloodImpact( vecGibCenter, Vector(0,0,1), BLOOD_COLOR_YELLOW, 1 ); for ( int i = 0 ; i < 3 ; i++ ) { - Vector vecSpot = pGib->WorldSpaceCenter(); + Vector vecSpot = vecGibCenter; vecSpot.x += random->RandomFloat( -8, 8 ); vecSpot.y += random->RandomFloat( -8, 8 ); @@ -2590,6 +2657,9 @@ void CNPC_BaseZombie::ReleaseHeadcrab( const Vector &vecOrigin, const Vector &ve // Inherit some misc. properties pCrab->m_bForceServerRagdoll = m_bForceServerRagdoll; pCrab->m_iViewHideFlags = m_iViewHideFlags; + + // Add response context for companion response (more reliable than checking for post-death zombie entity) + pCrab->AddContext( "from_zombie", "1", 2.0f ); #endif // make me the crab's owner to avoid collision issues diff --git a/sp/src/game/server/hl2/npc_BaseZombie.h b/sp/src/game/server/hl2/npc_BaseZombie.h index 743186de..fb17f037 100644 --- a/sp/src/game/server/hl2/npc_BaseZombie.h +++ b/sp/src/game/server/hl2/npc_BaseZombie.h @@ -151,6 +151,8 @@ public: int OnTakeDamage_Alive( const CTakeDamageInfo &info ); virtual float GetReactionDelay( CBaseEntity *pEnemy ) { return 0.0; } + bool CanFlinch( void ); + virtual int SelectSchedule ( void ); virtual int SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode ); virtual void BuildScheduleTestBits( void ); @@ -186,9 +188,10 @@ public: // Headcrab releasing/breaking apart void RemoveHead( void ); virtual void SetZombieModel( void ) { }; + virtual void SetModel( const char *szModelName ); virtual void BecomeTorso( const Vector &vecTorsoForce, const Vector &vecLegsForce ); virtual bool CanBecomeLiveTorso() { return false; } - virtual bool HeadcrabFits( CBaseAnimating *pCrab ); + virtual bool HeadcrabFits( CBaseAnimating *pCrab, const Vector *vecOrigin = NULL ); void ReleaseHeadcrab( const Vector &vecOrigin, const Vector &vecVelocity, bool fRemoveHead, bool fRagdollBody, bool fRagdollCrab = false ); void SetHeadcrabSpawnLocation( int iCrabAttachment, CBaseAnimating *pCrab ); diff --git a/sp/src/game/server/hl2/npc_alyx_episodic.cpp b/sp/src/game/server/hl2/npc_alyx_episodic.cpp index ee8b197c..dd5a35b0 100644 --- a/sp/src/game/server/hl2/npc_alyx_episodic.cpp +++ b/sp/src/game/server/hl2/npc_alyx_episodic.cpp @@ -1092,10 +1092,14 @@ void CNPC_Alyx::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo & //----------------------------------------------------------------------------- void CNPC_Alyx::EnemyIgnited( CAI_BaseNPC *pVictim ) { +#ifdef MAPBASE + BaseClass::EnemyIgnited( pVictim ); +#else if ( FVisible( pVictim ) ) { SpeakIfAllowed( TLK_ENEMY_BURNING ); } +#endif } //----------------------------------------------------------------------------- @@ -1252,6 +1256,7 @@ void CNPC_Alyx::DoCustomSpeechAI( void ) CBasePlayer *pPlayer = AI_GetSinglePlayer(); +#ifndef MAPBASE // Ported to CNPC_PlayerCompanion if ( HasCondition(COND_NEW_ENEMY) && GetEnemy() ) { if ( GetEnemy()->Classify() == CLASS_HEADCRAB ) @@ -1278,6 +1283,7 @@ void CNPC_Alyx::DoCustomSpeechAI( void ) } } } +#endif // Darkness mode speech ClearCondition( COND_ALYX_IN_DARK ); @@ -1917,6 +1923,7 @@ int CNPC_Alyx::SelectSchedule( void ) //----------------------------------------------------------------------------- int CNPC_Alyx::SelectScheduleDanger( void ) { +#ifndef MAPBASE if( HasCondition( COND_HEAR_DANGER ) ) { CSound *pSound; @@ -1929,6 +1936,7 @@ int CNPC_Alyx::SelectScheduleDanger( void ) SpeakIfAllowed( TLK_DANGER_ZOMBINE_GRENADE ); } } +#endif return BaseClass::SelectScheduleDanger(); } diff --git a/sp/src/game/server/hl2/npc_citizen17.cpp b/sp/src/game/server/hl2/npc_citizen17.cpp index be80179a..02baaf07 100644 --- a/sp/src/game/server/hl2/npc_citizen17.cpp +++ b/sp/src/game/server/hl2/npc_citizen17.cpp @@ -412,7 +412,6 @@ 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." ) diff --git a/sp/src/game/server/hl2/npc_combine.cpp b/sp/src/game/server/hl2/npc_combine.cpp index 73bac242..c2cac902 100644 --- a/sp/src/game/server/hl2/npc_combine.cpp +++ b/sp/src/game/server/hl2/npc_combine.cpp @@ -47,6 +47,8 @@ ConVar npc_combine_protected_run( "npc_combine_protected_run", "0", FCVAR_NONE, 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." ); + +ConVar npc_combine_fixed_shootpos( "npc_combine_fixed_shootpos", "0", FCVAR_NONE, "Mapbase: Toggles fixed Combine soldier shoot position." ); #endif #define COMBINE_SKIN_DEFAULT 0 @@ -2959,6 +2961,28 @@ Vector CNPC_Combine::Weapon_ShootPosition( ) // FIXME: rename this "estimated" since it's not based on animation // FIXME: the orientation won't be correct when testing from arbitary positions for arbitary angles +#ifdef MAPBASE + // HACKHACK: This weapon shoot position code does not work properly when in close range, causing the aim + // to drift to the left as the enemy gets closer to it. + // This problem is usually bearable for regular combat, but it causes dynamic interaction yaw to be offset + // as well, preventing most from ever being triggered. + // Ideally, this should be fixed from the root cause, but due to the sensitivity of such a change, this is + // currently being tied to a cvar which is off by default. + // + // If the cvar is disabled but the soldier has valid interactions on its current enemy, then a separate hack + // will still attempt to correct the drift as the enemy gets closer. + if ( npc_combine_fixed_shootpos.GetBool() ) + { + right *= 0.0f; + } + else if ( HasValidInteractionsOnCurrentEnemy() ) + { + float flDistSqr = GetEnemy()->WorldSpaceCenter().DistToSqr( WorldSpaceCenter() ); + if (flDistSqr < Square( 128.0f )) + right *= (flDistSqr / Square( 128.0f )); + } +#endif + if ( bStanding ) { if( HasShotgun() ) diff --git a/sp/src/game/server/hl2/npc_playercompanion.cpp b/sp/src/game/server/hl2/npc_playercompanion.cpp index 68568a72..660cb218 100644 --- a/sp/src/game/server/hl2/npc_playercompanion.cpp +++ b/sp/src/game/server/hl2/npc_playercompanion.cpp @@ -35,6 +35,8 @@ #include "mapbase/GlobalStrings.h" #include "world.h" #include "vehicle_base.h" +#include "npc_headcrab.h" +#include "npc_BaseZombie.h" #endif ConVar ai_debug_readiness("ai_debug_readiness", "0" ); @@ -640,6 +642,55 @@ void CNPC_PlayerCompanion::DoCustomSpeechAI( void ) { SpeakIfAllowed( TLK_PLDEAD ); } + +#ifdef MAPBASE + // Unique new enemy concepts ported from Alyx + // The casts have been changed to dynamic_cast due to the risk of non-CBaseHeadcrab/CNPC_BaseZombie enemies using those classes + if ( HasCondition(COND_NEW_ENEMY) && GetEnemy() ) + { + int nClass = GetEnemy()->Classify(); + if ( nClass == CLASS_HEADCRAB ) + { + CBaseHeadcrab *pHC = dynamic_cast(GetEnemy()); + if ( pHC ) + { + // If we see a headcrab for the first time as he's jumping at me, freak out! + if ( ( GetEnemy()->GetEnemy() == this ) && pHC->IsJumping() && gpGlobals->curtime - GetEnemies()->FirstTimeSeen(GetEnemy()) < 0.5 ) + { + SpeakIfAllowed( "TLK_SPOTTED_INCOMING_HEADCRAB" ); + } + else + { + // If we see a headcrab leaving a zombie that just died, mention it + // (Note that this is now a response context since some death types remove the zombie instantly) + int nContext = pHC->FindContextByName( "from_zombie" ); + if ( nContext > -1 && !ContextExpired( nContext ) ) // pHC->GetOwnerEntity() && ( pHC->GetOwnerEntity()->Classify() == CLASS_ZOMBIE ) && !pHC->GetOwnerEntity()->IsAlive() + { + SpeakIfAllowed( "TLK_SPOTTED_HEADCRAB_LEAVING_ZOMBIE" ); + } + } + } + } + else if ( nClass == CLASS_ZOMBIE ) + { + CNPC_BaseZombie *pZombie = dynamic_cast(GetEnemy()); + // If we see a zombie getting up, mention it + if ( pZombie && pZombie->IsGettingUp() ) + { + SpeakIfAllowed( "TLK_SPOTTED_ZOMBIE_WAKEUP" ); + } + } + + if ( gpGlobals->curtime - GetEnemies()->TimeAtFirstHand( GetEnemy() ) <= 1.0f && nClass != CLASS_BULLSEYE ) + { + // New concept which did not originate from Alyx, but is in the same category as the above concepts. + // This is meant to be used when a new enemy enters the arena while combat is already in progress. + // (Note that this can still trigger when combat begins, but unlike TLK_STARTCOMBAT, it has no delay + // between combat engagements.) + SpeakIfAllowed( TLK_NEW_ENEMY ); + } + } +#endif } //----------------------------------------------------------------------------- @@ -910,8 +961,21 @@ int CNPC_PlayerCompanion::SelectScheduleDanger() if ( pSound && (pSound->m_iType & SOUND_DANGER) ) { +#ifdef MAPBASE + if ( pSound->SoundChannel() == SOUNDENT_CHANNEL_ZOMBINE_GRENADE ) + { + SetSpeechTarget( pSound->m_hOwner ); + SpeakIfAllowed( TLK_DANGER_ZOMBINE_GRENADE ); + } + else if (!(pSound->SoundContext() & (SOUND_CONTEXT_MORTAR | SOUND_CONTEXT_FROM_SNIPER)) || IsOkToCombatSpeak()) + { + SetSpeechTarget( pSound->m_hOwner ); + SpeakIfAllowed( TLK_DANGER ); + } +#else if ( !(pSound->SoundContext() & (SOUND_CONTEXT_MORTAR|SOUND_CONTEXT_FROM_SNIPER)) || IsOkToCombatSpeak() ) SpeakIfAllowed( TLK_DANGER ); +#endif if ( HasCondition( COND_PC_SAFE_FROM_MORTAR ) ) { @@ -4309,6 +4373,20 @@ void CNPC_PlayerCompanion::Event_KilledOther( CBaseEntity *pVictim, const CTakeD } } +//----------------------------------------------------------------------------- +// Purpose: Called by enemy NPC's when they are ignited +// Input : pVictim - entity that was ignited +//----------------------------------------------------------------------------- +void CNPC_PlayerCompanion::EnemyIgnited( CAI_BaseNPC *pVictim ) +{ + BaseClass::EnemyIgnited( pVictim ); + + if ( FVisible( pVictim ) ) + { + SpeakIfAllowed( TLK_ENEMY_BURNING ); + } +} + //----------------------------------------------------------------------------- // Purpose: Handles custom combat speech stuff ported from Alyx. //----------------------------------------------------------------------------- @@ -4376,6 +4454,21 @@ void CNPC_PlayerCompanion::DoCustomCombatAI( void ) { SpeakIfAllowed( TLK_MANY_ENEMIES ); } + + // If we're not currently attacking or vulnerable, try speaking + else if ( gpGlobals->curtime - GetLastAttackTime() > 1.0f && (!HasCondition( COND_SEE_ENEMY ) || IsCurSchedule( SCHED_RELOAD ) || IsCurSchedule( SCHED_HIDE_AND_RELOAD )) ) + { + int chance = ( IsMoving() ) ? 20 : 3; + if ( ShouldSpeakRandom( TLK_COMBAT_IDLE, chance ) ) + { + AI_CriteriaSet modifiers; + + modifiers.AppendCriteria( "in_cover", HasMemory( bits_MEMORY_INCOVER ) ? "1" : "0" ); + modifiers.AppendCriteria( "lastseenenemy", UTIL_VarArgs( "%f", gpGlobals->curtime - GetEnemyLastTimeSeen() ) ); + + SpeakIfAllowed( TLK_COMBAT_IDLE, modifiers ); + } + } } #endif diff --git a/sp/src/game/server/hl2/npc_playercompanion.h b/sp/src/game/server/hl2/npc_playercompanion.h index 8dcf1aa1..e0f6769e 100644 --- a/sp/src/game/server/hl2/npc_playercompanion.h +++ b/sp/src/game/server/hl2/npc_playercompanion.h @@ -242,6 +242,7 @@ public: virtual void Event_Killed( const CTakeDamageInfo &info ); virtual void Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info ); + virtual void EnemyIgnited( CAI_BaseNPC *pVictim ); virtual void DoCustomCombatAI( void ); #endif diff --git a/sp/src/game/server/hl2/weapon_annabelle.cpp b/sp/src/game/server/hl2/weapon_annabelle.cpp index 6b7ecdd9..835fcc6a 100644 --- a/sp/src/game/server/hl2/weapon_annabelle.cpp +++ b/sp/src/game/server/hl2/weapon_annabelle.cpp @@ -121,6 +121,33 @@ acttable_t CWeaponAnnabelle::m_acttable[] = { ACT_RELOAD_LOW, ACT_RELOAD_ANNABELLE_LOW, false }, { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_ANNABELLE, false }, + // Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_AR2_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_AR2_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_AR2, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_AR2_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_AR2_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_AR2, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_AR2_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_AR2_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_RIFLE, false },//always aims + +// Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_AR2_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_AR2_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_AR2, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_AR2_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_AR2_STIMULATED, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_AR2, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_AR2_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_AR2_STIMULATED, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_RIFLE, false },//always aims +//End readiness activities + { ACT_ARM, ACT_ARM_RIFLE, true }, { ACT_DISARM, ACT_DISARM_RIFLE, true }, #else @@ -143,6 +170,13 @@ acttable_t CWeaponAnnabelle::m_acttable[] = { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SMG1, false }, #endif +#if EXPANDED_HL2_COVER_ACTIVITIES + { 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 }, @@ -161,6 +195,18 @@ acttable_t CWeaponAnnabelle::m_acttable[] = IMPLEMENT_ACTTABLE(CWeaponAnnabelle); +#ifdef MAPBASE +acttable_t* GetAnnabelleActtable() +{ + return CWeaponAnnabelle::m_acttable; +} + +int GetAnnabelleActtableCount() +{ + return ARRAYSIZE(CWeaponAnnabelle::m_acttable); +} +#endif // MAPBASE + void CWeaponAnnabelle::Precache( void ) { CBaseCombatWeapon::Precache(); diff --git a/sp/src/game/server/hl2/weapon_ar2.cpp b/sp/src/game/server/hl2/weapon_ar2.cpp index 6e7d4cac..deee7973 100644 --- a/sp/src/game/server/hl2/weapon_ar2.cpp +++ b/sp/src/game/server/hl2/weapon_ar2.cpp @@ -346,7 +346,7 @@ void CWeaponAR2::DelayedAttack( void ) pOwner->SnapEyeAngles( angles ); - pOwner->ViewPunch( QAngle( random->RandomInt( -8, -12 ), random->RandomInt( 1, 2 ), 0 ) ); + pOwner->ViewPunch( QAngle( random->RandomInt( -12, -8 ), random->RandomInt( 1, 2 ), 0 ) ); // Decrease ammo pOwner->RemoveAmmo( 1, m_iSecondaryAmmoType ); diff --git a/sp/src/game/server/hl2/weapon_crossbow.cpp b/sp/src/game/server/hl2/weapon_crossbow.cpp index b55fa441..f50a5c53 100644 --- a/sp/src/game/server/hl2/weapon_crossbow.cpp +++ b/sp/src/game/server/hl2/weapon_crossbow.cpp @@ -762,6 +762,16 @@ acttable_t CWeaponCrossbow::m_acttable[] = }; IMPLEMENT_ACTTABLE(CWeaponCrossbow); + +acttable_t* GetCrossbowActtable() +{ + return CWeaponCrossbow::m_acttable; +} + +int GetCrossbowActtableCount() +{ + return ARRAYSIZE(CWeaponCrossbow::m_acttable); +} #endif //----------------------------------------------------------------------------- diff --git a/sp/src/game/server/hl2/weapon_physcannon.cpp b/sp/src/game/server/hl2/weapon_physcannon.cpp index 2e985460..66991e45 100644 --- a/sp/src/game/server/hl2/weapon_physcannon.cpp +++ b/sp/src/game/server/hl2/weapon_physcannon.cpp @@ -68,6 +68,10 @@ ConVar player_throwforce( "player_throwforce", "1000" ); ConVar physcannon_dmg_glass( "physcannon_dmg_glass", "15" ); ConVar physcannon_right_turrets( "physcannon_right_turrets", "0" ); +#ifdef MAPBASE +ConVar sv_player_enable_propsprint("sv_player_enable_propsprint", "0", FCVAR_NONE, "If enabled, allows the player to sprint while holding a physics object" ); +ConVar sv_player_enable_gravgun_sprint("sv_player_enable_gravgun_sprint", "0", FCVAR_NONE, "Enables the player to sprint while holding a phys. object with the gravity gun" ); +#endif extern ConVar hl2_normspeed; extern ConVar hl2_walkspeed; @@ -1044,9 +1048,19 @@ void CPlayerPickupController::Init( CBasePlayer *pPlayer, CBaseEntity *pObject ) CHL2_Player *pOwner = (CHL2_Player *)ToBasePlayer( pPlayer ); if ( pOwner ) { +#ifndef MAPBASE pOwner->EnableSprint( false ); +#else + if ( sv_player_enable_propsprint.GetBool() == false ) + { + pOwner->EnableSprint( false ); + } + else + { + pOwner->EnableSprint( true ); + } +#endif } - // If the target is debris, convert it to non-debris if ( pObject->GetCollisionGroup() == COLLISION_GROUP_DEBRIS ) { @@ -1102,10 +1116,17 @@ void CPlayerPickupController::Shutdown( bool bThrown ) if ( m_pPlayer ) { CHL2_Player *pOwner = (CHL2_Player *)ToBasePlayer( m_pPlayer ); +#ifndef MAPBASE if ( pOwner ) { pOwner->EnableSprint( true ); } +#else + if ( pOwner && sv_player_enable_propsprint.GetBool() == false ) + { + pOwner->EnableSprint( true ); + } +#endif m_pPlayer->SetUseEntity( NULL ); if ( m_pPlayer->GetActiveWeapon() ) @@ -2497,6 +2518,7 @@ bool CWeaponPhysCannon::AttachObject( CBaseEntity *pObject, const Vector &vPosit // NVNT set the players constant force to simulate holding mass HapticSetConstantForce(pOwner,clamp(m_grabController.GetLoadWeight()*0.05,1,5)*Vector(0,-1,0)); #endif +#ifndef MAPBASE pOwner->EnableSprint( false ); float loadWeight = ( 1.0f - GetLoadPercentage() ); @@ -2504,6 +2526,22 @@ bool CWeaponPhysCannon::AttachObject( CBaseEntity *pObject, const Vector &vPosit //Msg( "Load perc: %f -- Movement speed: %f/%f\n", loadWeight, maxSpeed, hl2_normspeed.GetFloat() ); pOwner->SetMaxSpeed( maxSpeed ); +#else + if ( sv_player_enable_gravgun_sprint.GetBool() == false ) + { + pOwner->EnableSprint( false ); + + float loadWeight = ( 1.0f - GetLoadPercentage() ); + float maxSpeed = hl2_walkspeed.GetFloat() + ( ( hl2_normspeed.GetFloat() - hl2_walkspeed.GetFloat() ) * loadWeight ); + + //Msg( "Load perc: %f -- Movement speed: %f/%f\n", loadWeight, maxSpeed, hl2_normspeed.GetFloat() ); + pOwner->SetMaxSpeed( maxSpeed ); + } + else + { + pOwner->EnableSprint( true ); + } +#endif } // Don't drop again for a slight delay, in case they were pulling objects near them @@ -2950,9 +2988,17 @@ void CWeaponPhysCannon::DetachObject( bool playSound, bool wasLaunched ) CHL2_Player *pOwner = (CHL2_Player *)ToBasePlayer( GetOwner() ); if( pOwner != NULL ) { +#ifndef MAPBASE pOwner->EnableSprint( true ); pOwner->SetMaxSpeed( hl2_normspeed.GetFloat() ); +#else + if (sv_player_enable_gravgun_sprint.GetBool() == false) + { + pOwner->EnableSprint( true ); + pOwner->SetMaxSpeed( hl2_normspeed.GetFloat() ); + } +#endif if( wasLaunched ) { pOwner->RumbleEffect( RUMBLE_357, 0, RUMBLE_FLAG_RESTART ); diff --git a/sp/src/game/server/hl2/weapon_pistol.cpp b/sp/src/game/server/hl2/weapon_pistol.cpp index 9819f748..8a49f366 100644 --- a/sp/src/game/server/hl2/weapon_pistol.cpp +++ b/sp/src/game/server/hl2/weapon_pistol.cpp @@ -526,6 +526,10 @@ bool CWeaponPistol::Reload( void ) return fRet; } +#ifdef MAPBASE +ConVar weapon_pistol_upwards_viewkick( "weapon_pistol_upwards_viewkick", "0" ); +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -538,7 +542,11 @@ void CWeaponPistol::AddViewKick( void ) QAngle viewPunch; +#ifdef MAPBASE + viewPunch.x = weapon_pistol_upwards_viewkick.GetBool() ? random->RandomFloat( -0.5f, -0.25f ) : random->RandomFloat( 0.25f, 0.5f ); +#else viewPunch.x = random->RandomFloat( 0.25f, 0.5f ); +#endif viewPunch.y = random->RandomFloat( -.6f, .6f ); viewPunch.z = 0.0f; diff --git a/sp/src/game/server/logicentities.cpp b/sp/src/game/server/logicentities.cpp index aa838a35..2445f875 100644 --- a/sp/src/game/server/logicentities.cpp +++ b/sp/src/game/server/logicentities.cpp @@ -3112,7 +3112,7 @@ void CLogicBranch::UpdateOnRemove() CBaseEntity *pEntity = m_Listeners.Element( i ).Get(); if ( pEntity ) { - g_EventQueue.AddEvent( this, "_OnLogicBranchRemoved", 0, this, this ); + g_EventQueue.AddEvent( pEntity, "_OnLogicBranchRemoved", 0, this, this ); } } @@ -4059,7 +4059,7 @@ void CLogicFormat::FormatString(const char *szStringToFormat, char *szOutput, in curparam = atoi(szToken); if (curparam < MAX_LOGIC_FORMAT_PARAMETERS /*&& szParameters[curparam] != NULL*/) //if (curparam < MAX_FORMAT_PARAMETERS) { - Q_snprintf(szFormatted, sizeof(szFormatted), "%s%s", szFormatted, szParameters[curparam]); + Q_strncat(szFormatted, szParameters[curparam], sizeof(szFormatted)); } else { @@ -4068,8 +4068,8 @@ void CLogicFormat::FormatString(const char *szStringToFormat, char *szOutput, in // This might not be the best way to do this, but // reaching it is supposed to be the result of a mistake anyway. m_iszBackupParameter != NULL_STRING ? - Q_snprintf(szFormatted, sizeof(szFormatted), "%s%s", szFormatted, STRING(m_iszBackupParameter)) : - Q_snprintf(szFormatted, sizeof(szFormatted), "%s", szFormatted); + Q_strncat( szFormatted, STRING(m_iszBackupParameter), sizeof( szFormatted ) ) : + Q_strncat( szFormatted, "", sizeof( szFormatted ) ); } inparam = false; @@ -4077,7 +4077,7 @@ void CLogicFormat::FormatString(const char *szStringToFormat, char *szOutput, in } else { - Q_snprintf(szFormatted, sizeof(szFormatted), "%s%s", szFormatted, szToken); + Q_strncat( szFormatted, szToken, sizeof( szFormatted ) ); inparam = true; szToken = strtok(NULL, "}"); diff --git a/sp/src/game/server/mapbase/ai_grenade.h b/sp/src/game/server/mapbase/ai_grenade.h index 543969c2..cc5238f1 100644 --- a/sp/src/game/server/mapbase/ai_grenade.h +++ b/sp/src/game/server/mapbase/ai_grenade.h @@ -23,6 +23,7 @@ #include "hl2_gamerules.h" #include "weapon_physcannon.h" #include "globalstate.h" +#include "ai_hint.h" #define COMBINE_AE_GREN_TOSS ( 7 ) @@ -138,6 +139,8 @@ public: void ClearAttackConditions( void ); + bool FValidateHintType( CAI_Hint *pHint ); + Vector GetAltFireTarget() { return m_vecAltFireTarget; } virtual bool CanAltFireEnemy( bool bUseFreeKnowledge ); void DelayAltFireAttack( float flDelay ); @@ -558,6 +561,29 @@ bool CAI_GrenadeUser::CanThrowGrenade( const Vector &vecTarget ) } } + CHintCriteria hintCriteria; + hintCriteria.SetHintType( HINT_TACTICAL_GRENADE_THROW ); + hintCriteria.SetFlag( bits_HINT_NPC_IN_NODE_FOV ); + hintCriteria.SetGroup( this->GetHintGroup() ); + hintCriteria.AddIncludePosition( this->GetAbsOrigin(), 1024 ); + + if (this->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) + hintCriteria.SetFlag( bits_HINT_NODE_REPORT_FAILURES ); + + // If there's a grenade throw hint nearby, try using it + CAI_Hint *pHint = CAI_HintManager::FindHint( this, vecTarget, hintCriteria ); + if ( pHint ) + { + if ( CheckCanThrowGrenade( pHint->GetAbsOrigin() ) ) + { + return true; + } + else + { + DevMsg( this, "Unable to throw grenade at hint %s\n", pHint->GetDebugName() ); + } + } + return CheckCanThrowGrenade( vecTarget ); } @@ -636,6 +662,18 @@ void CAI_GrenadeUser::ClearAttackConditions() } } +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +template +bool CAI_GrenadeUser::FValidateHintType( CAI_Hint *pHint ) +{ + if ( pHint->HintType() == HINT_TACTICAL_GRENADE_THROW ) + return true; + + return BaseClass::FValidateHintType( pHint ); +} + //----------------------------------------------------------------------------- // Purpose: Drops grenades and alt-fire items on death. Based on code from npc_combines.cpp and npc_combine.cpp //----------------------------------------------------------------------------- diff --git a/sp/src/game/server/mapbase/custom_weapon_factory.cpp b/sp/src/game/server/mapbase/custom_weapon_factory.cpp new file mode 100644 index 00000000..2feaec16 --- /dev/null +++ b/sp/src/game/server/mapbase/custom_weapon_factory.cpp @@ -0,0 +1,208 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: The central manager of the custom weapons system. +// +// Author: Peter Covington (petercov@outlook.com) +// +//==================================================================================// + +#include "cbase.h" +#include "custom_weapon_factory.h" + +#define GENERIC_MANIFEST_FILE "scripts/mapbase_default_manifest.txt" +#define AUTOLOADED_MANIFEST_FILE UTIL_VarArgs("maps/%s_manifest.txt", STRING(gpGlobals->mapname)) +#define GLOBAL_WEAPONS_MANIFEST "scripts/custom_weapon_manifest.txt" + +extern ConVar mapbase_load_default_manifest; + +IMPLEMENT_PRIVATE_SYMBOLTYPE(CustomWeaponSymbol); + +CCustomWeaponSystem::CCustomWeaponSystem() : CAutoGameSystem("CustomWeaponFactorySystem") +{ +} + +void CCustomWeaponSystem::LevelInitPreEntity() +{ + LoadCustomWeaponsManifest(GLOBAL_WEAPONS_MANIFEST); + + // Check for a generic "mapname_manifest.txt" file and load it. + if (filesystem->FileExists(AUTOLOADED_MANIFEST_FILE, "GAME")) + { + AddManifestFile(AUTOLOADED_MANIFEST_FILE); + } + else + { + // Load the generic script instead. + ParseGenericManifest(); + } +} + +// Get a generic, hardcoded manifest with hardcoded names. +void CCustomWeaponSystem::ParseGenericManifest() +{ + if (!mapbase_load_default_manifest.GetBool()) + return; + + KeyValues* pKV = new KeyValues("DefaultManifest"); + pKV->LoadFromFile(filesystem, GENERIC_MANIFEST_FILE); + + AddManifestFile(pKV/*, true*/); + + pKV->deleteThis(); +} + +void CCustomWeaponSystem::AddManifestFile(const char* file) +{ + KeyValues* pKV = new KeyValues(file); + if (!pKV->LoadFromFile(filesystem, file)) + { + Warning("Mapbase Manifest: \"%s\" is unreadable or missing (can't load KV, check for syntax errors)\n", file); + pKV->deleteThis(); + return; + } + + CGMsg(1, CON_GROUP_MAPBASE_MISC, "===== Mapbase Manifest: Loading manifest file %s =====\n", file); + + AddManifestFile(pKV, false); + + CGMsg(1, CON_GROUP_MAPBASE_MISC, "==============================================================================\n"); + + pKV->deleteThis(); +} + +void CCustomWeaponSystem::AddManifestFile(KeyValues* pKV, bool bDontWarn) +{ + KeyValues* pKey = pKV->FindKey("weapons"); + + if (pKey) + { + char value[MAX_PATH]; + value[0] = '\0'; + + // Parse %mapname%, etc. + bool inparam = false; + CUtlStringList outStrings; + V_SplitString(pKey->GetString(), "%", outStrings); + for (int i = 0; i < outStrings.Count(); i++) + { + if (inparam) + { + if (FStrEq(outStrings[i], "mapname")) + { + Q_strncat(value, STRING(gpGlobals->mapname), sizeof(value)); + } + else if (FStrEq(outStrings[i], "language")) + { +#ifdef CLIENT_DLL + char uilanguage[64]; + engine->GetUILanguage(uilanguage, sizeof(uilanguage)); + Q_strncat(value, uilanguage, sizeof(value)); +#else + // Give up, use English + Q_strncat(value, "english", sizeof(value)); +#endif + } + } + else + { + Q_strncat(value, outStrings[i], sizeof(value)); + } + + inparam = !inparam; + } + + outStrings.PurgeAndDeleteElements(); + bDontWarn = pKV->GetBool("NoErrors", bDontWarn); + + LoadCustomWeaponsManifest(value, bDontWarn); + } +} + +#define Factory CustomWeaponsFactoryDictionary() +void CCustomWeaponSystem::LoadCustomWeaponsManifest(const char* file, bool bDontWarn) +{ + KeyValuesAD pKV("weapons_manifest"); + if (pKV->LoadFromFile(filesystem, file, "GAME")) + { + for (KeyValues *pkvWeapon = pKV->GetFirstValue(); pkvWeapon != nullptr; pkvWeapon = pkvWeapon->GetNextValue()) + { + const char* pszClassname = pkvWeapon->GetName(); + KeyValuesAD pkvWeaponScript("WeaponData"); + if (pkvWeaponScript->LoadFromFile(filesystem, pkvWeapon->GetString(), "GAME")) + { + const char* pszFactory = pkvWeaponScript->GetString("custom_factory", nullptr); + unsigned short FactoryIndex = Factory.Find(pszFactory); + if (Factory.IsValidIndex(FactoryIndex)) + { + auto* pFactory = Factory.Element(FactoryIndex); + const void* pData = pFactory->ParseDataFromWeaponFile(pkvWeaponScript); + if (!pData) + continue; + + unsigned short ClassIndex = m_ClassFactories.Find(pszClassname); + if (!m_ClassFactories.IsValidIndex(ClassIndex)) + { + ClassIndex = m_ClassFactories.Insert(pszClassname); + m_ClassFactories[ClassIndex].pOldFactory = EntityFactoryDictionary()->FindFactory(pszClassname); + } + else + { + Assert(m_ClassFactories[ClassIndex].pNewFactory); + Assert(m_ClassFactories[ClassIndex].pData); + + m_ClassFactories[ClassIndex].pNewFactory->ReleaseData(m_ClassFactories[ClassIndex].pData); + } + + m_ClassFactories[ClassIndex].sDataFile = pkvWeapon->GetString(); + m_ClassFactories[ClassIndex].pNewFactory = pFactory; + m_ClassFactories[ClassIndex].pData = pData; + EntityFactoryDictionary()->UninstallFactory(pszClassname); + EntityFactoryDictionary()->InstallFactory(m_ClassFactories[ClassIndex].pNewFactory, pszClassname); + } + } + } + } +} +#undef Factory + +void CCustomWeaponSystem::LevelShutdownPostEntity() +{ + for (unsigned short i = 0; i < m_ClassFactories.Count(); i++) + { + EntityFactoryDictionary()->UninstallFactory(m_ClassFactories.GetElementName(i)); + const CustomClassName_t& entry = m_ClassFactories.Element(i); + if (entry.pOldFactory) + EntityFactoryDictionary()->InstallFactory(entry.pOldFactory, m_ClassFactories.GetElementName(i)); + + Assert(entry.pData); + entry.pNewFactory->ReleaseData(entry.pData); + } + + m_ClassFactories.Purge(); + g_CustomWeaponSymbolSymbolTable.RemoveAll(); +} + +void CCustomWeaponSystem::ParseWeapon(CBaseCombatWeapon* pWeapon, const char* pClassName) +{ + ICustomWeapon* pCustom = dynamic_cast (pWeapon); + if (!pCustom) + return; + + unsigned short i = m_ClassFactories.Find(pClassName); + if (!m_ClassFactories.IsValidIndex(i)) + return; + + pCustom->InitCustomWeaponFromData(m_ClassFactories[i].pData, m_ClassFactories[i].sDataFile.String()); +} + +CUtlDict& CustomWeaponsFactoryDictionary() +{ + static CUtlDict dict; + return dict; +} + +static CCustomWeaponSystem g_CustomWeaponsSystem; +CCustomWeaponSystem* CustomWeaponSystem() +{ + return &g_CustomWeaponsSystem; +} diff --git a/sp/src/game/server/mapbase/custom_weapon_factory.h b/sp/src/game/server/mapbase/custom_weapon_factory.h new file mode 100644 index 00000000..3abb17ed --- /dev/null +++ b/sp/src/game/server/mapbase/custom_weapon_factory.h @@ -0,0 +1,117 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: See custom_weapon_factory.cpp +// +// Author: Peter Covington (petercov@outlook.com) +// +//==================================================================================// + +#ifndef CUSTOM_WEAPON_FACTORY_H +#define CUSTOM_WEAPON_FACTORY_H +#pragma once +#include "utldict.h" +#include "utlsymbol.h" + +DECLARE_PRIVATE_SYMBOLTYPE(CustomWeaponSymbol); + +class ICustomWeaponDataLoader : public IEntityFactory +{ +public: + virtual const void* ParseDataFromWeaponFile(KeyValues* pKV) const = 0; + virtual void ReleaseData(const void* pData) const = 0; +}; + +class ICustomWeapon +{ +public: + virtual void InitCustomWeaponFromData(const void* pData, const char *pszWeaponScript) = 0; +}; + +class CCustomWeaponSystem : public CAutoGameSystem +{ +public: + CCustomWeaponSystem(); + + // Level init, shutdown + virtual void LevelInitPreEntity(); + virtual void LevelShutdownPostEntity(); + + void ParseWeapon(CBaseCombatWeapon* pWeapon, const char* pClassName); + +private: + void ParseGenericManifest(); + void AddManifestFile(const char* file); + void AddManifestFile(KeyValues* pKV, bool bDontWarn = false); + void LoadCustomWeaponsManifest(const char* file, bool bDontWarn = false); + + typedef struct CustomClassName_s + { + CustomWeaponSymbol sDataFile; + ICustomWeaponDataLoader* pNewFactory; + IEntityFactory* pOldFactory; + const void* pData; + } CustomClassName_t; + CUtlDict m_ClassFactories; +}; + +CCustomWeaponSystem* CustomWeaponSystem(); + +CUtlDict< ICustomWeaponDataLoader*, unsigned short >& CustomWeaponsFactoryDictionary(); + +template +class CCustomWeaponEntityFactoryBase : public ICustomWeaponDataLoader +{ +public: + CCustomWeaponEntityFactoryBase(const char* pFactoryClass) + { + CustomWeaponsFactoryDictionary().Insert(pFactoryClass, this); + } + + IServerNetworkable* Create(const char* pClassName) + { + T* pEnt = _CreateEntityTemplate((T*)NULL, pClassName); + CustomWeaponSystem()->ParseWeapon(pEnt, pClassName); + return pEnt->NetworkProp(); + } + + void Destroy(IServerNetworkable* pNetworkable) + { + if (pNetworkable) + { + pNetworkable->Release(); + } + } + + virtual size_t GetEntitySize() + { + return sizeof(T); + } +}; + +template +class CDefaultCustomWeaponEntityFactory : public CCustomWeaponEntityFactoryBase +{ +public: + CDefaultCustomWeaponEntityFactory(const char *pFactoryClass) : CCustomWeaponEntityFactoryBase(pFactoryClass) + {} + + virtual const void* ParseDataFromWeaponFile(KeyValues* pKV) const + { + Data* pData = new Data; + if (pData && pData->Parse(pKV)) + return pData; + + delete pData; + return nullptr; + } + + virtual void ReleaseData(const void* pData) const + { + delete pData; + } +}; + +#define DEFINE_CUSTOM_WEAPON_FACTORY(factoryName, DLLClassName, DataStruct) \ + static CDefaultCustomWeaponEntityFactory custom_weapon_##factoryName##_factory( #factoryName ); + +#endif // !CUSTOM_WEAPON_FACTORY_H diff --git a/sp/src/game/server/mapbase/weapon_custom_hl2.cpp b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp new file mode 100644 index 00000000..7c8241bc --- /dev/null +++ b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp @@ -0,0 +1,1546 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Custom weapon classes for Half-Life 2 based weapons. +// +// Author: Peter Covington (petercov@outlook.com) +// +//==================================================================================// + +#include "cbase.h" +#include "custom_weapon_factory.h" +#include "basebludgeonweapon.h" +#include "ai_basenpc.h" +#include "player.h" +#include "npcevent.h" +#include "in_buttons.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// Acttables +extern acttable_t* GetSMG1Acttable(); +extern int GetSMG1ActtableCount(); +extern acttable_t* GetPistolActtable(); +extern int GetPistolActtableCount(); +extern acttable_t* GetShotgunActtable(); +extern int GetShotgunActtableCount(); +extern acttable_t* Get357Acttable(); +extern int Get357ActtableCount(); +extern acttable_t* GetAR2Acttable(); +extern int GetAR2ActtableCount(); +extern acttable_t* GetCrossbowActtable(); +extern int GetCrossbowActtableCount(); +extern acttable_t* GetAnnabelleActtable(); +extern int GetAnnabelleActtableCount(); + + + +const char* g_ppszDamageClasses[] = { + "BLUNT", + "SLASH", + "STUN", + "BURN", +}; + +int g_nDamageClassTypeBits[ARRAYSIZE(g_ppszDamageClasses)] = { + DMG_CLUB, + DMG_SLASH, + DMG_CLUB|DMG_SHOCK, + DMG_CLUB|DMG_BURN, +}; + +typedef struct HL2CustomMeleeData_s +{ + float m_flMeleeRange; + float m_flRefireRate; + float m_flDamage; + float m_flNPCDamage; + float m_flHitDelay; + byte m_nDamageClass; + bool m_bHitUsesMissAnim; + + bool Parse(KeyValues*); +} HL2CustomMeleeData_t; + +class CHLCustomWeaponMelee : public CBaseHLBludgeonWeapon, public ICustomWeapon +{ +public: + DECLARE_CLASS(CHLCustomWeaponMelee, CBaseHLBludgeonWeapon); + + DECLARE_SERVERCLASS(); + DECLARE_ACTTABLE(); + + CHLCustomWeaponMelee(); + + float GetRange(void) { return m_CustomData.m_flMeleeRange; } + float GetFireRate(void) { return m_CustomData.m_flRefireRate; } + float GetHitDelay() { return m_CustomData.m_flHitDelay; } + + void AddViewKick(void); + float GetDamageForActivity(Activity hitActivity); + + virtual int WeaponMeleeAttack1Condition(float flDot, float flDist); + void SecondaryAttack(void) { return; } + + // Animation event + virtual void Operator_HandleAnimEvent(animevent_t* pEvent, CBaseCombatCharacter* pOperator); + + // Don't use backup activities + acttable_t* GetBackupActivityList() { return NULL; } + int GetBackupActivityListCount() { return 0; } + + //Functions to select animation sequences + virtual Activity GetPrimaryAttackActivity(void) { return m_CustomData.m_bHitUsesMissAnim ? ACT_VM_MISSCENTER : BaseClass::GetPrimaryAttackActivity(); } + + const char* GetWeaponScriptName() { return m_iszWeaponScriptName.Get(); } + virtual int GetDamageType() { return g_nDamageClassTypeBits[m_CustomData.m_nDamageClass]; } + + virtual void InitCustomWeaponFromData(const void* pData, const char* pszWeaponScript); + +private: + // Animation event handlers + void HandleAnimEventMeleeHit(animevent_t* pEvent, CBaseCombatCharacter* pOperator); + +private: + HL2CustomMeleeData_t m_CustomData; + + CNetworkString(m_iszWeaponScriptName, 128); +}; + +IMPLEMENT_SERVERCLASS_ST(CHLCustomWeaponMelee, DT_HLCustomWeaponMelee) +SendPropString(SENDINFO(m_iszWeaponScriptName)), +END_SEND_TABLE(); + +DEFINE_CUSTOM_WEAPON_FACTORY(hl2_melee, CHLCustomWeaponMelee, HL2CustomMeleeData_t); + +bool HL2CustomMeleeData_s::Parse(KeyValues* pKVWeapon) +{ + KeyValues* pkvData = pKVWeapon->FindKey("CustomData"); + if (pkvData) + { + m_flDamage = pkvData->GetFloat("damage"); + m_flNPCDamage = pkvData->GetFloat("damage_npc", m_flDamage); + m_flMeleeRange = pkvData->GetFloat("range", 70.f); + m_flRefireRate = pkvData->GetFloat("rate", 0.7f); + m_flHitDelay = pkvData->GetFloat("hitdelay"); + m_bHitUsesMissAnim = pkvData->GetBool("hit_uses_miss_anim"); + + const char* pszDamageClass = pkvData->GetString("damage_type", nullptr); + if (pszDamageClass) + { + for (byte i = 0; i < ARRAYSIZE(g_ppszDamageClasses); i++) + { + if (V_stricmp(pszDamageClass, g_ppszDamageClasses[i]) == 0) + { + m_nDamageClass = i; + break; + } + } + } + return true; + } + + return false; +} + +void CHLCustomWeaponMelee::InitCustomWeaponFromData(const void* pData, const char* pszWeaponScript) +{ + Q_FileBase(pszWeaponScript, m_iszWeaponScriptName.GetForModify(), 128); + V_memcpy(&m_CustomData, pData, sizeof(HL2CustomMeleeData_t)); +} + +acttable_t CHLCustomWeaponMelee::m_acttable[] = +{ + { ACT_MELEE_ATTACK1, ACT_MELEE_ATTACK_SWING, true }, + { ACT_GESTURE_MELEE_ATTACK1, ACT_GESTURE_MELEE_ATTACK_SWING, false}, + + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_MELEE, false }, +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_IDLE, ACT_IDLE_MELEE, false }, + { ACT_RUN, ACT_RUN_MELEE, false }, + { ACT_WALK, ACT_WALK_MELEE, false }, + + { ACT_ARM, ACT_ARM_MELEE, false }, + { ACT_DISARM, ACT_DISARM_MELEE, false }, + + // Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_MELEE, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_MELEE, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_MELEE, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_MELEE, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_MELEE, false }, + { ACT_WALK_AGITATED, ACT_WALK_MELEE, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_MELEE, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_MELEE, false }, + { ACT_RUN_AGITATED, ACT_RUN_MELEE, false },//always aims + + // Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_MELEE, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_ANGRY_MELEE, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_MELEE, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_MELEE, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_MELEE, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_MELEE, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_MELEE, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_MELEE, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_MELEE, false },//always aims + //End readiness activities +#else + { ACT_IDLE, ACT_IDLE_ANGRY_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(CHLCustomWeaponMelee); + +CHLCustomWeaponMelee::CHLCustomWeaponMelee() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Get the damage amount for the animation we're doing +// Input : hitActivity - currently played activity +// Output : Damage amount +//----------------------------------------------------------------------------- +float CHLCustomWeaponMelee::GetDamageForActivity(Activity hitActivity) +{ + if ((GetOwner() != NULL) && (GetOwner()->IsPlayer())) + return m_CustomData.m_flDamage; + + return m_CustomData.m_flNPCDamage; +} + +//----------------------------------------------------------------------------- +// Purpose: Add in a view kick for this weapon +//----------------------------------------------------------------------------- +void CHLCustomWeaponMelee::AddViewKick(void) +{ + CBasePlayer* pPlayer = ToBasePlayer(GetOwner()); + + if (pPlayer == NULL) + return; + + QAngle punchAng; + + punchAng.x = random->RandomFloat(1.0f, 2.0f); + punchAng.y = random->RandomFloat(-2.0f, -1.0f); + punchAng.z = 0.0f; + + pPlayer->ViewPunch(punchAng); +} + + +//----------------------------------------------------------------------------- +// Attempt to lead the target (needed because citizens can't hit manhacks with the crowbar!) +//----------------------------------------------------------------------------- +extern ConVar sk_crowbar_lead_time; + +int CHLCustomWeaponMelee::WeaponMeleeAttack1Condition(float flDot, float flDist) +{ + // Attempt to lead the target (needed because citizens can't hit manhacks with the crowbar!) + CAI_BaseNPC* pNPC = GetOwner()->MyNPCPointer(); + CBaseEntity* pEnemy = pNPC->GetEnemy(); + if (!pEnemy) + return COND_NONE; + + Vector vecVelocity; + vecVelocity = pEnemy->GetSmoothedVelocity(); + + // Project where the enemy will be in a little while + float dt = sk_crowbar_lead_time.GetFloat(); + dt += random->RandomFloat(-0.3f, 0.2f); + if (dt < 0.0f) + dt = 0.0f; + + Vector vecExtrapolatedPos; + VectorMA(pEnemy->WorldSpaceCenter(), dt, vecVelocity, vecExtrapolatedPos); + + Vector vecDelta; + VectorSubtract(vecExtrapolatedPos, pNPC->WorldSpaceCenter(), vecDelta); + + if (fabs(vecDelta.z) > 70) + { + return COND_TOO_FAR_TO_ATTACK; + } + + Vector vecForward = pNPC->BodyDirection2D(); + vecDelta.z = 0.0f; + float flExtrapolatedDist = Vector2DNormalize(vecDelta.AsVector2D()); + if ((flDist > 64) && (flExtrapolatedDist > 64)) + { + return COND_TOO_FAR_TO_ATTACK; + } + + float flExtrapolatedDot = DotProduct2D(vecDelta.AsVector2D(), vecForward.AsVector2D()); + if ((flDot < 0.7) && (flExtrapolatedDot < 0.7)) + { + return COND_NOT_FACING_ATTACK; + } + + return COND_CAN_MELEE_ATTACK1; +} + + +//----------------------------------------------------------------------------- +// Animation event handlers +//----------------------------------------------------------------------------- +void CHLCustomWeaponMelee::HandleAnimEventMeleeHit(animevent_t* pEvent, CBaseCombatCharacter* pOperator) +{ + // Trace up or down based on where the enemy is... + // But only if we're basically facing that direction + Vector vecDirection; + AngleVectors(GetAbsAngles(), &vecDirection); + + CBaseEntity* pEnemy = pOperator->MyNPCPointer() ? pOperator->MyNPCPointer()->GetEnemy() : NULL; + if (pEnemy) + { + Vector vecDelta; + VectorSubtract(pEnemy->WorldSpaceCenter(), pOperator->Weapon_ShootPosition(), vecDelta); + VectorNormalize(vecDelta); + + Vector2D vecDelta2D = vecDelta.AsVector2D(); + Vector2DNormalize(vecDelta2D); + if (DotProduct2D(vecDelta2D, vecDirection.AsVector2D()) > 0.8f) + { + vecDirection = vecDelta; + } + } + + Vector vecEnd; + VectorMA(pOperator->Weapon_ShootPosition(), 50, vecDirection, vecEnd); + CBaseEntity* pHurt = pOperator->CheckTraceHullAttack(pOperator->Weapon_ShootPosition(), vecEnd, + Vector(-16, -16, -16), Vector(36, 36, 36), m_CustomData.m_flNPCDamage, GetDamageType(), 0.75); + + // did I hit someone? + if (pHurt) + { + // play sound + WeaponSound(MELEE_HIT); + + // Fake a trace impact, so the effects work out like a player's crowbaw + trace_t traceHit; + UTIL_TraceLine(pOperator->Weapon_ShootPosition(), pHurt->GetAbsOrigin(), MASK_SHOT_HULL, pOperator, COLLISION_GROUP_NONE, &traceHit); + ImpactEffect(traceHit); + } + else + { + WeaponSound(MELEE_MISS); + } +} + + +//----------------------------------------------------------------------------- +// Animation event +//----------------------------------------------------------------------------- +void CHLCustomWeaponMelee::Operator_HandleAnimEvent(animevent_t* pEvent, CBaseCombatCharacter* pOperator) +{ + switch (pEvent->event) + { + case EVENT_WEAPON_MELEE_HIT: + HandleAnimEventMeleeHit(pEvent, pOperator); + break; + + default: + BaseClass::Operator_HandleAnimEvent(pEvent, pOperator); + break; + } +} + +//-------------------------------------------------------------------------- +// +// Custom ranged weapon +// +//-------------------------------------------------------------------------- + +class CHLCustomWeaponGun : public CBaseHLCombatWeapon, public ICustomWeapon +{ +public: + DECLARE_CLASS(CHLCustomWeaponGun, CBaseHLCombatWeapon); + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + + CHLCustomWeaponGun(); + virtual void InitCustomWeaponFromData(const void* pData, const char* pszWeaponScript); + const char* GetWeaponScriptName() { return m_iszWeaponScriptName.Get(); } + + // Weapon behaviour + virtual void ItemPostFrame(void); // called each frame by the player PostThink + virtual void ItemBusyFrame(void); // called each frame by the player PostThink, if the player's not ready to attack yet + virtual bool ReloadOrSwitchWeapons(void); + virtual bool Holster(CBaseCombatWeapon* pSwitchingTo = NULL); + + // Bullet launch information + virtual const Vector& GetBulletSpread(void); + virtual float GetFireRate(void) { return m_CustomData.m_flFireRate; } + virtual int GetMinBurst() { return m_CustomData.m_nMinBurst; } + virtual int GetMaxBurst() { return m_CustomData.m_nMaxBurst; } + virtual float GetMinRestTime() { return m_CustomData.m_RestInterval.start; } + virtual float GetMaxRestTime() { return m_CustomData.m_RestInterval.start + m_CustomData.m_RestInterval.range; } + + // Autoaim + virtual float GetMaxAutoAimDeflection() { return 0.99f; } + virtual float WeaponAutoAimScale() { return m_CustomData.m_flAutoAimScale; } // allows a weapon to influence the perceived size of the target's autoaim radius. + + virtual void AddViewKick(void); + int WeaponSoundRealtime(WeaponSound_t shoot_type); + + bool StartReload(void); + bool Reload(void); + void FillClip(void); + void FinishReload(void); + void Pump(void); + + void PrimaryAttack(); + + void FireNPCPrimaryAttack(CBaseCombatCharacter* pOperator, bool bUseWeaponAngles); + void FireNPCSecondaryAttack(CBaseCombatCharacter* pOperator, bool bUseWeaponAngles); + void Operator_ForceNPCFire(CBaseCombatCharacter* pOperator, bool bSecondary); + void Operator_HandleAnimEvent(animevent_t* pEvent, CBaseCombatCharacter* pOperator); + int CapabilitiesGet(void) { return bits_CAP_WEAPON_RANGE_ATTACK1; } + + Activity GetPrimaryAttackActivity(void); + + virtual acttable_t* ActivityList(void); + virtual int ActivityListCount(void); + + virtual acttable_t* GetBackupActivityList(); + virtual int GetBackupActivityListCount(); +private: + void CheckZoomToggle(void); + void ToggleZoom(void); + +public: + typedef struct Data_s + { + float m_flFireRate; + int m_nMinBurst; + int m_nMaxBurst; + interval_t m_RestInterval; + + float m_flAutoAimScale; + + Vector m_vPlayerSpread; + Vector m_vAllySpread; + Vector m_vNPCSpread; + int m_nBulletsPerShot; // For shotguns + + // Viewkick + float m_flMaxVerticalKick; + float m_flSlideLimit; + interval_t m_VerticalPunchRange; + + int m_nActTableIndex; + + bool m_bUseRecoilAnims; + bool m_bFullAuto; // True for machine gun, false for semi-auto + bool m_bNextAttackFromSequence; + bool m_bUsePumpAnimation; + bool m_bHasSecondaryFire; + bool m_bHasZoom; + bool m_bZoomDuringReload; + } Data_t; + + struct Cache_s : public Data_s + { + bool m_bFiresUnderwater; // true if this weapon can fire underwater + bool m_bAltFiresUnderwater; // true if this weapon can fire underwater + float m_fMinRange1; // What's the closest this weapon can be used? + float m_fMinRange2; // What's the closest this weapon can be used? + float m_fMaxRange1; // What's the furthest this weapon can be used? + float m_fMaxRange2; // What's the furthest this weapon can be used? + bool m_bReloadsSingly; // True if this weapon reloads 1 round at a time + + bool Parse(KeyValues*); + }; + +private: + CNetworkString(m_iszWeaponScriptName, 128); + + Data_t m_CustomData; + + bool m_bNeedPump; // When emptied completely + bool m_bDelayedFire1; // Fire primary when finished reloading + bool m_bDelayedFire2; // Fire secondary when finished reloading + bool m_bInZoom; + bool m_bMustReload; + + int m_nShotsFired; // Number of consecutive shots fired + float m_flNextSoundTime; // real-time clock of when to make next sound +public: + enum WeaponActTable_e + { + ACTTABLE_SMG1 = 0, + ACTTABLE_PISTOL, + ACTTABLE_REVOLVER, + ACTTABLE_SHOTGUN, + ACTTABLE_AR2, + ACTTABLE_CROSSBOW, + ACTTABLE_ANNABELLE, + + NUM_GUN_ACT_TABLES + }; +}; + +IMPLEMENT_SERVERCLASS_ST(CHLCustomWeaponGun, DT_HLCustomWeaponGun) +SendPropString(SENDINFO(m_iszWeaponScriptName)), +END_SEND_TABLE(); + +BEGIN_DATADESC(CHLCustomWeaponGun) +DEFINE_FIELD(m_nShotsFired, FIELD_INTEGER), +DEFINE_FIELD(m_flNextSoundTime, FIELD_TIME), +DEFINE_FIELD(m_bNeedPump, FIELD_BOOLEAN), +DEFINE_FIELD(m_bDelayedFire1, FIELD_BOOLEAN), +DEFINE_FIELD(m_bDelayedFire2, FIELD_BOOLEAN), +DEFINE_FIELD(m_bInZoom, FIELD_BOOLEAN), +DEFINE_FIELD(m_bMustReload, FIELD_BOOLEAN), +END_DATADESC(); + +DEFINE_CUSTOM_WEAPON_FACTORY(hl2_gun, CHLCustomWeaponGun, CHLCustomWeaponGun::Cache_s); + +CHLCustomWeaponGun::CHLCustomWeaponGun() +{ + m_bNeedPump = false; + m_bDelayedFire1 = false; + m_bDelayedFire2 = false; + m_bInZoom = false; + m_bMustReload = false; + m_nShotsFired = 0; +} + +acttable_t* CHLCustomWeaponGun::ActivityList(void) +{ + switch (m_CustomData.m_nActTableIndex) + { + default: + case ACTTABLE_SMG1: + return GetSMG1Acttable(); + break; + case ACTTABLE_PISTOL: + return GetPistolActtable(); + break; + case ACTTABLE_REVOLVER: + return Get357Acttable(); + break; + case ACTTABLE_SHOTGUN: + return GetShotgunActtable(); + break; + case ACTTABLE_AR2: + return GetAR2Acttable(); + break; + case ACTTABLE_CROSSBOW: + return GetCrossbowActtable(); + break; + case ACTTABLE_ANNABELLE: + return GetAnnabelleActtable(); + break; + } +} + +int CHLCustomWeaponGun::ActivityListCount(void) +{ + switch (m_CustomData.m_nActTableIndex) + { + default: + case ACTTABLE_SMG1: + return GetSMG1ActtableCount(); + break; + case ACTTABLE_PISTOL: + return GetPistolActtableCount(); + break; + case ACTTABLE_REVOLVER: + return Get357ActtableCount(); + break; + case ACTTABLE_SHOTGUN: + return GetShotgunActtableCount(); + break; + case ACTTABLE_AR2: + return GetAR2ActtableCount(); + break; + case ACTTABLE_CROSSBOW: + return GetCrossbowActtableCount(); + break; + case ACTTABLE_ANNABELLE: + return GetAnnabelleActtableCount(); + break; + } +} + +acttable_t* CHLCustomWeaponGun::GetBackupActivityList(void) +{ + switch (m_CustomData.m_nActTableIndex) + { + default: + case ACTTABLE_SMG1: + case ACTTABLE_CROSSBOW: + case ACTTABLE_AR2: + return GetSMG1Acttable(); + break; + case ACTTABLE_PISTOL: + case ACTTABLE_REVOLVER: + return GetPistolActtable(); + break; + case ACTTABLE_SHOTGUN: + case ACTTABLE_ANNABELLE: + return GetShotgunActtable(); + break; + } +} + +int CHLCustomWeaponGun::GetBackupActivityListCount(void) +{ + switch (m_CustomData.m_nActTableIndex) + { + default: + case ACTTABLE_SMG1: + case ACTTABLE_CROSSBOW: + case ACTTABLE_AR2: + return GetSMG1ActtableCount(); + break; + case ACTTABLE_PISTOL: + case ACTTABLE_REVOLVER: + return GetPistolActtableCount(); + break; + case ACTTABLE_SHOTGUN: + case ACTTABLE_ANNABELLE: + return GetShotgunActtableCount(); + break; + } +} + +void ReadIntervalInt(const char* pString, int &iMin, int &iMax) +{ + char tempString[128]; + Q_strncpy(tempString, pString, sizeof(tempString)); + + char* token = strtok(tempString, ","); + if (token) + { + iMin = atoi(token); + token = strtok(NULL, ","); + if (token) + { + iMax = atoi(token); + } + else + { + iMax = iMin; + } + } +} + +bool CHLCustomWeaponGun::Cache_s::Parse(KeyValues* pKVWeapon) +{ + static const char* ppszCustomGunAnimTypes[NUM_GUN_ACT_TABLES] = { + "smg", + "pistol", + "revolver", + "shotgun", + "ar2", + "crossbow", + "annabelle", + }; + + KeyValues* pkvData = pKVWeapon->FindKey("CustomData"); + if (pkvData) + { + m_flFireRate = pkvData->GetFloat("fire_rate", 0.5f); + ReadIntervalInt(pkvData->GetString("npc_burst", "1"), m_nMinBurst, m_nMaxBurst); + m_RestInterval = ReadInterval(pkvData->GetString("npc_rest_time", "0.3,0.6")); + m_flAutoAimScale = pkvData->GetFloat("autoaim_scale", 1.f); + m_bFullAuto = pkvData->GetBool("auto_fire"); + m_nBulletsPerShot = pkvData->GetInt("bullets", 1); + m_bUseRecoilAnims = pkvData->GetBool("recoil_anims", true); + m_bReloadsSingly = pkvData->GetBool("reload_singly"); + m_bFiresUnderwater = pkvData->GetBool("fires_underwater"); + m_bHasZoom = pkvData->GetBool("zoom_enable"); + m_bZoomDuringReload = m_bHasZoom && pkvData->GetBool("zoom_in_reload"); + + m_fMinRange1 = pkvData->GetFloat("range1_min", 65.f); + m_fMinRange2 = pkvData->GetFloat("range2_min", 65.f); + m_fMaxRange1 = pkvData->GetFloat("range1_max", 1024.f); + m_fMaxRange2 = pkvData->GetFloat("range2_max", 1024.f); + + if (m_bFullAuto) + { + m_flMaxVerticalKick = pkvData->GetFloat("viewkick_vertical_max", 1.f); + m_flSlideLimit = pkvData->GetFloat("viewkick_slide_limit", 2.f); + } + else + { + m_flSlideLimit = pkvData->GetFloat("viewpunch_side_max", .6f); + m_VerticalPunchRange = ReadInterval(pkvData->GetString("viewpunch_vertical", "0.25,0.5")); + + m_bNextAttackFromSequence = pkvData->GetBool("next_attack_time_from_sequence"); + m_bUsePumpAnimation = pkvData->GetBool("use_pump_anim"); + } + + // NOTE: The way these are calculated is that each component == sin (degrees/2) + float flSpread = pkvData->GetFloat("spread", 5.f); + float flNPCSpread = pkvData->GetFloat("spread_npc", flSpread); + float flAllySperad = pkvData->GetFloat("spread_ally", flNPCSpread); + m_vPlayerSpread = Vector(sin(DEG2RAD(flSpread * 0.5f))); + m_vNPCSpread = Vector(sin(DEG2RAD(flNPCSpread * 0.5f))); + m_vAllySpread = Vector(sin(DEG2RAD(flAllySperad * 0.5f))); + + const char* pszAnimType = pkvData->GetString("anim_type", nullptr); + if (pszAnimType) + { + for (int i = 0; i < NUM_GUN_ACT_TABLES; i++) + { + if (V_stricmp(pszAnimType, ppszCustomGunAnimTypes[i]) == 0) + { + m_nActTableIndex = i; + break; + } + } + } + + return true; + } + + return false; +} + +void CHLCustomWeaponGun::InitCustomWeaponFromData(const void* pData, const char* pszWeaponScript) +{ + Q_FileBase(pszWeaponScript, m_iszWeaponScriptName.GetForModify(), 128); + const auto* pCache = static_cast (pData); + m_CustomData = *pCache; + m_bFiresUnderwater = pCache->m_bFiresUnderwater; + m_bAltFiresUnderwater = pCache->m_bAltFiresUnderwater; + m_fMinRange1 = pCache->m_fMinRange1; + m_fMinRange2 = pCache->m_fMinRange2; + m_fMaxRange1 = pCache->m_fMaxRange1; + m_fMaxRange2 = pCache->m_fMaxRange2; + m_bReloadsSingly = pCache->m_bReloadsSingly; +} + +const Vector& CHLCustomWeaponGun::GetBulletSpread() +{ + if (!GetOwner() || !GetOwner()->IsNPC()) + return m_CustomData.m_vPlayerSpread; + + if (GetOwner()->MyNPCPointer()->IsPlayerAlly()) + { + // 357 allies should be cooler + return m_CustomData.m_vAllySpread; + } + + return m_CustomData.m_vNPCSpread; +} + +void CHLCustomWeaponGun::AddViewKick(void) +{ + //Get the view kick + CBasePlayer* pPlayer = ToBasePlayer(GetOwner()); + + if (!pPlayer) + return; + + if (m_CustomData.m_bFullAuto) + { + float flDuration = m_fFireDuration; + + if (g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE) + { + // On the 360 (or in any configuration using the 360 aiming scheme), don't let the + // AR2 progressive into the late, highly inaccurate stages of its kick. Just + // spoof the time to make it look (to the kicking code) like we haven't been + // firing for very long. + flDuration = MIN(flDuration, 0.75f); + } + + CHLMachineGun::DoMachineGunKick(pPlayer, 0.5f, m_CustomData.m_flMaxVerticalKick, flDuration, m_CustomData.m_flSlideLimit); + } + else + { + QAngle viewPunch; + viewPunch.x = RandomInterval(m_CustomData.m_VerticalPunchRange); + viewPunch.y = RandomFloat(-m_CustomData.m_flSlideLimit, m_CustomData.m_flSlideLimit); + viewPunch.z = 0.0f; + + //Add it to the view punch + pPlayer->ViewPunch(viewPunch); + } +} + +bool CHLCustomWeaponGun::Holster(CBaseCombatWeapon* pSwitchingTo) +{ + // Stop zooming + if (m_bInZoom) + { + ToggleZoom(); + } + + return BaseClass::Holster(pSwitchingTo); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::CheckZoomToggle(void) +{ + if (!m_CustomData.m_bHasZoom) + return; + + CBasePlayer* pPlayer = ToBasePlayer(GetOwner()); + + int iButtonsTest = IN_ATTACK3; + if (!m_CustomData.m_bHasSecondaryFire) + iButtonsTest |= IN_ATTACK2; + + if (pPlayer->m_afButtonPressed & iButtonsTest) + { + ToggleZoom(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::ToggleZoom(void) +{ + CBasePlayer* pPlayer = ToBasePlayer(GetOwner()); + + if (pPlayer == NULL) + return; + + if (m_bInZoom) + { + if (pPlayer->SetFOV(this, 0, 0.2f)) + { + m_bInZoom = false; + } + } + else + { + if (pPlayer->SetFOV(this, 20, 0.1f)) + { + m_bInZoom = true; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Override so only reload one shell at a time +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CHLCustomWeaponGun::StartReload(void) +{ + CBaseCombatCharacter* pOwner = GetOwner(); + + if (pOwner == NULL) + return false; + + if (pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0) + return false; + + if (m_iClip1 >= GetMaxClip1()) + return false; + + // If shotgun totally emptied then a pump animation is needed + + //NOTENOTE: This is kinda lame because the player doesn't get strong feedback on when the reload has finished, + // without the pump. Technically, it's incorrect, but it's good for feedback... + + if (m_CustomData.m_bUsePumpAnimation && m_iClip1 <= 0) + { + m_bNeedPump = true; + } + + int j = MIN(1, pOwner->GetAmmoCount(m_iPrimaryAmmoType)); + + if (j <= 0) + return false; + + SendWeaponAnim(ACT_SHOTGUN_RELOAD_START); + + // Make shotgun shell visible + SetBodygroup(1, 0); + + pOwner->m_flNextAttack = gpGlobals->curtime; + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); + +#ifdef MAPBASE + if (pOwner->IsPlayer()) + { + static_cast(pOwner)->SetAnimation(PLAYER_RELOAD); + } +#endif + + if (m_bInZoom && !m_CustomData.m_bZoomDuringReload) + { + ToggleZoom(); + } + + m_bInReload = true; + m_bMustReload = false; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Override so only reload one shell at a time +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CHLCustomWeaponGun::Reload(void) +{ + if (m_bReloadsSingly) + { + // Check that StartReload was called first + if (!m_bInReload) + { + Warning("ERROR: Shotgun Reload called incorrectly!\n"); + } + + CBaseCombatCharacter* pOwner = GetOwner(); + + if (pOwner == NULL) + return false; + + if (pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0) + return false; + + if (m_iClip1 >= GetMaxClip1()) + return false; + + int j = MIN(1, pOwner->GetAmmoCount(m_iPrimaryAmmoType)); + + if (j <= 0) + return false; + + FillClip(); + // Play reload on different channel as otherwise steals channel away from fire sound + WeaponSound(RELOAD); + SendWeaponAnim(ACT_VM_RELOAD); + + pOwner->m_flNextAttack = gpGlobals->curtime; + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); + + return true; + } + else if (BaseClass::Reload()) + { + if (m_bInZoom && !m_CustomData.m_bZoomDuringReload) + { + ToggleZoom(); + } + + m_bMustReload = false; + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Play finish reload anim and fill clip +// Input : +// Output : +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::FinishReload(void) +{ + if (m_bReloadsSingly) + { + // Make shotgun shell invisible + SetBodygroup(1, 1); + + CBaseCombatCharacter* pOwner = GetOwner(); + + if (pOwner == NULL) + return; + + m_bInReload = false; + + // Finish reload animation + SendWeaponAnim(ACT_SHOTGUN_RELOAD_FINISH); + + pOwner->m_flNextAttack = gpGlobals->curtime; + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); + } + else + { + BaseClass::FinishReload(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Play finish reload anim and fill clip +// Input : +// Output : +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::FillClip(void) +{ + CBaseCombatCharacter* pOwner = GetOwner(); + + if (pOwner == NULL) + return; + + // Add them to the clip + if (pOwner->GetAmmoCount(m_iPrimaryAmmoType) > 0) + { + if (Clip1() < GetMaxClip1()) + { + m_iClip1++; + pOwner->RemoveAmmo(1, m_iPrimaryAmmoType); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Play weapon pump anim +// Input : +// Output : +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::Pump(void) +{ + CBaseCombatCharacter* pOwner = GetOwner(); + + if (pOwner == NULL) + return; + + m_bNeedPump = false; + + WeaponSound(SPECIAL1); + + // Finish reload animation + SendWeaponAnim(ACT_SHOTGUN_PUMP); + + pOwner->m_flNextAttack = gpGlobals->curtime + SequenceDuration(); + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); +} + +//----------------------------------------------------------------------------- +// Purpose: If the current weapon has more ammo, reload it. Otherwise, switch +// to the next best weapon we've got. Returns true if it took any action. +//----------------------------------------------------------------------------- +bool CHLCustomWeaponGun::ReloadOrSwitchWeapons(void) +{ + CBasePlayer* pOwner = ToBasePlayer(GetOwner()); + Assert(pOwner); + + m_bFireOnEmpty = false; + + // If we don't have any ammo, switch to the next best weapon + if (!HasAnyAmmo() && m_flNextPrimaryAttack < gpGlobals->curtime && m_flNextSecondaryAttack < gpGlobals->curtime) + { + // weapon isn't useable, switch. + // Ammo might be overridden to 0, in which case we shouldn't do this + if (((GetWeaponFlags() & ITEM_FLAG_NOAUTOSWITCHEMPTY) == false) && !HasSpawnFlags(SF_WEAPON_NO_AUTO_SWITCH_WHEN_EMPTY) && (g_pGameRules->SwitchToNextBestWeapon(pOwner, this))) + { + m_flNextPrimaryAttack = gpGlobals->curtime + 0.3; + return true; + } + } + else + { + // Weapon is useable. Reload if empty and weapon has waited as long as it has to after firing + if (UsesClipsForAmmo1() && !AutoFiresFullClip() && + (m_iClip1 == 0) && + (GetWeaponFlags() & ITEM_FLAG_NOAUTORELOAD) == false && + m_flNextPrimaryAttack < gpGlobals->curtime && + m_flNextSecondaryAttack < gpGlobals->curtime) + { + // if we're successfully reloading, we're done + if (m_bReloadsSingly) + return StartReload(); + else + return Reload(); + } + } + + return false; +} + +void CHLCustomWeaponGun::ItemBusyFrame(void) +{ + BaseClass::ItemBusyFrame(); + + if (m_CustomData.m_bZoomDuringReload) + CheckZoomToggle(); +} + +void CHLCustomWeaponGun::ItemPostFrame(void) +{ + CBasePlayer* pOwner = ToBasePlayer(GetOwner()); + if (!pOwner) + return; + + // Debounce the recoiling counter + if ((pOwner->m_nButtons & IN_ATTACK) == false) + { + m_nShotsFired = 0; + } + + UpdateAutoFire(); + + if (m_CustomData.m_bZoomDuringReload || !m_bInReload) + CheckZoomToggle(); + + if (m_bReloadsSingly) + { + if (m_bInReload) + { + m_fFireDuration = 0.f; + + // If I'm primary firing and have one round stop reloading and fire + if ((pOwner->m_nButtons & IN_ATTACK) && (m_iClip1 >= 1)) + { + m_bInReload = false; + m_bNeedPump = false; + m_bDelayedFire1 = true; + } + // If I'm secondary firing and have one round stop reloading and fire + else if (m_CustomData.m_bHasSecondaryFire && (pOwner->m_nButtons & IN_ATTACK2) && (m_iClip1 >= 2)) + { + m_bInReload = false; + m_bNeedPump = false; + m_bDelayedFire2 = true; + } + else if (m_flNextPrimaryAttack <= gpGlobals->curtime) + { + // If out of ammo end reload + if (pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0) + { + FinishReload(); + return; + } + // If clip not full reload again + if (m_iClip1 < GetMaxClip1()) + { + Reload(); + return; + } + // Clip full, stop reloading + else + { + FinishReload(); + return; + } + } + } + else + { + // Make shotgun shell invisible + SetBodygroup(1, 1); + } + } + else if (UsesClipsForAmmo1()) + { + CheckReload(); + } + + if (m_CustomData.m_bUsePumpAnimation && (m_bNeedPump) && (m_flNextPrimaryAttack <= gpGlobals->curtime)) + { + m_fFireDuration = 0.f; + Pump(); + return; + } + + //Track the duration of the fire + //FIXME: Check for IN_ATTACK2 as well? + //FIXME: What if we're calling ItemBusyFrame? + m_fFireDuration = (pOwner->m_nButtons & IN_ATTACK) ? (m_fFireDuration + gpGlobals->frametime) : 0.0f; + + bool bFired = false; + + // Secondary attack has priority + if (m_CustomData.m_bHasSecondaryFire && !m_bMustReload && (m_bDelayedFire2 || pOwner->m_nButtons & IN_ATTACK2) && (m_flNextSecondaryAttack <= gpGlobals->curtime)) + { + m_bDelayedFire2 = false; + + if (pOwner->HasSpawnFlags(SF_PLAYER_SUPPRESS_FIRING)) + { + // Don't do anything, just cancel the whole function + return; + } + else if (UsesSecondaryAmmo() && pOwner->GetAmmoCount(m_iSecondaryAmmoType) <= 0) + { + if (m_flNextEmptySoundTime < gpGlobals->curtime) + { + WeaponSound(EMPTY); + m_flNextSecondaryAttack = m_flNextEmptySoundTime = gpGlobals->curtime + 0.5; + } + } + else if (pOwner->GetWaterLevel() == 3 && m_bAltFiresUnderwater == false) + { + // This weapon doesn't fire underwater + WeaponSound(EMPTY); + m_flNextPrimaryAttack = gpGlobals->curtime + 0.2; + return; + } + else + { + // FIXME: This isn't necessarily true if the weapon doesn't have a secondary fire! + // For instance, the crossbow doesn't have a 'real' secondary fire, but it still + // stops the crossbow from firing on the 360 if the player chooses to hold down their + // zoom button. (sjb) Orange Box 7/25/2007 +#if !defined(CLIENT_DLL) + if (!IsX360() || !ClassMatches("weapon_crossbow")) +#endif + { + bFired = ShouldBlockPrimaryFire(); + } + + SecondaryAttack(); + + // Secondary ammo doesn't have a reload animation + if (UsesClipsForAmmo2()) + { + // reload clip2 if empty + if (m_iClip2 < 1) + { + pOwner->RemoveAmmo(1, m_iSecondaryAmmoType); + m_iClip2 = m_iClip2 + 1; + } + } + } + } + + if (!bFired && !m_bMustReload && (m_bDelayedFire1 || pOwner->m_nButtons & IN_ATTACK) && (m_flNextPrimaryAttack <= gpGlobals->curtime)) + { + m_bDelayedFire1 = false; + + if (pOwner->HasSpawnFlags(SF_PLAYER_SUPPRESS_FIRING)) + { + // Don't do anything, just cancel the whole function + return; + } + // Clip empty? Or out of ammo on a no-clip weapon? + else if ((UsesClipsForAmmo1() && m_iClip1 <= 0) || (!UsesClipsForAmmo1() && pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0)) + { + HandleFireOnEmpty(); + } + else if (pOwner->GetWaterLevel() == 3 && m_bFiresUnderwater == false) + { + // This weapon doesn't fire underwater + WeaponSound(EMPTY); + m_flNextPrimaryAttack = gpGlobals->curtime + 0.2; + return; + } + else + { + //NOTENOTE: There is a bug with this code with regards to the way machine guns catch the leading edge trigger + // on the player hitting the attack key. It relies on the gun catching that case in the same frame. + // However, because the player can also be doing a secondary attack, the edge trigger may be missed. + // We really need to hold onto the edge trigger and only clear the condition when the gun has fired its + // first shot. Right now that's too much of an architecture change -- jdw + + // If the firing button was just pressed, or the alt-fire just released, reset the firing time + if ((pOwner->m_afButtonPressed & IN_ATTACK) || (pOwner->m_afButtonReleased & IN_ATTACK2)) + { + m_flNextPrimaryAttack = gpGlobals->curtime; + } + + PrimaryAttack(); + + if (AutoFiresFullClip()) + { + m_bFiringWholeClip = true; + } + +#ifdef CLIENT_DLL + pOwner->SetFiredWeapon(true); +#endif + } + } + + // ----------------------- + // Reload pressed / Clip Empty + // ----------------------- + if ((pOwner->m_nButtons & IN_RELOAD || m_bMustReload) && UsesClipsForAmmo1() && !m_bInReload) + { + // reload when reload is pressed, or if no buttons are down and weapon is empty. + if (m_bReloadsSingly) + StartReload(); + else + Reload(); + + m_fFireDuration = 0.0f; + } + + // ----------------------- + // No buttons down + // ----------------------- + else if (!((pOwner->m_nButtons & IN_ATTACK) || (pOwner->m_nButtons & IN_ATTACK2) || (CanReload() && pOwner->m_nButtons & IN_RELOAD))) + { + // no fire buttons down or reloading + if (!ReloadOrSwitchWeapons() && (m_bInReload == false)) + { + WeaponIdle(); + } + } +} + +void CHLCustomWeaponGun::PrimaryAttack() +{ + // Only the player fires this way so we can cast + CBasePlayer* pPlayer = ToBasePlayer(GetOwner()); + if (!pPlayer) + return; + + // Abort here to handle burst and auto fire modes + if ((UsesClipsForAmmo1() && m_iClip1 == 0) || (!UsesClipsForAmmo1() && !pPlayer->GetAmmoCount(m_iPrimaryAmmoType))) + return; + + if (m_CustomData.m_bFullAuto) + { + m_nShotsFired++; + + pPlayer->DoMuzzleFlash(); + + // To make the firing framerate independent, we may have to fire more than one bullet here on low-framerate systems, + // especially if the weapon we're firing has a really fast rate of fire. + int iBulletsToFire = 0; + float fireRate = GetFireRate(); + + // MUST call sound before removing a round from the clip of a CHLMachineGun + while (m_flNextPrimaryAttack <= gpGlobals->curtime) + { + WeaponSound(SINGLE, m_flNextPrimaryAttack); + m_flNextPrimaryAttack = m_flNextPrimaryAttack + fireRate; + iBulletsToFire++; + } + + // Make sure we don't fire more than the amount in the clip, if this weapon uses clips + if (UsesClipsForAmmo1()) + { + if (iBulletsToFire > m_iClip1) + iBulletsToFire = m_iClip1; + m_iClip1 -= iBulletsToFire; + } + + // Fire the bullets + FireBulletsInfo_t info; + info.m_iShots = iBulletsToFire * m_CustomData.m_nBulletsPerShot; + info.m_vecSrc = pPlayer->Weapon_ShootPosition(); + info.m_vecDirShooting = pPlayer->GetAutoaimVector(AUTOAIM_SCALE_DEFAULT); + info.m_vecSpread = pPlayer->GetAttackSpread(this); + info.m_flDistance = MAX_TRACE_LENGTH; + info.m_iAmmoType = m_iPrimaryAmmoType; + info.m_iTracerFreq = 2; + FireBullets(info); + + SendWeaponAnim(GetPrimaryAttackActivity()); + } + else + { + if (!m_CustomData.m_bNextAttackFromSequence && !m_CustomData.m_bUsePumpAnimation && !(pPlayer->m_afButtonPressed & IN_ATTACK)) + return; + + m_nShotsFired++; + + // MUST call sound before removing a round from the clip of a CMachineGun + WeaponSound(SINGLE); + pPlayer->DoMuzzleFlash(); + SendWeaponAnim(GetPrimaryAttackActivity()); + + m_flNextPrimaryAttack = gpGlobals->curtime + ((m_CustomData.m_bNextAttackFromSequence || m_CustomData.m_bUsePumpAnimation) ? GetViewModelSequenceDuration() : GetFireRate()); + m_iClip1 -= 1; + + Vector vecSrc = pPlayer->Weapon_ShootPosition(); + Vector vecAiming = pPlayer->GetAutoaimVector(AUTOAIM_SCALE_DEFAULT); + + pPlayer->SetMuzzleFlashTime(gpGlobals->curtime + 1.0); + + // Fire the bullets, and force the first shot to be perfectly accuracy + pPlayer->FireBullets(m_CustomData.m_nBulletsPerShot, vecSrc, vecAiming, GetBulletSpread(), MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 0, -1, -1, 0, NULL, (m_CustomData.m_nBulletsPerShot > 1), true); + + if (m_CustomData.m_bUsePumpAnimation && m_iClip1) + { + // pump so long as some rounds are left. + m_bNeedPump = true; + } + } + + m_iPrimaryAttacks++; + + //Factor in the view kick + AddViewKick(); + + CSoundEnt::InsertSound(SOUND_COMBAT, GetAbsOrigin(), SOUNDENT_VOLUME_MACHINEGUN, 0.2, pPlayer); + + if (!m_iClip1 && pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0) + { + // HEV suit - indicate out of ammo condition + pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + } + + pPlayer->SetAnimation(PLAYER_ATTACK1); + + // Register a muzzleflash for the AI + pPlayer->SetMuzzleFlashTime(gpGlobals->curtime + 0.5); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Activity +//----------------------------------------------------------------------------- +Activity CHLCustomWeaponGun::GetPrimaryAttackActivity(void) +{ + if (!m_CustomData.m_bUseRecoilAnims || m_nShotsFired < 2) + return ACT_VM_PRIMARYATTACK; + + if (m_nShotsFired < 3) + return ACT_VM_RECOIL1; + + if (m_nShotsFired < 4) + return ACT_VM_RECOIL2; + + return ACT_VM_RECOIL3; +} + +//----------------------------------------------------------------------------- +// Purpose: Make enough sound events to fill the estimated think interval +// returns: number of shots needed +//----------------------------------------------------------------------------- +int CHLCustomWeaponGun::WeaponSoundRealtime(WeaponSound_t shoot_type) +{ + int numBullets = 0; + + // ran out of time, clamp to current + if (m_flNextSoundTime < gpGlobals->curtime) + { + m_flNextSoundTime = gpGlobals->curtime; + } + + // make enough sound events to fill up the next estimated think interval + float dt = Clamp(m_flAnimTime - m_flPrevAnimTime, 0.f, 0.2f); + if (m_flNextSoundTime < gpGlobals->curtime + dt) + { + WeaponSound(SINGLE_NPC, m_flNextSoundTime); + m_flNextSoundTime += GetFireRate(); + numBullets++; + } + if (m_flNextSoundTime < gpGlobals->curtime + dt) + { + WeaponSound(SINGLE_NPC, m_flNextSoundTime); + m_flNextSoundTime += GetFireRate(); + numBullets++; + } + + return numBullets; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pOperator - +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::FireNPCPrimaryAttack(CBaseCombatCharacter* pOperator, bool bUseWeaponAngles) +{ + Vector vecShootOrigin, vecShootDir; + CAI_BaseNPC* npc = pOperator->MyNPCPointer(); + int iMuzzle = LookupAttachment("muzzle"); + + ASSERT(npc != NULL); + + if (bUseWeaponAngles) + { + QAngle angShootDir; + GetAttachment(iMuzzle, vecShootOrigin, angShootDir); + AngleVectors(angShootDir, &vecShootDir); + } + else + { + vecShootOrigin = pOperator->Weapon_ShootPosition(); + vecShootDir = npc->GetActualShootTrajectory(vecShootOrigin); + } + + CSoundEnt::InsertSound(SOUND_COMBAT | SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), SOUNDENT_VOLUME_MACHINEGUN, 0.2f, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy()); + + const Vector& vecSpread = (bUseWeaponAngles || m_CustomData.m_nBulletsPerShot > 1) ? GetBulletSpread() : VECTOR_CONE_PRECALCULATED; + if (m_CustomData.m_bFullAuto) + { + int nShots = WeaponSoundRealtime(SINGLE_NPC); + pOperator->FireBullets(nShots * m_CustomData.m_nBulletsPerShot, vecShootOrigin, vecShootDir, vecSpread, MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 2, entindex(), iMuzzle); + pOperator->DoMuzzleFlash(); + m_iClip1 = m_iClip1 - nShots; + } + else + { + WeaponSound(SINGLE_NPC); + pOperator->FireBullets(m_CustomData.m_nBulletsPerShot, vecShootOrigin, vecShootDir, vecSpread, MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 2, entindex(), iMuzzle); + pOperator->DoMuzzleFlash(); + m_iClip1 = m_iClip1 - 1; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::FireNPCSecondaryAttack(CBaseCombatCharacter* pOperator, bool bUseWeaponAngles) +{ + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::Operator_ForceNPCFire(CBaseCombatCharacter* pOperator, bool bSecondary) +{ + if (bSecondary) + { + FireNPCSecondaryAttack(pOperator, true); + } + else + { + // Ensure we have enough rounds in the clip + m_iClip1++; + + FireNPCPrimaryAttack(pOperator, true); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEvent - +// *pOperator - +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::Operator_HandleAnimEvent(animevent_t* pEvent, CBaseCombatCharacter* pOperator) +{ + switch (pEvent->event) + { + case EVENT_WEAPON_SMG1: + case EVENT_WEAPON_SHOTGUN_FIRE: + case EVENT_WEAPON_AR1: + case EVENT_WEAPON_AR2: + case EVENT_WEAPON_HMG1: + case EVENT_WEAPON_SMG2: + case EVENT_WEAPON_SNIPER_RIFLE_FIRE: + case EVENT_WEAPON_PISTOL_FIRE: + { + FireNPCPrimaryAttack(pOperator, false); + } + break; + + case EVENT_WEAPON_AR2_ALTFIRE: + { + FireNPCSecondaryAttack(pOperator, false); + } + break; + + default: + CBaseCombatWeapon::Operator_HandleAnimEvent(pEvent, pOperator); + break; + } +} diff --git a/sp/src/game/server/maprules.cpp b/sp/src/game/server/maprules.cpp index 91049ad8..60279244 100644 --- a/sp/src/game/server/maprules.cpp +++ b/sp/src/game/server/maprules.cpp @@ -447,7 +447,8 @@ void CGameText::SetText( const char* pszStr ) for (int i = 1; i < vecLines.Count(); i++) { - Q_snprintf( szMsg, sizeof( szMsg ), "%s\n%s", szMsg, vecLines[i] ); + Q_strncat( szMsg, "\n", sizeof( szMsg ) ); + Q_strncat( szMsg, vecLines[i], sizeof( szMsg ) ); } m_iszMessage = AllocPooledString( szMsg ); } diff --git a/sp/src/game/server/physics_prop_ragdoll.cpp b/sp/src/game/server/physics_prop_ragdoll.cpp index 93efddc7..cc788fc6 100644 --- a/sp/src/game/server/physics_prop_ragdoll.cpp +++ b/sp/src/game/server/physics_prop_ragdoll.cpp @@ -1557,6 +1557,16 @@ CBaseEntity *CreateServerRagdoll( CBaseAnimating *pAnimating, int forceBone, con pRagdoll->CollisionProp()->SetCollisionBounds( mins, maxs ); #ifdef MAPBASE + // If this was a NPC running a dynamic interaction, disable collisions with the interaction partner + if (pAnimating->IsNPC() /*&& pAnimating->MyNPCPointer()->IsRunningDynamicInteraction()*/) + { + CAI_BaseNPC *pNPC = pAnimating->MyNPCPointer(); + if (pNPC->GetInteractionPartner() && pNPC->GetInteractionPartner()->VPhysicsGetObject()) + { + PhysDisableEntityCollisions( pRagdoll, pNPC->GetInteractionPartner() ); + } + } + variant_t variant; variant.SetEntity(pRagdoll); pAnimating->FireNamedOutput("OnServerRagdoll", variant, pRagdoll, pAnimating); diff --git a/sp/src/game/server/player.cpp b/sp/src/game/server/player.cpp index a68bf982..28c767c5 100644 --- a/sp/src/game/server/player.cpp +++ b/sp/src/game/server/player.cpp @@ -7021,7 +7021,7 @@ bool CBasePlayer::BumpWeapon( CBaseCombatWeapon *pWeapon ) { //Weapon_EquipAmmoOnly( pWeapon ); - // I'm too lazy to make my own version of Weapon_EquipAmmoOnly that doesn't check if we already have the weapon first + // Weapon_EquipAmmoOnly checks the array again, which isn't necessary here int primaryGiven = (pWeapon->UsesClipsForAmmo1()) ? pWeapon->m_iClip1 : pWeapon->GetPrimaryAmmoCount(); int secondaryGiven = (pWeapon->UsesClipsForAmmo2()) ? pWeapon->m_iClip2 : pWeapon->GetSecondaryAmmoCount(); @@ -7734,6 +7734,93 @@ void CBasePlayer::ResetAutoaim( void ) m_fOnTarget = false; } +#ifdef MAPBASE +ConVar player_debug_probable_aim_target( "player_debug_probable_aim_target", "0", FCVAR_CHEAT, "" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseEntity *CBasePlayer::GetProbableAimTarget( const Vector &vecSrc, const Vector &vecDir ) +{ + trace_t tr; + CBaseEntity *pIgnore = NULL; + if (IsInAVehicle()) + pIgnore = GetVehicleEntity(); + + CTraceFilterSkipTwoEntities traceFilter( this, pIgnore, COLLISION_GROUP_NONE ); + + // Based on dot product and distance + // If we aim directly at something, only return it if there's not a larger entity slightly off-center + // Should be weighted based on whether an entity is a NPC, etc. + CBaseEntity *pBestEnt = NULL; + float flBestWeight = 0.0f; + for (CBaseEntity *pEntity = UTIL_EntitiesInPVS( this, NULL ); pEntity; pEntity = UTIL_EntitiesInPVS( this, pEntity )) + { + // Combat characters can be unviewable if they just died + if (!pEntity->IsViewable() && !pEntity->IsCombatCharacter()) + continue; + + if (pEntity == this || pEntity->GetMoveParent() == this || pEntity == GetVehicleEntity()) + continue; + + Vector vecEntDir = (pEntity->EyePosition() - vecSrc); + float flDot = DotProduct( vecEntDir.Normalized(), vecDir); + + if (flDot < m_flFieldOfView) + continue; + + // Make sure we can see it + UTIL_TraceLine( vecSrc, pEntity->EyePosition(), MASK_SHOT, &traceFilter, &tr ); + if (tr.m_pEnt != pEntity) + { + if (pEntity->IsCombatCharacter()) + { + // Trace between centers as well just in case our eyes are blocked + UTIL_TraceLine( WorldSpaceCenter(), pEntity->WorldSpaceCenter(), MASK_SHOT, &traceFilter, &tr ); + if (tr.m_pEnt != pEntity) + continue; + } + else + continue; + } + + float flWeight = flDot - (vecEntDir.LengthSqr() / Square( 2048.0f )); + + if (pEntity->IsCombatCharacter()) + { + // Hostile NPCs are more likely targets + if (IRelationType( pEntity ) <= D_FR) + flWeight += 0.5f; + } + else if (pEntity->GetFlags() & FL_AIMTARGET) + { + // FL_AIMTARGET is often used for props like explosive barrels + flWeight += 0.25f; + } + + if (player_debug_probable_aim_target.GetBool()) + { + float flWeightClamped = 1.0f - RemapValClamped( flWeight, -2.0f, 2.0f, 0.0f, 1.0f ); + pEntity->EntityText( 0, UTIL_VarArgs( "%f", flWeight ), 2.0f, flWeightClamped * 255.0f, 255.0f, flWeightClamped * 255.0f, 255 ); + } + + if (flWeight > flBestWeight) + { + pBestEnt = pEntity; + flBestWeight = flWeight; + } + } + + if (player_debug_probable_aim_target.GetBool()) + { + Msg( "Best probable aim target is %s\n", pBestEnt->GetDebugName() ); + NDebugOverlay::EntityBounds( pBestEnt, 255, 100, 0, 0, 2.0f ); + } + + return pBestEnt; +} +#endif + // ========================================================================== // > Weapon stuff // ========================================================================== diff --git a/sp/src/game/server/player.h b/sp/src/game/server/player.h index 0bfba8fb..72a1e38e 100644 --- a/sp/src/game/server/player.h +++ b/sp/src/game/server/player.h @@ -608,6 +608,11 @@ public: virtual bool ShouldAutoaim( void ); void SetTargetInfo( Vector &vecSrc, float flDist ); +#ifdef MAPBASE + // Tries to figure out what the player is trying to aim at + CBaseEntity *GetProbableAimTarget( const Vector &vecSrc, const Vector &vecDir ); +#endif + void SetViewEntity( CBaseEntity *pEntity ); CBaseEntity *GetViewEntity( void ) { return m_hViewEntity; } diff --git a/sp/src/game/server/player_command.cpp b/sp/src/game/server/player_command.cpp index d5a6a1f0..7614babe 100644 --- a/sp/src/game/server/player_command.cpp +++ b/sp/src/game/server/player_command.cpp @@ -393,7 +393,7 @@ void CPlayerMove::RunCommand ( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper if ( weapon ) { VPROF( "player->SelectItem()" ); - player->SelectItem( weapon->GetName(), ucmd->weaponsubtype ); + player->SelectItem( weapon->GetClassname(), ucmd->weaponsubtype ); } } diff --git a/sp/src/game/server/props.cpp b/sp/src/game/server/props.cpp index da632cea..5ad4c11a 100644 --- a/sp/src/game/server/props.cpp +++ b/sp/src/game/server/props.cpp @@ -6308,14 +6308,12 @@ void CPropDoorRotating::Break( CBaseEntity *pBreaker, const CTakeDamageInfo &inf } #endif -#ifdef MAPBASE void CPropDoorRotating::InputSetSpeed(inputdata_t &inputdata) { AssertMsg1(inputdata.value.Float() > 0.0f, "InputSetSpeed on %s called with negative parameter!", GetDebugName() ); m_flSpeed = inputdata.value.Float(); DoorResume(); } -#endif // Debug sphere class CPhysSphere : public CPhysicsProp @@ -6362,15 +6360,6 @@ BEGIN_DATADESC( CPhysSphere ) END_DATADESC() #endif -#ifndef MAPBASE // Yes, all I'm doing is moving this up a few lines and I'm still using the preprocessor. -void CPropDoorRotating::InputSetSpeed(inputdata_t &inputdata) -{ - AssertMsg1(inputdata.value.Float() > 0.0f, "InputSetSpeed on %s called with negative parameter!", GetDebugName() ); - m_flSpeed = inputdata.value.Float(); - DoorResume(); -} -#endif - LINK_ENTITY_TO_CLASS( prop_sphere, CPhysSphere ); diff --git a/sp/src/game/server/scripted.cpp b/sp/src/game/server/scripted.cpp index 37b45844..f02a1dcd 100644 --- a/sp/src/game/server/scripted.cpp +++ b/sp/src/game/server/scripted.cpp @@ -1396,11 +1396,31 @@ void CAI_ScriptedSequence::ModifyScriptedAutoMovement( Vector *vecNewPos ) } } + VMatrix matInteractionPosition = m_matInteractionPosition; + +#ifdef MAPBASE + // Account for our own sequence movement + pAnimating = m_hTargetEnt->GetBaseAnimating(); + if (pAnimating) + { + Vector vecDeltaPos; + QAngle angDeltaAngles; + + pAnimating->GetSequenceMovement( pAnimating->GetSequence(), 0.0f, pAnimating->GetCycle(), vecDeltaPos, angDeltaAngles ); + if (!vecDeltaPos.IsZero()) + { + VMatrix matLocalMovement; + matLocalMovement.SetupMatrixOrgAngles( vecDeltaPos, angDeltaAngles ); + MatrixMultiply( m_matInteractionPosition, matLocalMovement, matInteractionPosition ); + } + } +#endif + // We've been asked to maintain a specific position relative to the other NPC // we're interacting with. Lerp towards the relative position. VMatrix matMeToWorld, matLocalToWorld; matMeToWorld.SetupMatrixOrgAngles( vecRelativeOrigin, angRelativeAngles ); - MatrixMultiply( matMeToWorld, m_matInteractionPosition, matLocalToWorld ); + MatrixMultiply( matMeToWorld, matInteractionPosition, matLocalToWorld ); // Get the desired NPC position in worldspace Vector vecOrigin; diff --git a/sp/src/game/server/server_mapbase.vpc b/sp/src/game/server/server_mapbase.vpc index b04706d4..7d74847e 100644 --- a/sp/src/game/server/server_mapbase.vpc +++ b/sp/src/game/server/server_mapbase.vpc @@ -63,6 +63,8 @@ $Project $File "mapbase\ai_grenade.h" $File "mapbase\ai_monitor.cpp" $File "mapbase\ai_weaponmodifier.cpp" + $File "mapbase\custom_weapon_factory.cpp" + $File "mapbase\custom_weapon_factory.h" $File "mapbase\closecaption_entity.cpp" $File "mapbase\datadesc_mod.cpp" $File "mapbase\datadesc_mod.h" @@ -86,6 +88,7 @@ $Project $File "mapbase\SystemConvarMod.h" $File "mapbase\variant_tools.h" $File "mapbase\vgui_text_display.cpp" + $File "mapbase\weapon_custom_hl2.cpp" $File "mapbase\logic_eventlistener.cpp" $File "mapbase\logic_register_activator.cpp" diff --git a/sp/src/game/server/triggers.h b/sp/src/game/server/triggers.h index 47bf82be..b6760677 100644 --- a/sp/src/game/server/triggers.h +++ b/sp/src/game/server/triggers.h @@ -96,7 +96,7 @@ public: bool PointIsWithin( const Vector &vecPoint ); #ifdef MAPBASE_VSCRIPT - bool ScriptPassesTriggerFilters( HSCRIPT hOther ) { return ToEnt(hOther) ? PassesTriggerFilters( ToEnt(hOther) ) : NULL; } + bool ScriptPassesTriggerFilters( HSCRIPT hOther ) { return ToEnt(hOther) ? PassesTriggerFilters( ToEnt(hOther) ) : false; } HSCRIPT ScriptGetTouchedEntityOfType( const char *sClassName ) { return ToHScript( GetTouchedEntityOfType(sClassName) ); } void ScriptGetTouchingEntities( HSCRIPT hTable ); diff --git a/sp/src/game/server/util.cpp b/sp/src/game/server/util.cpp index 8a97695a..6a44cec2 100644 --- a/sp/src/game/server/util.cpp +++ b/sp/src/game/server/util.cpp @@ -76,6 +76,10 @@ public: virtual const char *GetCannonicalName( const char *pClassName ); void ReportEntitySizes(); +#ifdef MAPBASE + virtual void UninstallFactory(const char* pClassName); +#endif // MAPBASE + private: IEntityFactory *FindFactory( const char *pClassName ); public: @@ -203,6 +207,11 @@ void CEntityFactoryDictionary::ReportEntitySizes() } #ifdef MAPBASE +void CEntityFactoryDictionary::UninstallFactory(const char* pClassName) +{ + m_Factories.Remove(pClassName); +} + int EntityFactory_AutoComplete( const char *cmdname, CUtlVector< CUtlString > &commands, CUtlRBTree< CUtlString > &symbols, char *substring, int checklen = 0 ) { CEntityFactoryDictionary *pFactoryDict = (CEntityFactoryDictionary*)EntityFactoryDictionary(); diff --git a/sp/src/game/server/util.h b/sp/src/game/server/util.h index 82f485ba..bed006e1 100644 --- a/sp/src/game/server/util.h +++ b/sp/src/game/server/util.h @@ -100,6 +100,9 @@ public: virtual void Destroy( const char *pClassName, IServerNetworkable *pNetworkable ) = 0; virtual IEntityFactory *FindFactory( const char *pClassName ) = 0; virtual const char *GetCannonicalName( const char *pClassName ) = 0; +#ifdef MAPBASE + virtual void UninstallFactory(const char* pClassName) = 0; +#endif // MAPBASE }; IEntityFactoryDictionary *EntityFactoryDictionary(); @@ -188,8 +191,7 @@ extern CGlobalVars *gpGlobals; inline bool FStrEq(const char *sz1, const char *sz2) { #ifdef MAPBASE - // V_stricmp() already checks if the pointers are equal, so having a pointer comparison here is pointless. - // I'm not sure if this was already automatically phased out by the compiler, but if it wasn't, then this is a very good change. + // V_stricmp() already checks if the pointers are equal, so having a pointer comparison here is unnecessary. return ( V_stricmp(sz1, sz2) == 0 ); #else return ( sz1 == sz2 || V_stricmp(sz1, sz2) == 0 ); diff --git a/sp/src/game/server/vscript_server.cpp b/sp/src/game/server/vscript_server.cpp index 40e35f00..aa14a1b4 100644 --- a/sp/src/game/server/vscript_server.cpp +++ b/sp/src/game/server/vscript_server.cpp @@ -606,10 +606,10 @@ bool VScriptServerInit() #ifdef MAPBASE_VSCRIPT // MULTIPLAYER + // NOTE: 'PlayerInstanceFromIndex' and 'GetPlayerFromUserID' are used in L4D2 and Source 2, + // but the GetPlayerBy* names are more consistent. // ScriptRegisterFunctionNamed( g_pScriptVM, UTIL_PlayerByIndex, "GetPlayerByIndex", "PlayerInstanceFromIndex" ); - // ScriptRegisterFunctionNamed( g_pScriptVM, UTIL_PlayerByUserId, "GetPlayerByUserId", "GetPlayerFromUserID" ); - // ScriptRegisterFunctionNamed( g_pScriptVM, UTIL_PlayerByName, "GetPlayerByName", "" ); - // ScriptRegisterFunctionNamed( g_pScriptVM, ScriptGetPlayerByNetworkID, "GetPlayerByNetworkID", "" ); + // ScriptRegisterFunctionNamed( g_pScriptVM, UTIL_PlayerByUserId, "GetPlayerByUserID", "GetPlayerFromUserID" ); ScriptRegisterFunctionNamed( g_pScriptVM, UTIL_ShowMessageAll, "ShowMessage", "Print a hud message on all clients" ); #else @@ -841,6 +841,8 @@ public: virtual void LevelShutdownPostEntity( void ) { #ifdef MAPBASE_VSCRIPT + g_ScriptEntityIterator.DisableEntityListening(); + g_ScriptNetMsg->LevelShutdownPreVM(); GetScriptHookManager().OnShutdown(); diff --git a/sp/src/game/shared/basecombatweapon_shared.cpp b/sp/src/game/shared/basecombatweapon_shared.cpp index bef09f34..c67ab35a 100644 --- a/sp/src/game/shared/basecombatweapon_shared.cpp +++ b/sp/src/game/shared/basecombatweapon_shared.cpp @@ -239,7 +239,7 @@ const unsigned char *CBaseCombatWeapon::GetEncryptionKey( void ) void CBaseCombatWeapon::Precache( void ) { #if defined( CLIENT_DLL ) - Assert( Q_strlen( GetClassname() ) > 0 ); + Assert( Q_strlen(GetWeaponScriptName() ) > 0 ); // Msg( "Client got %s\n", GetClassname() ); #endif m_iPrimaryAmmoType = m_iSecondaryAmmoType = -1; @@ -321,7 +321,7 @@ void CBaseCombatWeapon::Precache( void ) else { // Couldn't read data file, remove myself - Warning( "Error reading weapon data file for: %s\n", GetClassname() ); + Warning( "Error reading weapon data file for: %s\n", GetWeaponScriptName() ); // Remove( ); //don't remove, this gets released soon! } } @@ -2886,6 +2886,15 @@ bool CBaseCombatWeapon::IsLocked( CBaseEntity *pAsker ) return ( m_flUnlockTime > gpGlobals->curtime && m_hLocker != pAsker ); } +bool CBaseCombatWeapon::CanBePickedUpByNPCs(void) +{ +#ifdef MAPBASE + return GetWpnData().m_nWeaponRestriction != WPNRESTRICT_PLAYER_ONLY; +#else + return true; +#endif // MAPBASE +} + //----------------------------------------------------------------------------- // Purpose: // Input : diff --git a/sp/src/game/shared/basecombatweapon_shared.h b/sp/src/game/shared/basecombatweapon_shared.h index bd439b8c..1b411999 100644 --- a/sp/src/game/shared/basecombatweapon_shared.h +++ b/sp/src/game/shared/basecombatweapon_shared.h @@ -390,7 +390,7 @@ public: bool IsLocked( CBaseEntity *pAsker ); //All weapons can be picked up by NPCs by default - virtual bool CanBePickedUpByNPCs( void ) { return true; } + virtual bool CanBePickedUpByNPCs(void); virtual int GetSkinOverride() const { return -1; } diff --git a/sp/src/game/shared/baseentity_shared.cpp b/sp/src/game/shared/baseentity_shared.cpp index 3393afa8..b2cd6bdc 100644 --- a/sp/src/game/shared/baseentity_shared.cpp +++ b/sp/src/game/shared/baseentity_shared.cpp @@ -83,6 +83,10 @@ ConVar ai_shot_bias_min( "ai_shot_bias_min", "-1.0", FCVAR_REPLICATED ); ConVar ai_shot_bias_max( "ai_shot_bias_max", "1.0", FCVAR_REPLICATED ); ConVar ai_debug_shoot_positions( "ai_debug_shoot_positions", "0", FCVAR_REPLICATED | FCVAR_CHEAT ); +#if defined(MAPBASE) && defined(GAME_DLL) +ConVar ai_shot_notify_targets( "ai_shot_notify_targets", "0", FCVAR_NONE, "Allows fired bullets to notify the NPCs and players they are targeting, regardless of whether they hit them or not. Can be used for custom AI and speech." ); +#endif + // Utility func to throttle rate at which the "reasonable position" spew goes out static double s_LastEntityReasonableEmitTime; bool CheckEmitReasonablePhysicsSpew() @@ -2081,6 +2085,25 @@ void CBaseEntity::FireBullets( const FireBulletsInfo_t &info ) CTakeDamageInfo dmgInfo( this, pAttacker, flCumulativeDamage, nDamageType ); gamestats->Event_WeaponHit( pPlayer, info.m_bPrimaryAttack, pPlayer->GetActiveWeapon()->GetClassname(), dmgInfo ); } + +#ifdef MAPBASE + if ( ai_shot_notify_targets.GetBool() ) + { + if ( IsPlayer() ) + { + // Look for probable target to notify of attack + CBaseEntity *pAimTarget = static_cast(this)->GetProbableAimTarget( info.m_vecSrc, info.m_vecDirShooting ); + if ( pAimTarget && pAimTarget->IsCombatCharacter() ) + { + pAimTarget->MyCombatCharacterPointer()->OnEnemyRangeAttackedMe( this, vecDir, vecEnd ); + } + } + else if ( GetEnemy() && GetEnemy()->IsCombatCharacter() ) + { + GetEnemy()->MyCombatCharacterPointer()->OnEnemyRangeAttackedMe( this, vecDir, vecEnd ); + } + } +#endif #endif } @@ -2736,6 +2759,18 @@ void CBaseEntity::SetScriptOwnerEntity(HSCRIPT pOwner) SetOwnerEntity(ToEnt(pOwner)); } +#ifdef MAPBASE_VSCRIPT +HSCRIPT CBaseEntity::ScriptGetGroundEntity() +{ + return ToHScript( m_hGroundEntity.Get() ); +} + +void CBaseEntity::ScriptSetGroundEntity( HSCRIPT hGroundEnt ) +{ + SetGroundEntity( ToEnt( hGroundEnt ) ); +} +#endif + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- const Vector& CBaseEntity::ScriptGetColorVector() diff --git a/sp/src/game/shared/eventlist.cpp b/sp/src/game/shared/eventlist.cpp index 043293bc..2776aa8f 100644 --- a/sp/src/game/shared/eventlist.cpp +++ b/sp/src/game/shared/eventlist.cpp @@ -233,6 +233,11 @@ void EventList_RegisterSharedEvents( void ) REGISTER_SHARED_ANIMEVENT( AE_SV_DUSTTRAIL, AE_TYPE_SERVER ); REGISTER_SHARED_ANIMEVENT( AE_CL_CREATE_PARTICLE_EFFECT, AE_TYPE_CLIENT ); +#ifdef MAPBASE // From Alien Swarm SDK + REGISTER_SHARED_ANIMEVENT( AE_CL_STOP_PARTICLE_EFFECT, AE_TYPE_CLIENT ); + REGISTER_SHARED_ANIMEVENT( AE_CL_ADD_PARTICLE_EFFECT_CP, AE_TYPE_CLIENT ); + //REGISTER_SHARED_ANIMEVENT( AE_CL_CREATE_PARTICLE_BRASS, AE_TYPE_CLIENT ); +#endif REGISTER_SHARED_ANIMEVENT( AE_RAGDOLL, AE_TYPE_SERVER ); @@ -252,5 +257,8 @@ void EventList_RegisterSharedEvents( void ) #ifdef MAPBASE REGISTER_SHARED_ANIMEVENT( AE_NPC_RESPONSE, AE_TYPE_SERVER ); REGISTER_SHARED_ANIMEVENT( AE_NPC_RESPONSE_FORCED, AE_TYPE_SERVER ); + + REGISTER_SHARED_ANIMEVENT( AE_VSCRIPT_RUN, AE_TYPE_CLIENT | AE_TYPE_SERVER ); + REGISTER_SHARED_ANIMEVENT( AE_VSCRIPT_RUN_FILE, AE_TYPE_CLIENT | AE_TYPE_SERVER ); #endif } \ No newline at end of file diff --git a/sp/src/game/shared/eventlist.h b/sp/src/game/shared/eventlist.h index ee8b38df..7d810bd4 100644 --- a/sp/src/game/shared/eventlist.h +++ b/sp/src/game/shared/eventlist.h @@ -69,6 +69,11 @@ typedef enum AE_SV_DUSTTRAIL, AE_CL_CREATE_PARTICLE_EFFECT, +#ifdef MAPBASE // From Alien Swarm SDK + AE_CL_STOP_PARTICLE_EFFECT, + AE_CL_ADD_PARTICLE_EFFECT_CP, + //AE_CL_CREATE_PARTICLE_BRASS, +#endif AE_RAGDOLL, @@ -88,6 +93,9 @@ typedef enum #ifdef MAPBASE AE_NPC_RESPONSE, // Play a response system concept if we're not speaking AE_NPC_RESPONSE_FORCED, // Always play a response system concept + + AE_VSCRIPT_RUN, // Run vscript code (server + client) + AE_VSCRIPT_RUN_FILE, // Run vscript file (server + client) #endif LAST_SHARED_ANIMEVENT, diff --git a/sp/src/game/shared/hl2mp/weapon_ar2.cpp b/sp/src/game/shared/hl2mp/weapon_ar2.cpp index 5086db1b..faa5b14c 100644 --- a/sp/src/game/shared/hl2mp/weapon_ar2.cpp +++ b/sp/src/game/shared/hl2mp/weapon_ar2.cpp @@ -208,7 +208,7 @@ void CWeaponAR2::DelayedAttack( void ) // pOwner->SnapEyeAngles( angles ); - pOwner->ViewPunch( QAngle( SharedRandomInt( "ar2pax", -8, -12 ), SharedRandomInt( "ar2pay", 1, 2 ), 0 ) ); + pOwner->ViewPunch( QAngle( SharedRandomInt( "ar2pax", -12, -8 ), SharedRandomInt( "ar2pay", 1, 2 ), 0 ) ); // Decrease ammo pOwner->RemoveAmmo( 1, m_iSecondaryAmmoType ); diff --git a/sp/src/game/shared/mapbase/mapbase_shared.cpp b/sp/src/game/shared/mapbase/mapbase_shared.cpp index 79c4f51e..8c364b62 100644 --- a/sp/src/game/shared/mapbase/mapbase_shared.cpp +++ b/sp/src/game/shared/mapbase/mapbase_shared.cpp @@ -70,7 +70,7 @@ ConVar mapbase_version_client( "mapbase_version_client", MAPBASE_VERSION, FCVAR_ // This is from the vgui_controls library extern vgui::HScheme g_iCustomClientSchemeOverride; -bool g_bUsingCustomHudAnimations = false; +extern bool g_bUsingCustomHudAnimations; bool g_bUsingCustomHudLayout = false; #endif diff --git a/sp/src/game/shared/mapbase/vscript_singletons.cpp b/sp/src/game/shared/mapbase/vscript_singletons.cpp index c5cca45b..86b1248f 100644 --- a/sp/src/game/shared/mapbase/vscript_singletons.cpp +++ b/sp/src/game/shared/mapbase/vscript_singletons.cpp @@ -22,6 +22,7 @@ #include "filesystem.h" #include "igameevents.h" #include "engine/ivdebugoverlay.h" +#include "icommandline.h" #ifdef CLIENT_DLL #include "IEffects.h" @@ -1094,7 +1095,7 @@ const char *CScriptReadWriteFile::FileRead( 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 ) ) + if ( !CommandLine()->FindParm( "-script_dotslash_read" ) && !V_RemoveDotSlashes( pszFullName, CORRECT_PATH_SEPARATOR, true ) ) { DevWarning( 2, "Invalid file location : %s\n", szFile ); return NULL; @@ -1143,7 +1144,7 @@ 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 ) ) + if ( !CommandLine()->FindParm( "-script_dotslash_read" ) && !V_RemoveDotSlashes( pszFullName, CORRECT_PATH_SEPARATOR, true ) ) { DevWarning( 2, "Invalid file location : %s\n", szFile ); return NULL; @@ -1224,7 +1225,7 @@ HSCRIPT CScriptReadWriteFile::KeyValuesRead( 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 ) ) + if ( !CommandLine()->FindParm( "-script_dotslash_read" ) && !V_RemoveDotSlashes( pszFullName, CORRECT_PATH_SEPARATOR, true ) ) { DevWarning( 2, "Invalid file location : %s\n", szFile ); return NULL; @@ -1338,8 +1339,7 @@ static const char *HasNetMsgCollision( int hash, const char *ignore ) inline int CNetMsgScriptHelper::Hash( const char *key ) { - int hash = HashStringCaseless( key ); - Assert( hash < (1 << SCRIPT_NETMSG_HEADER_BITS) ); + int hash = CaselessStringHashFunctor()( key ); return hash; } @@ -1445,7 +1445,7 @@ void CNetMsgScriptHelper::ReceiveMessage( bf_read &msg ) while ( count-- ) #endif { - int hash = m_MsgIn_()ReadWord(); + int hash = m_MsgIn_()ReadUBitLong( SCRIPT_NETMSG_HEADER_BITS ); #ifdef _DEBUG const char *msgName = GetNetMsgName( hash ); @@ -1514,7 +1514,7 @@ void CNetMsgScriptHelper::Start( const char *msg ) Reset(); #endif - m_MsgOut.WriteWord( Hash( msg ) ); + m_MsgOut.WriteUBitLong( Hash( msg ), SCRIPT_NETMSG_HEADER_BITS ); } #ifdef GAME_DLL @@ -1923,7 +1923,7 @@ BEGIN_SCRIPTDESC_ROOT_NAMED( CNetMsgScriptHelper, "CNetMsg", SCRIPT_SINGLETON "N DEFINE_SCRIPTFUNC( Receive, "Set custom network message callback" ) DEFINE_SCRIPTFUNC_NAMED( Receive, "Recieve", SCRIPT_HIDE ) // This was a typo until v6.3 #ifdef GAME_DLL - DEFINE_SCRIPTFUNC( Send, "Send a custom network message from the server to the client (max 252 bytes)" ) + DEFINE_SCRIPTFUNC( Send, "Send a custom network message from the server to the client (max 251 bytes)" ) #else DEFINE_SCRIPTFUNC( Send, "Send a custom network message from the client to the server (max 2044 bytes)" ) #endif @@ -2742,6 +2742,11 @@ public: void CScriptConvarAccessor::RegisterCommand( const char *name, HSCRIPT fn, const char *helpString, int flags ) { +#if CLIENT_DLL + // FIXME: This crashes in engine when used as a hook (dispatched from CScriptConCommand::CommandCallback()) + Assert( V_stricmp( name, "load" ) != 0 ); +#endif + unsigned int hash = Hash(name); int idx = g_ScriptConCommands.Find(hash); if ( idx == g_ScriptConCommands.InvalidIndex() ) diff --git a/sp/src/game/shared/mapbase/vscript_singletons.h b/sp/src/game/shared/mapbase/vscript_singletons.h index e514f4d9..b20cc67c 100644 --- a/sp/src/game/shared/mapbase/vscript_singletons.h +++ b/sp/src/game/shared/mapbase/vscript_singletons.h @@ -24,7 +24,7 @@ void RegisterScriptSingletons(); #endif #define SCRIPT_NETMSG_QUEUE_BITS 3 // determines the number of custom messages client can write to a usercmd -#define SCRIPT_NETMSG_HEADER_BITS (sizeof(word) << 3) +#define SCRIPT_NETMSG_HEADER_BITS (sizeof(unsigned int) << 3) #define SCRIPT_NETMSG_STRING_SIZE 512 diff --git a/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp b/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp index 4d5c744d..b988a5a3 100644 --- a/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp +++ b/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp @@ -7,6 +7,7 @@ #include "cbase.h" #include "tier1/fmtstr.h" +#include "tier1/utlvector.h" #include "weapon_custom_scripted.h" // memdbgon must be the last include file in a .cpp file!!! @@ -179,7 +180,7 @@ bool CWeaponCustomScripted::RunWeaponHook( ScriptHook_t &hook, HSCRIPT &cached, { if ( !cached ) { - if ( hook.CanRunInScope( m_ScriptScope ) ) + if ( m_ScriptScope.IsInitialized() && hook.CanRunInScope( m_ScriptScope ) ) { cached = hook.m_hFunc; } @@ -187,6 +188,7 @@ bool CWeaponCustomScripted::RunWeaponHook( ScriptHook_t &hook, HSCRIPT &cached, if (cached) { + hook.m_hFunc = cached; return hook.Call( m_ScriptScope, retVal, pArgs, false ); } @@ -198,13 +200,6 @@ bool CWeaponCustomScripted::RunWeaponHook( ScriptHook_t &hook, HSCRIPT &cached, //----------------------------------------------------------------------------- void CWeaponCustomScripted::Spawn( void ) { -#ifdef CLIENT_DLL - if (m_iszClientScripts[0] != '\0' && ValidateScriptScope()) - { - RunScriptFile( m_iszClientScripts ); - } -#endif - BaseClass::Spawn(); } @@ -334,7 +329,7 @@ void CWeaponCustomScripted::ItemPreFrame( void ) { SIMPLE_VOID_OVERRIDE( ItemPreFrame, NULL ); - BaseClass::ItemPostFrame(); + BaseClass::ItemPreFrame(); } void CWeaponCustomScripted::ItemPostFrame( void ) @@ -432,7 +427,7 @@ void CWeaponCustomScripted::SecondaryAttack( void ) // Purpose: //----------------------------------------------------------------------------- #define ACTIVITY_FUNC_OVERRIDE( name ) ScriptVariant_t retVal; \ - if (RunWeaponHook( g_Hook_##name, m_Func_##name, &retVal ) && retVal.m_bool == false) \ + if (RunWeaponHook( g_Hook_##name, m_Func_##name, &retVal ) && !retVal.IsNull()) \ { \ if (retVal.m_type == FIELD_INTEGER) \ { \ @@ -586,6 +581,36 @@ int CWeaponCustomScripted::WeaponMeleeAttack2Condition( float flDot, float flDis return BaseClass::WeaponMeleeAttack2Condition( flDot, flDist ); } + +struct VScriptWeaponCustomData_s +{ + char cScripts[256]; + + bool Parse(KeyValues* pKVWeapon) + { + Q_strncpy(cScripts, pKVWeapon->GetString("vscript_file"), 256); + return true; + } +}; + +DEFINE_CUSTOM_WEAPON_FACTORY(vscript, CWeaponCustomScripted, VScriptWeaponCustomData_s); +void CWeaponCustomScripted::InitCustomWeaponFromData(const void* pData, const char* pszWeaponScript) +{ + Q_FileBase(pszWeaponScript, m_iszWeaponScriptName.GetForModify(), 256); + Q_strncpy(m_iszClientScripts.GetForModify(), static_cast (pData)->cScripts, 256); +} + +extern ConVar sv_script_think_interval; +#else +void CWeaponCustomScripted::OnDataChanged(DataUpdateType_t type) +{ + BaseClass::OnDataChanged(type); + + if (!m_ScriptScope.IsInitialized()) + { + RunVScripts(); + } +} #endif //----------------------------------------------------------------------------- @@ -604,3 +629,96 @@ int CWeaponCustomScripted::ActivityListCount( void ) return BaseClass::ActivityListCount(); } + +void CWeaponCustomScripted::RunVScripts() +{ +#ifdef CLIENT_DLL + if (m_iszClientScripts[0] != '\0' && ValidateScriptScope()) + { + RunScriptFile(m_iszClientScripts); + } +#else + if (m_iszVScripts == NULL_STRING && m_iszClientScripts[0] == '\0') + { + return; + } + +#ifdef MAPBASE_VSCRIPT + if (g_pScriptVM == NULL) + { + return; + } +#endif + + ValidateScriptScope(); + + // All functions we want to have call chained instead of overwritten + // by other scripts in this entities list. + static const char* sCallChainFunctions[] = + { + "OnPostSpawn", + "Precache" + }; + + ScriptLanguage_t language = g_pScriptVM->GetLanguage(); + + // Make a call chainer for each in this entities scope + for (int j = 0; j < ARRAYSIZE(sCallChainFunctions); ++j) + { + + if (language == SL_PYTHON) + { + // UNDONE - handle call chaining in python + ; + } + else if (language == SL_SQUIRREL) + { + //TODO: For perf, this should be precompiled and the %s should be passed as a parameter + HSCRIPT hCreateChainScript = g_pScriptVM->CompileScript(CFmtStr("%sCallChain <- CSimpleCallChainer(\"%s\", self.GetScriptScope(), true)", sCallChainFunctions[j], sCallChainFunctions[j])); + g_pScriptVM->Run(hCreateChainScript, (HSCRIPT)m_ScriptScope); + } + } + + CUtlStringList szScripts; + if (m_iszVScripts != NULL_STRING) + { + V_SplitString(STRING(m_iszVScripts), " ", szScripts); + } + + if (m_iszClientScripts[0] != '\0') + { + szScripts.AddToHead(strdup(m_iszClientScripts.Get())); + } + + for (int i = 0; i < szScripts.Count(); i++) + { +#ifdef MAPBASE + CGMsg(0, CON_GROUP_VSCRIPT, "%s executing script: %s\n", GetDebugName(), szScripts[i]); +#else + Log("%s executing script: %s\n", GetDebugName(), szScripts[i]); +#endif + + RunScriptFile(szScripts[i], IsWorld()); + + for (int j = 0; j < ARRAYSIZE(sCallChainFunctions); ++j) + { + if (language == SL_PYTHON) + { + // UNDONE - handle call chaining in python + ; + } + else if (language == SL_SQUIRREL) + { + //TODO: For perf, this should be precompiled and the %s should be passed as a parameter. + HSCRIPT hRunPostScriptExecute = g_pScriptVM->CompileScript(CFmtStr("%sCallChain.PostScriptExecute()", sCallChainFunctions[j])); + g_pScriptVM->Run(hRunPostScriptExecute, (HSCRIPT)m_ScriptScope); + } + } + } + + if (m_iszScriptThinkFunction != NULL_STRING) + { + SetContextThink(&CBaseEntity::ScriptThink, gpGlobals->curtime + sv_script_think_interval.GetFloat(), "ScriptThink"); + } +#endif +} \ No newline at end of file diff --git a/sp/src/game/shared/mapbase/weapon_custom_scripted.h b/sp/src/game/shared/mapbase/weapon_custom_scripted.h index 924f1031..c227e35c 100644 --- a/sp/src/game/shared/mapbase/weapon_custom_scripted.h +++ b/sp/src/game/shared/mapbase/weapon_custom_scripted.h @@ -14,6 +14,8 @@ #include "basecombatweapon_shared.h" #ifdef CLIENT_DLL #include "vscript_client.h" +#else +#include "mapbase/custom_weapon_factory.h" #endif // The base class of the scripted weapon is game-specific. @@ -32,6 +34,9 @@ HSCRIPT m_Func_##name; class CWeaponCustomScripted : public SCRIPTED_WEAPON_DERIVED_FROM +#ifndef CLIENT_DLL + , public ICustomWeapon +#endif // !CLIENT_DLL { public: DECLARE_CLASS( CWeaponCustomScripted, SCRIPTED_WEAPON_DERIVED_FROM ); @@ -45,6 +50,8 @@ public: bool KeyValue( const char *szKeyName, const char *szValue ); bool GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ); + void RunVScripts(); + // Base script has a function for this //void Precache( void ); @@ -106,6 +113,11 @@ public: int WeaponRangeAttack2Condition( float flDot, float flDist ); int WeaponMeleeAttack1Condition( float flDot, float flDist ); int WeaponMeleeAttack2Condition( float flDot, float flDist ); + + // Inherited via ICustomWeapon + virtual void InitCustomWeaponFromData(const void* pData, const char* pszWeaponScript); +#else + void OnDataChanged(DataUpdateType_t type); #endif ALLOW_SCRIPT_ACCESS(); diff --git a/sp/src/game/shared/vscript_shared.cpp b/sp/src/game/shared/vscript_shared.cpp index 9f1d610c..8ab2ac97 100644 --- a/sp/src/game/shared/vscript_shared.cpp +++ b/sp/src/game/shared/vscript_shared.cpp @@ -453,8 +453,7 @@ void RunAddonScripts() // mapspawn_addon char fullpath[MAX_PATH]; - Q_snprintf( fullpath, sizeof( fullpath ), "%sscripts/vscripts/mapspawn_addon", path ); - Q_FixSlashes( fullpath ); + Q_ComposeFileName( path, "scripts/vscripts/mapspawn_addon", fullpath, sizeof( fullpath ) ); VScriptRunScriptAbsolute( fullpath, NULL, false, folderName ); } diff --git a/sp/src/game/shared/weapon_parse.cpp b/sp/src/game/shared/weapon_parse.cpp index d8844916..872c6ad0 100644 --- a/sp/src/game/shared/weapon_parse.cpp +++ b/sp/src/game/shared/weapon_parse.cpp @@ -408,6 +408,7 @@ FileWeaponInfo_t::FileWeaponInfo_t() m_flSwaySpeedScale = 1.0f; szDroppedModel[0] = 0; m_bUsesHands = false; + m_nWeaponRestriction = WPNRESTRICT_NONE; #endif } @@ -415,6 +416,14 @@ FileWeaponInfo_t::FileWeaponInfo_t() extern ConVar hud_fastswitch; #endif +#ifdef MAPBASE +const char* pWeaponRestrictions[NUM_WEAPON_RESTRICTION_TYPES] = { + "none", + "player_only", + "npc_only", +}; +#endif // MAPBASE + void FileWeaponInfo_t::Parse( KeyValues *pKeyValuesData, const char *szWeaponName ) { // Okay, we tried at least once to look this up... @@ -483,6 +492,19 @@ void FileWeaponInfo_t::Parse( KeyValues *pKeyValuesData, const char *szWeaponNam Q_strncpy( szDroppedModel, pKeyValuesData->GetString( "droppedmodel" ), MAX_WEAPON_STRING ); m_bUsesHands = ( pKeyValuesData->GetInt( "uses_hands", 0 ) != 0 ) ? true : false; + + const char* pszRestrictString = pKeyValuesData->GetString("usage_restriction", nullptr); + if (pszRestrictString) + { + for (int i = 0; i < NUM_WEAPON_RESTRICTION_TYPES; i++) + { + if (V_stricmp(pszRestrictString, pWeaponRestrictions[i]) == 0) + { + m_nWeaponRestriction = i; + break; + } + } + } #endif #ifndef MAPBASE // Mapbase makes weapons in the same slot & position swap each other out, which is a feature mods can intentionally use. diff --git a/sp/src/game/shared/weapon_parse.h b/sp/src/game/shared/weapon_parse.h index 28032744..4ba7b02c 100644 --- a/sp/src/game/shared/weapon_parse.h +++ b/sp/src/game/shared/weapon_parse.h @@ -58,6 +58,17 @@ int GetWeaponSoundFromString( const char *pszString ); class CHudTexture; class KeyValues; +#ifdef MAPBASE +enum WeaponUsageRestricions_e +{ + WPNRESTRICT_NONE = 0, + WPNRESTRICT_PLAYER_ONLY, + WPNRESTRICT_NPCS_ONLY, + + NUM_WEAPON_RESTRICTION_TYPES +}; +#endif // MAPBASE + //----------------------------------------------------------------------------- // Purpose: Contains the data read from the weapon's script file. // It's cached so we only read each weapon's script file once. @@ -125,6 +136,8 @@ public: char szDroppedModel[MAX_WEAPON_STRING]; // Model of this weapon when dropped on the ground bool m_bUsesHands; + + int m_nWeaponRestriction; #endif // CLIENT DLL diff --git a/sp/src/public/soundflags.h b/sp/src/public/soundflags.h index 71ae1192..9fab2308 100644 --- a/sp/src/public/soundflags.h +++ b/sp/src/public/soundflags.h @@ -102,8 +102,22 @@ enum soundlevel_t #define MAX_SNDLVL_VALUE ((1< 50) ? (20.0f / (float)(a - 50)) : 4.0 ) +inline soundlevel_t ATTN_TO_SNDLVL(float a) +{ + soundlevel_t sndlvl = soundlevel_t::SNDLVL_NONE; + + if (a >= 0.0f) + { + sndlvl = soundlevel_t(float(soundlevel_t::SNDLVL_50dB) + float(soundlevel_t::SNDLVL_20dB) / a); + } + + return sndlvl; +} + +inline float SNDLVL_TO_ATTN(soundlevel_t s) +{ + return (s > soundlevel_t::SNDLVL_50dB)? (20.0f / float(s - soundlevel_t::SNDLVL_50dB)) : 4.0f; +} // This is a limit due to network encoding. // It encodes attenuation * 64 in 8 bits, so the maximum is (255 / 64) diff --git a/sp/src/public/tier0/platform.h b/sp/src/public/tier0/platform.h index 6d5751a8..457e5ec9 100644 --- a/sp/src/public/tier0/platform.h +++ b/sp/src/public/tier0/platform.h @@ -1281,8 +1281,8 @@ PLATFORM_INTERFACE bool Is64BitOS(); //----------------------------------------------------------------------------- // General Mapbase version constants compiled into projects for versioning purposes //----------------------------------------------------------------------------- -#define MAPBASE_VERSION "7.1" -#define MAPBASE_VER_INT 7100 // For use in #if in a similar fashion to macros like _MSC_VER +#define MAPBASE_VERSION "7.2" +#define MAPBASE_VER_INT 7200 // For use in #if in a similar fashion to macros like _MSC_VER #endif diff --git a/sp/src/public/vgui_controls/ImagePanel.h b/sp/src/public/vgui_controls/ImagePanel.h index bba8f88b..f4f5ce85 100644 --- a/sp/src/public/vgui_controls/ImagePanel.h +++ b/sp/src/public/vgui_controls/ImagePanel.h @@ -61,7 +61,11 @@ public: int GetNumFrames(); void SetFrame( int nFrame ); +#ifdef MAPBASE + void SetRotation( int iRotation ); +#else void SetRotation( int iRotation ) { m_iRotation = iRotation; } +#endif protected: virtual void PaintBackground(); diff --git a/sp/src/vgui2/vgui_controls/AnimationController.cpp b/sp/src/vgui2/vgui_controls/AnimationController.cpp index bc2b1774..ba51d93c 100644 --- a/sp/src/vgui2/vgui_controls/AnimationController.cpp +++ b/sp/src/vgui2/vgui_controls/AnimationController.cpp @@ -33,7 +33,7 @@ static CUtlSymbolTable g_ScriptSymbols(0, 128, true); #ifdef MAPBASE // Allows animation sequences to be overridden by map-specific files -extern bool g_bUsingCustomHudAnimations; +bool g_bUsingCustomHudAnimations = false; #endif // singleton accessor for animation controller for use by the vgui controls diff --git a/sp/src/vgui2/vgui_controls/ImagePanel.cpp b/sp/src/vgui2/vgui_controls/ImagePanel.cpp index 1a77bd2a..e045666e 100644 --- a/sp/src/vgui2/vgui_controls/ImagePanel.cpp +++ b/sp/src/vgui2/vgui_controls/ImagePanel.cpp @@ -73,6 +73,13 @@ void ImagePanel::OnSizeChanged(int newWide, int newTall) //----------------------------------------------------------------------------- void ImagePanel::SetImage(IImage *image) { +#ifdef MAPBASE + if ( image ) + { + image->SetRotation( m_iRotation ); + } +#endif + m_pImage = image; Repaint(); } @@ -471,3 +478,15 @@ void ImagePanel::SetFrame( int nFrame ) return m_pImage->SetFrame( nFrame ); } + +#ifdef MAPBASE +void ImagePanel::SetRotation( int iRotation ) +{ + m_iRotation = iRotation; + + if ( m_pImage ) + { + m_pImage->SetRotation( m_iRotation ); + } +} +#endif diff --git a/sp/src/vscript/vscript_bindings_base.cpp b/sp/src/vscript/vscript_bindings_base.cpp index 4f20234a..99b6194a 100644 --- a/sp/src/vscript/vscript_bindings_base.cpp +++ b/sp/src/vscript/vscript_bindings_base.cpp @@ -206,19 +206,28 @@ void CScriptKeyValues::ScriptReleaseKeyValues( ) m_pKeyValues = NULL; } -void CScriptKeyValues::TableToSubKeys( HSCRIPT hTable ) +void KeyValues_TableToSubKeys( HSCRIPT hTable, KeyValues *pKV ) { int nIterator = -1; ScriptVariant_t varKey, varValue; while ((nIterator = g_pScriptVM->GetKeyValue( hTable, nIterator, &varKey, &varValue )) != -1) { - switch (varValue.m_type) + if ( varKey.m_type == FIELD_CSTRING ) { - case FIELD_CSTRING: m_pKeyValues->SetString( varKey.m_pszString, varValue.m_pszString ); break; - case FIELD_INTEGER: m_pKeyValues->SetInt( varKey.m_pszString, varValue.m_int ); break; - case FIELD_FLOAT: m_pKeyValues->SetFloat( varKey.m_pszString, varValue.m_float ); break; - case FIELD_BOOLEAN: m_pKeyValues->SetBool( varKey.m_pszString, varValue.m_bool ); break; - case FIELD_VECTOR: m_pKeyValues->SetString( varKey.m_pszString, CFmtStr( "%f %f %f", varValue.m_pVector->x, varValue.m_pVector->y, varValue.m_pVector->z ) ); break; + switch ( varValue.m_type ) + { + case FIELD_CSTRING: pKV->SetString( varKey.m_pszString, varValue.m_pszString ); break; + case FIELD_INTEGER: pKV->SetInt( varKey.m_pszString, varValue.m_int ); break; + case FIELD_FLOAT: pKV->SetFloat( varKey.m_pszString, varValue.m_float ); break; + case FIELD_BOOLEAN: pKV->SetBool( varKey.m_pszString, varValue.m_bool ); break; + case FIELD_VECTOR: pKV->SetString( varKey.m_pszString, CFmtStr( "%f %f %f", varValue.m_pVector->x, varValue.m_pVector->y, varValue.m_pVector->z ) ); break; + case FIELD_HSCRIPT: + { + KeyValues *subKey = pKV->FindKey( varKey.m_pszString, true ); + KeyValues_TableToSubKeys( varValue, subKey ); + break; + } + } } g_pScriptVM->ReleaseValue( varKey ); @@ -226,19 +235,38 @@ void CScriptKeyValues::TableToSubKeys( HSCRIPT hTable ) } } -void CScriptKeyValues::SubKeysToTable( HSCRIPT hTable ) +void KeyValues_SubKeysToTable( KeyValues *pKV, HSCRIPT hTable ) { - FOR_EACH_SUBKEY( m_pKeyValues, key ) + FOR_EACH_SUBKEY( pKV, key ) { switch ( key->GetDataType() ) { case KeyValues::TYPE_STRING: g_pScriptVM->SetValue( hTable, key->GetName(), key->GetString() ); break; case KeyValues::TYPE_INT: g_pScriptVM->SetValue( hTable, key->GetName(), key->GetInt() ); break; case KeyValues::TYPE_FLOAT: g_pScriptVM->SetValue( hTable, key->GetName(), key->GetFloat() ); break; + case KeyValues::TYPE_NONE: + { + ScriptVariant_t subTable; + g_pScriptVM->CreateTable( subTable ); + g_pScriptVM->SetValue( hTable, key->GetName(), subTable ); + KeyValues_SubKeysToTable( key, subTable ); + g_pScriptVM->ReleaseValue( subTable ); + break; + } } } } +void CScriptKeyValues::TableToSubKeys( HSCRIPT hTable ) +{ + KeyValues_TableToSubKeys( hTable, m_pKeyValues ); +} + +void CScriptKeyValues::SubKeysToTable( HSCRIPT hTable ) +{ + KeyValues_SubKeysToTable( m_pKeyValues, hTable ); +} + HSCRIPT CScriptKeyValues::ScriptFindOrCreateKey( const char *pszName ) { KeyValues *pKeyValues = m_pKeyValues->FindKey(pszName, true); diff --git a/sp/src/vscript/vscript_squirrel.nut b/sp/src/vscript/vscript_squirrel.nut index 5b76bbe3..54492423 100644 --- a/sp/src/vscript/vscript_squirrel.nut +++ b/sp/src/vscript/vscript_squirrel.nut @@ -118,15 +118,14 @@ class CSimpleCallChainer function PostScriptExecute() { - local func; - try { - func = scope[prefix]; - } catch(e) { - return; + if ( prefix in scope ) + { + local func = scope[prefix]; + if ( typeof func == "function" ) + { + chain.push(func); + } } - if (typeof(func) != "function") - return; - chain.push(func); } function Call() @@ -569,4 +568,4 @@ if (developer) __Documentation.RegisterHelp( "Lerp", "float Lerp(float, float, float)", "" ); __Documentation.RegisterHelp( "SimpleSpline", "float SimpleSpline(float)", "" ); } -)vscript"; \ No newline at end of file +)vscript";