diff --git a/src/game/client/CMakeLists.txt b/src/game/client/CMakeLists.txt index 19c2458a4..85f50ab7f 100644 --- a/src/game/client/CMakeLists.txt +++ b/src/game/client/CMakeLists.txt @@ -473,6 +473,7 @@ target_sources_grouped( c_baseflex.cpp c_baseplayer.cpp c_baseviewmodel.cpp + c_buttons.cpp c_breakableprop.cpp c_colorcorrection.cpp c_colorcorrectionvolume.cpp @@ -856,6 +857,7 @@ target_sources_grouped( c_baseplayer.h c_basetempentity.h c_baseviewmodel.h + c_buttons.h c_breakableprop.h c_effects.h c_entitydissolve.h diff --git a/src/game/client/c_baseentity.h b/src/game/client/c_baseentity.h index 2200f6db1..0d60cf6f0 100644 --- a/src/game/client/c_baseentity.h +++ b/src/game/client/c_baseentity.h @@ -1120,7 +1120,11 @@ class C_BaseEntity : public IClientEntity // These methods encapsulate MOVETYPE_FOLLOW, which became obsolete void FollowEntity( CBaseEntity *pBaseEntity, bool bBoneMerge = true ); void StopFollowingEntity( ); // will also change to MOVETYPE_NONE +#ifdef NEO + virtual bool IsFollowingEntity(); +#else bool IsFollowingEntity(); +#endif // NEO CBaseEntity *GetFollowedEntity(); // For shadows rendering the correct body + sequence... diff --git a/src/game/client/c_baseplayer.cpp b/src/game/client/c_baseplayer.cpp index dabd3125b..665b6ec17 100644 --- a/src/game/client/c_baseplayer.cpp +++ b/src/game/client/c_baseplayer.cpp @@ -2660,6 +2660,19 @@ void C_BasePlayer::SetSwimSoundTime( float flSwimSoundTime ) //----------------------------------------------------------------------------- bool C_BasePlayer::IsUseableEntity( CBaseEntity *pEntity, unsigned int requiredCaps ) { +#ifdef NEO + if ( pEntity ) + { + int caps = pEntity->ObjectCaps(); + if ( caps & (FCAP_IMPULSE_USE|FCAP_CONTINUOUS_USE|FCAP_ONOFF_USE|FCAP_DIRECTIONAL_USE) ) + { + if ( (caps & requiredCaps) == requiredCaps ) + { + return true; + } + } + } +#endif // NEO return false; } diff --git a/src/game/client/c_baseplayer.h b/src/game/client/c_baseplayer.h index be8c78f15..332e921a2 100644 --- a/src/game/client/c_baseplayer.h +++ b/src/game/client/c_baseplayer.h @@ -134,7 +134,11 @@ class C_BasePlayer : public C_BaseCombatCharacter, public CGameEventListener virtual void AvoidPhysicsProps( CUserCmd *pCmd ); virtual void PlayerUse( void ); +#ifdef NEO + virtual CBaseEntity *FindUseEntity( void ); +#else CBaseEntity *FindUseEntity( void ); +#endif // NEO virtual bool IsUseableEntity( CBaseEntity *pEntity, unsigned int requiredCaps ); // Data handlers diff --git a/src/game/client/c_buttons.cpp b/src/game/client/c_buttons.cpp new file mode 100644 index 000000000..e16fe01ef --- /dev/null +++ b/src/game/client/c_buttons.cpp @@ -0,0 +1,15 @@ +#include "c_buttons.h" + +#include "tier0/memdbgon.h" + +#define SF_BUTTON_USE_ACTIVATES 1024 // Button fires when used. + +LINK_ENTITY_TO_CLASS(func_button, C_BaseButton); + +IMPLEMENT_CLIENTCLASS_DT( C_BaseButton, DT_BaseButton, CBaseButton ) + RecvPropInt( RECVINFO(m_spawnflags) ), +END_RECV_TABLE() + +int C_BaseButton::ObjectCaps(void) { + return BaseClass::ObjectCaps() | ((m_spawnflags & SF_BUTTON_USE_ACTIVATES) ? (FCAP_IMPULSE_USE | FCAP_USE_IN_RADIUS) : 0); +}; \ No newline at end of file diff --git a/src/game/client/c_buttons.h b/src/game/client/c_buttons.h new file mode 100644 index 000000000..1429579e5 --- /dev/null +++ b/src/game/client/c_buttons.h @@ -0,0 +1,14 @@ +#pragma once + +#include "c_baseentity.h" + +class C_BaseButton : public C_BaseEntity +{ +public: + DECLARE_CLASS(C_BaseButton, C_BaseEntity); + DECLARE_CLIENTCLASS(); + + int m_spawnflags; + + virtual int ObjectCaps(void) override; +}; \ No newline at end of file diff --git a/src/game/client/c_playerresource.cpp b/src/game/client/c_playerresource.cpp index 03e7ef1e5..42c107dc0 100644 --- a/src/game/client/c_playerresource.cpp +++ b/src/game/client/c_playerresource.cpp @@ -33,6 +33,7 @@ IMPLEMENT_CLIENTCLASS_DT_NOBASE(C_PlayerResource, DT_PlayerResource, CPlayerReso RecvPropArray3(RECVINFO_ARRAY(m_iStar), RecvPropInt(RECVINFO(m_iStar[0]))), RecvPropArray3(RECVINFO_ARRAY(m_szNeoClantag), RecvPropString(RECVINFO(m_szNeoClantag[0]))), RecvPropArray3(RECVINFO_ARRAY(m_iMaxHealth), RecvPropInt(RECVINFO(m_iMaxHealth[0]))), + RecvPropArray3(RECVINFO_ARRAY(m_bAfk), RecvPropInt(RECVINFO(m_bAfk[0]))), #endif RecvPropArray3( RECVINFO_ARRAY(m_iScore), RecvPropInt( RECVINFO(m_iScore[0]))), RecvPropArray3( RECVINFO_ARRAY(m_iDeaths), RecvPropInt( RECVINFO(m_iDeaths[0]))), @@ -57,6 +58,7 @@ BEGIN_PREDICTION_DATA( C_PlayerResource ) DEFINE_PRED_ARRAY(m_iStar, FIELD_INTEGER, MAX_PLAYERS_ARRAY_SAFE, FTYPEDESC_PRIVATE), DEFINE_PRED_ARRAY(m_szNeoClantag, FIELD_STRING, MAX_PLAYERS_ARRAY_SAFE, FTYPEDESC_PRIVATE), DEFINE_PRED_ARRAY(m_iMaxHealth, FIELD_INTEGER, MAX_PLAYERS_ARRAY_SAFE, FTYPEDESC_PRIVATE), + DEFINE_PRED_ARRAY(m_bAfk, FIELD_BOOLEAN, MAX_PLAYERS_ARRAY_SAFE, FTYPEDESC_PRIVATE), #endif DEFINE_PRED_ARRAY( m_iScore, FIELD_INTEGER, MAX_PLAYERS_ARRAY_SAFE, FTYPEDESC_PRIVATE ), DEFINE_PRED_ARRAY( m_iDeaths, FIELD_INTEGER, MAX_PLAYERS_ARRAY_SAFE, FTYPEDESC_PRIVATE ), @@ -94,6 +96,7 @@ C_PlayerResource::C_PlayerResource() memset(m_iStar, 0, sizeof(m_iStar)); memset(m_szNeoClantag, 0, sizeof(m_szNeoClantag)); memset(m_iMaxHealth, 1, sizeof(m_iMaxHealth)); + memset(m_bAfk, 0, sizeof(m_bAfk)); #endif memset( m_iScore, 0, sizeof( m_iScore ) ); memset( m_iDeaths, 0, sizeof( m_iDeaths ) ); @@ -470,6 +473,14 @@ int C_PlayerResource::GetDisplayedHealth(int iIndex, int mode) return GetHealth(iIndex); } } + +bool C_PlayerResource::IsAfk(int iIndex) +{ + if ( !IsConnected( iIndex ) && !IsValid( iIndex ) ) + return false; + + return m_bAfk[iIndex]; +} #endif const Color &C_PlayerResource::GetTeamColor(int index_ ) diff --git a/src/game/client/c_playerresource.h b/src/game/client/c_playerresource.h index 13402ce81..12ff142aa 100644 --- a/src/game/client/c_playerresource.h +++ b/src/game/client/c_playerresource.h @@ -61,6 +61,7 @@ public : // IGameResources interface const char *GetClanTag(int index); virtual int GetMaxHealth(int index); virtual int GetDisplayedHealth(int index, int mode); + virtual bool IsAfk(int index); #endif virtual int GetFrags( int index ); virtual int GetHealth( int index ); @@ -89,6 +90,7 @@ public : // IGameResources interface int m_iStar[MAX_PLAYERS_ARRAY_SAFE]; char m_szNeoClantag[MAX_PLAYERS_ARRAY_SAFE][NEO_MAX_CLANTAG_LENGTH]; int m_iMaxHealth[MAX_PLAYERS_ARRAY_SAFE]; + bool m_bAfk[MAX_PLAYERS_ARRAY_SAFE]; #endif int m_iScore[MAX_PLAYERS_ARRAY_SAFE]; int m_iDeaths[MAX_PLAYERS_ARRAY_SAFE]; diff --git a/src/game/client/glow_outline_effect.cpp b/src/game/client/glow_outline_effect.cpp index 5b68d827b..5e3c89d1b 100644 --- a/src/game/client/glow_outline_effect.cpp +++ b/src/game/client/glow_outline_effect.cpp @@ -35,6 +35,18 @@ ConVar glow_outline_effect_width( "glow_outline_effect_width", "1.f", FCVAR_ARCH ConVar glow_outline_effect_alpha( "glow_outline_effect_alpha", "0.5f", FCVAR_ARCHIVE, "Alpha of glow outline effect.", true, 0.f, true, 1.f); ConVar glow_outline_effect_center_alpha("glow_outline_effect_center_alpha", "0.1f", FCVAR_ARCHIVE, "Opacity of the part of the glow effect drawn on top of the player model when obstructed", true, 0.f, true, 1.f); ConVar glow_outline_effect_textured_center_alpha("glow_outline_effect_textured_center_alpha", "0.2f", FCVAR_ARCHIVE, "Opacity of the part of the glow effect drawn on top of the player model when cloaked", true, 0.f, true, 1.f); +ConVar cl_neo_hud_context_hint_highlight_object("cl_neo_hud_context_hint_highlight_object", "1", FCVAR_ARCHIVE, "Highlight interactible object", true, 0.f, true, 1.f, + [](IConVar* var, const char* pOldValue, float flOldValue)->void{ + if (!cl_neo_hud_context_hint_highlight_object.GetBool()) + g_GlowObjectManager.ClearUseItemObject(); + return; + }); +ConVar cl_neo_hud_context_hint_highlight_player("cl_neo_hud_context_hint_highlight_player", "1", FCVAR_ARCHIVE, "Highlight interactible players", true, 0.f, true, 1.f, + [](IConVar* var, const char* pOldValue, float flOldValue)->void{ + if (!cl_neo_hud_context_hint_highlight_player.GetBool()) + g_GlowObjectManager.ClearUseItemPlayer(); + return; + }); #else ConVar glow_outline_effect_enable( "glow_outline_effect_enable", "0", 0, "Enable entity outline glow effects."); ConVar glow_outline_effect_width( "glow_outline_width", "10.0f", FCVAR_CHEAT, "Width of glow outline effect in screen space." ); @@ -81,7 +93,11 @@ void CGlowObjectManager::RenderGlowEffects( const CViewSetup *pSetup, int nSplit { if ( g_pMaterialSystemHardwareConfig->SupportsPixelShaders_2_0() ) { +#ifdef NEO + if ( glow_outline_effect_enable.GetBool() || cl_neo_hud_context_hint_highlight_object.GetBool() || cl_neo_hud_context_hint_highlight_player.GetBool() ) +#else if ( glow_outline_effect_enable.GetBool() ) +#endif // NEO { CMatRenderContextPtr pRenderContext( materials ); @@ -156,6 +172,9 @@ void CGlowObjectManager::RenderGlowModels( const CViewSetup *pSetup, int nSplitS continue; #ifdef NEO + if ( i != m_GlowObjectDefinitions.Count() - 1 && useItem.m_hEntity.IsValid() && useItem.m_hEntity == m_GlowObjectDefinitions[i].m_hEntity) + continue; + // DrawModel can call ForcedMaterialOverride also g_pStudioRender->ForcedMaterialOverride(pMatGlowColor); #endif @@ -201,11 +220,24 @@ void CGlowObjectManager::ApplyEntityGlowEffects( const CViewSetup *pSetup, int n int iNumGlowObjects = 0; +#ifdef NEO + constexpr int INVALID_USEELEMENT_INDEX = -1; + int useElementIndex = INVALID_USEELEMENT_INDEX; + if (useItem.m_hEntity.IsValid()) + { + useElementIndex = m_GlowObjectDefinitions.AddToTail(useItem); + } +#endif // NEO for ( int i = 0; i < m_GlowObjectDefinitions.Count(); ++ i ) { if ( m_GlowObjectDefinitions[i].IsUnused() || !m_GlowObjectDefinitions[i].ShouldDraw( nSplitScreenSlot ) ) continue; +#ifdef NEO + if (i != useElementIndex && useItem.m_hEntity == m_GlowObjectDefinitions[i].m_hEntity) + continue; +#endif // NEO + if ( m_GlowObjectDefinitions[i].m_bRenderWhenOccluded || m_GlowObjectDefinitions[i].m_bRenderWhenUnoccluded ) { if ( m_GlowObjectDefinitions[i].m_bRenderWhenOccluded && m_GlowObjectDefinitions[i].m_bRenderWhenUnoccluded ) @@ -280,6 +312,11 @@ void CGlowObjectManager::ApplyEntityGlowEffects( const CViewSetup *pSetup, int n { if ( m_GlowObjectDefinitions[i].IsUnused() || !m_GlowObjectDefinitions[i].ShouldDraw( nSplitScreenSlot ) ) continue; + +#ifdef NEO + if (i != useElementIndex && useItem.m_hEntity == m_GlowObjectDefinitions[i].m_hEntity) + continue; +#endif // NEO if ( m_GlowObjectDefinitions[i].m_bRenderWhenOccluded && !m_GlowObjectDefinitions[i].m_bRenderWhenUnoccluded ) { @@ -316,7 +353,17 @@ void CGlowObjectManager::ApplyEntityGlowEffects( const CViewSetup *pSetup, int n // this fixes a bug where if there are glow objects in the list, but none of them are glowing, // the whole screen blooms. if ( iNumGlowObjects <= 0 ) +#ifdef NEO + { + if (useElementIndex != INVALID_USEELEMENT_INDEX) + { + m_GlowObjectDefinitions.Remove(useElementIndex); + } return; + } +#else + return; +#endif // NEO //============================================= // Render the glow colors to _rt_FullFrameFB @@ -325,6 +372,12 @@ void CGlowObjectManager::ApplyEntityGlowEffects( const CViewSetup *pSetup, int n PIXEvent pixEvent( pRenderContext, "RenderGlowModels" ); RenderGlowModels( pSetup, nSplitScreenSlot, pRenderContext ); } +#ifdef NEO + if (useElementIndex != INVALID_USEELEMENT_INDEX) + { + m_GlowObjectDefinitions.Remove(useElementIndex); + } +#endif // NEO // Get viewport #ifndef NEO diff --git a/src/game/client/glow_outline_effect.h b/src/game/client/glow_outline_effect.h index 625fef866..955cb902c 100644 --- a/src/game/client/glow_outline_effect.h +++ b/src/game/client/glow_outline_effect.h @@ -22,6 +22,11 @@ class CMatRenderContextPtr; static const int GLOW_FOR_ALL_SPLIT_SCREEN_SLOTS = -1; +#ifdef NEO +extern ConVar cl_neo_hud_context_hint_highlight_object; +extern ConVar cl_neo_hud_context_hint_highlight_player; +#endif // NEO + class CGlowObjectManager { public: @@ -121,6 +126,39 @@ class CGlowObjectManager Assert( !m_GlowObjectDefinitions[nGlowObjectHandle].IsUnused() ); m_GlowObjectDefinitions[nGlowObjectHandle].m_bUseTexturedHighlight = useTexturedHighlight; } + + void SetUseItem( C_BaseEntity *pEntity, const Vector &vGlowColor = Vector( 1.0f, 1.0f, 1.0f ), float flGlowAlpha = 1.0f, bool bRenderWhenOccluded = false, bool bRenderWhenUnoccluded = false, int nSplitScreenSlot = GLOW_FOR_ALL_SPLIT_SCREEN_SLOTS ) + { + if (pEntity->IsPlayer() && !cl_neo_hud_context_hint_highlight_player.GetBool()) + return; + else if (!pEntity->IsPlayer() && !cl_neo_hud_context_hint_highlight_object.GetBool()) + return; + + useItem.m_hEntity = pEntity; + useItem.m_vGlowColor = vGlowColor; + useItem.m_flGlowAlpha = flGlowAlpha; + useItem.m_bRenderWhenOccluded = bRenderWhenOccluded; + useItem.m_bRenderWhenUnoccluded = bRenderWhenUnoccluded; + useItem.m_nSplitScreenSlot = nSplitScreenSlot; + useItem.m_nNextFreeSlot = GlowObjectDefinition_t::ENTRY_IN_USE; + } + + void ClearUseItem() + { + useItem.m_hEntity = INVALID_EHANDLE; + } + void ClearUseItemObject() + { + C_BaseEntity* pEntity = useItem.m_hEntity.Get(); + if (pEntity && !pEntity->IsPlayer()) + useItem.m_hEntity = INVALID_EHANDLE; + } + void ClearUseItemPlayer() + { + C_BaseEntity* pEntity = useItem.m_hEntity.Get(); + if (pEntity && pEntity->IsPlayer()) + useItem.m_hEntity = INVALID_EHANDLE; + } #endif // NEO private: @@ -162,6 +200,10 @@ class CGlowObjectManager CUtlVector< GlowObjectDefinition_t > m_GlowObjectDefinitions; int m_nFirstFreeSlot; + +#ifdef NEO + GlowObjectDefinition_t useItem; +#endif // NEO }; extern CGlowObjectManager g_GlowObjectManager; diff --git a/src/game/client/neo/c_neo_player.cpp b/src/game/client/neo/c_neo_player.cpp index c473a9979..ead4529b1 100644 --- a/src/game/client/neo/c_neo_player.cpp +++ b/src/game/client/neo/c_neo_player.cpp @@ -1461,11 +1461,6 @@ void C_NEO_Player::TeamChange(int iNewTeam) #ifdef GLOWS_ENABLE void C_NEO_Player::UpdateGlowEffects(int iNewTeam) { - if (!glow_outline_effect_enable.GetBool() || NEORules()->GetHiddenHudElements() & NEO_HUD_ELEMENT_FRIENDLY_MARKER) - { - return; - } - auto updateGlowColour = [](C_BasePlayer* pPlayer, int iTeam = 0) { float r, g, b; NEORules()->GetTeamGlowColor(iTeam ? iTeam : pPlayer->GetTeamNumber(), r, g, b); @@ -1479,7 +1474,7 @@ void C_NEO_Player::UpdateGlowEffects(int iNewTeam) continue; } - if (pPlayer->GetTeamNumber() == TEAM_SPECTATOR || pPlayer->GetTeamNumber() == TEAM_UNASSIGNED) + if (pPlayer->GetTeamNumber() == TEAM_SPECTATOR || pPlayer->GetTeamNumber() == TEAM_UNASSIGNED || !glow_outline_effect_enable.GetBool() || NEORules()->GetHiddenHudElements() & NEO_HUD_ELEMENT_FRIENDLY_MARKER) { pPlayer->SetClientSideGlowEnabled(false); continue; @@ -1495,7 +1490,7 @@ void C_NEO_Player::UpdateGlowEffects(int iNewTeam) } } else { - if (iNewTeam == TEAM_SPECTATOR || iNewTeam == TEAM_UNASSIGNED) + if (iNewTeam == TEAM_SPECTATOR || iNewTeam == TEAM_UNASSIGNED || !glow_outline_effect_enable.GetBool() || NEORules()->GetHiddenHudElements() & NEO_HUD_ELEMENT_FRIENDLY_MARKER) { SetClientSideGlowEnabled(false); return; @@ -2104,3 +2099,41 @@ const char* C_NEO_Player::GetPlayerNameWithTakeoverContext(int player_index) return base_name; } +C_NEO_Player* C_NEO_Player::PlayerUseTraceLine() +{ + // Select player under cursor + Vector eyePos = EyePosition(); + Vector forward; + EyeVectors( &forward ); + Vector traceEnd = eyePos + forward * MAX_COORD_RANGE; + + // MASK_SHOT_HULL to match friendly fire warning trace + trace_t tr; + UTIL_TraceLine( eyePos, traceEnd, MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, &tr ); + + if (tr.DidHit() && tr.m_pEnt) + { + return ToNEOPlayer(tr.m_pEnt); + } + return nullptr; +} + +void C_NEO_Player::PlayerUse() +{ + BaseClass::PlayerUse(); + + // Was use pressed or released? + if ( ! ((m_nButtons | m_afButtonPressed | m_afButtonReleased) & IN_USE) ) + return; + + if ( (m_afButtonPressed & IN_USE) && prediction->IsFirstTimePredicted() && !GetUseEntity()) + { + if (C_NEO_Player* pTargetPlayer = PlayerUseTraceLine(); + pTargetPlayer ) + { + m_Local.m_nOldButtons |= IN_USE; + m_afButtonPressed &= ~IN_USE; + engine->ExecuteClientCmd(VarArgs("useplayer %i", pTargetPlayer->entindex())); + } + } +} diff --git a/src/game/client/neo/c_neo_player.h b/src/game/client/neo/c_neo_player.h index 590769b11..2768a700d 100644 --- a/src/game/client/neo/c_neo_player.h +++ b/src/game/client/neo/c_neo_player.h @@ -75,6 +75,7 @@ class C_NEO_Player : public C_HL2MP_Player IRagdoll* GetRepresentativeRagdoll() const; virtual void CalcView( Vector &eyeOrigin, QAngle &eyeAngles, float &zNear, float &zFar, float &fov ); virtual const QAngle& EyeAngles( void ); + virtual CBaseEntity* FindUseEntity() override; virtual void ModifyFireBulletsDamage(CTakeDamageInfo* dmgInfo) OVERRIDE; @@ -188,7 +189,10 @@ class C_NEO_Player : public C_HL2MP_Player #ifdef GLOWS_ENABLE void UpdateGlowEffects(int iNewTeam); #endif // GLOWS_ENABLE - + C_NEO_Player* PlayerUseTraceLine(); + virtual void PlayerUse() override; + + bool ValidTakeoverTargetFor(CNEO_Player* pPlayerTakingOver); private: char m_sNameWithTakeoverContextProcessingBuffer[MAX_PLAYER_NAME_LENGTH]; @@ -201,6 +205,10 @@ class C_NEO_Player : public C_HL2MP_Player bool IsAllowedToSuperJump(void); + // Spectator takeover player related functionality + bool IsAFK() const; + bool IsFakePlayer() const; + public: CNetworkVar(bool, m_bShowTestMessage); CNetworkString(m_pszTestMessage, 32 * 2 + 1); diff --git a/src/game/client/neo/ui/neo_hud_context_hint.cpp b/src/game/client/neo/ui/neo_hud_context_hint.cpp index dfb597545..b6043c1c2 100644 --- a/src/game/client/neo/ui/neo_hud_context_hint.cpp +++ b/src/game/client/neo/ui/neo_hud_context_hint.cpp @@ -3,6 +3,7 @@ #include "iclientmode.h" #include "c_neo_player.h" +#include "neo_player_shared.h" #include "vgui/ISurface.h" #include "igameresources.h" #include "ienginevgui.h" @@ -10,6 +11,8 @@ #include "vgui/IPanel.h" #include "vgui_controls/AnimationController.h" #include "neo_root_settings.h" +#include "glow_outline_effect.h" +#include "smoke_fog_overlay.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -22,7 +25,7 @@ ConVar cl_neo_spec_takeover_player_hint_time_sec("cl_neo_spec_takeover_player_hi DECLARE_HUDELEMENT(CNEOHud_ContextHint); -NEO_HUD_ELEMENT_DECLARE_FREQ_CVAR(ContextHint, 0.00695); +NEO_HUD_ELEMENT_DECLARE_FREQ_CVAR(ContextHint, 0.1); CNEOHud_ContextHint::CNEOHud_ContextHint(const char* pElementName) : CNEOHud_ChildElement(), CHudElement(pElementName), vgui::EditablePanel(NULL, "neo_context_hint") @@ -30,8 +33,7 @@ CNEOHud_ContextHint::CNEOHud_ContextHint(const char* pElementName) SetParent(g_pClientMode->GetViewport()); SetVisible(false); - m_flDisplayTime = 0.0f; - m_bHintShownForCurrentSpecTarget = false; + m_flDisplayEndTime = 0.0f; m_hLastSpecTarget = nullptr; } @@ -49,8 +51,7 @@ void CNEOHud_ContextHint::VidInit() void CNEOHud_ContextHint::Reset() { - m_flDisplayTime = 0.0f; - m_bHintShownForCurrentSpecTarget = false; + m_flDisplayEndTime = 0.0f; m_hLastSpecTarget = nullptr; SetVisible(false); } @@ -70,88 +71,206 @@ bool CNEOHud_ContextHint::ShouldDraw() { if (!cl_neo_hud_context_hint_enabled.GetBool()) { + g_GlowObjectManager.ClearUseItem(); return false; } - C_BasePlayer* pLocalPlayer = C_BasePlayer::GetLocalPlayer(); - if (!pLocalPlayer) - { - return false; - } + return true; +} - C_BaseEntity* pObserverTargetEntity = pLocalPlayer->GetObserverTarget(); - C_BasePlayer* pObserverTargetPlayer = (pObserverTargetEntity && pObserverTargetEntity->IsPlayer()) ? ToBasePlayer(pObserverTargetEntity) : nullptr; +extern ConVar sv_neo_spec_replace_player_bot_enable; +extern ConVar sv_neo_spec_replace_player_min_exp; +extern ConVar sv_neo_bot_cmdr_enable; +ConVar cl_neo_hud_context_hint_show_player_takeover_hint("cl_neo_hud_context_hint_show_player_takeover_hint", "1", FCVAR_ARCHIVE, "Show player takeover hint", true, 0.f, true, 1.f); +ConVar cl_neo_hud_context_hint_show_object_interact_hint("cl_neo_hud_context_hint_show_object_interact_hint", "1", FCVAR_ARCHIVE, "Show object inteact hint", true, 0.f, true, 1.f); +ConVar cl_neo_hud_context_hint_show_bot_interact_hint("cl_neo_hud_context_hint_show_bot_interact_hint", "1", FCVAR_ARCHIVE, "Show bot command and weapon request hint", true, 0.f, true, 1.f); +void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() +{ + g_GlowObjectManager.ClearUseItem(); + m_hUseEntity = nullptr; - auto eObserverMode = pLocalPlayer->GetObserverMode(); - bool bIsSpectating = (eObserverMode == OBS_MODE_CHASE || eObserverMode == OBS_MODE_IN_EYE); + C_NEO_Player* pLocalNeoPlayer = C_NEO_Player::GetLocalNEOPlayer(); + if (!pLocalNeoPlayer) + return; - if (pObserverTargetPlayer != m_hLastSpecTarget.Get()) + char szUppercaseKeyBinding[16]; // Assuming keybinds won't exceed 15 characters + null terminator + const char* useKeyBinding = engine->Key_LookupBinding("+use"); + if (useKeyBinding && useKeyBinding[0] != '\0') { - m_bHintShownForCurrentSpecTarget = false; - m_hLastSpecTarget = pObserverTargetPlayer; + V_strncpy(szUppercaseKeyBinding, useKeyBinding, sizeof(szUppercaseKeyBinding)); + V_strupr(szUppercaseKeyBinding); + } + else + { + const char notBoundText[] = "+use unbound\0"; + COMPILE_TIME_ASSERT(sizeof(notBoundText) <= sizeof(szUppercaseKeyBinding)); + V_strncpy(szUppercaseKeyBinding, notBoundText, sizeof(szUppercaseKeyBinding)); } - bool bShouldDisplayBotTakeoverHint = false; - if (bIsSpectating && pObserverTargetPlayer && NEORules()->GetRoundStatus() != PostRound) + if (pLocalNeoPlayer->IsObserver()) { - if (GameResources()->IsFakePlayer(pObserverTargetPlayer->entindex())) + if (!cl_neo_hud_context_hint_show_player_takeover_hint.GetBool()) + return; + + // Takeover hint { - if (pLocalPlayer->InSameTeam(pObserverTargetPlayer) && NEORules()->IsTeamplay()) + bool showTakeOverHint = false; + if (auto eObserverMode = pLocalNeoPlayer->GetObserverMode(); + (eObserverMode == OBS_MODE_CHASE || eObserverMode == OBS_MODE_IN_EYE)) { - ConVar* pBotEnableCvar = g_pCVar->FindVar("sv_neo_spec_replace_player_bot_enable"); - bool bBotEnable = pBotEnableCvar ? pBotEnableCvar->GetBool() : false; - - if (bBotEnable) + if (C_NEO_Player* pObserverTargetPlayer = ToNEOPlayer(pLocalNeoPlayer->GetObserverTarget()); + pObserverTargetPlayer && pObserverTargetPlayer->ValidTakeoverTargetFor(pLocalNeoPlayer)) { - // Check that spectator's XP is not at concerning griefing levels - int localPlayerXP = GameResources()->GetXP(pLocalPlayer->entindex()); - ConVar* pMinExpCvar = g_pCVar->FindVar("sv_neo_spec_replace_player_min_exp"); - int minExp = pMinExpCvar ? pMinExpCvar->GetInt() : 0; - - bShouldDisplayBotTakeoverHint = (localPlayerXP >= minExp); + // update hint duration + if (pObserverTargetPlayer != m_hLastSpecTarget.Get()) + { + m_hLastSpecTarget = pObserverTargetPlayer; + V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"[%hs] Takeover player", szUppercaseKeyBinding); + m_flDisplayEndTime = gpGlobals->curtime + cl_neo_spec_takeover_player_hint_time_sec.GetFloat(); + } + + showTakeOverHint = true; } } + + if (!showTakeOverHint) + { + m_hLastSpecTarget = INVALID_EHANDLE; + m_flDisplayEndTime = gpGlobals->curtime; + } } } - - if (bShouldDisplayBotTakeoverHint) + else { - // If the hint has not been shown for the current target yet, start the timer. - if (!m_bHintShownForCurrentSpecTarget) + constexpr float ITEM_DISCOVERY_SMOKE_THRESHOLD = 0.8f; + SetAddUseEntitysToUseEntityList(true); + if (CBaseEntity *pUseEntity = pLocalNeoPlayer->FindUseEntity(); + SetAddUseEntitysToUseEntityList(false) && pUseEntity && (g_SmokeFogOverlayThermalOverride || g_SmokeFogOverlayAlpha < ITEM_DISCOVERY_SMOKE_THRESHOLD)) { - m_flDisplayTime = gpGlobals->curtime + cl_neo_spec_takeover_player_hint_time_sec.GetFloat(); - m_bHintShownForCurrentSpecTarget = true; + g_GlowObjectManager.SetUseItem(pUseEntity, Vector( 1.0f, 1.0f, 1.0f ), g_SmokeFogOverlayThermalOverride ? 1.0f : Max( 0.0f, 1.0f - g_SmokeFogOverlayAlpha), true, false); + m_hUseEntity = pUseEntity; + + if (!cl_neo_hud_context_hint_show_object_interact_hint.GetBool()) + return; + + // Weapon pickup hint + if (pUseEntity->IsBaseCombatWeapon()) + { + C_NEOBaseCombatWeapon* pNeoWeapon = static_cast(pUseEntity); + + // Ghost pickup hint + if (pNeoWeapon->GetNeoWepBits() & NEO_WEP_GHOST) + { + V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"[%hs] pickup the Ghost", szUppercaseKeyBinding); + m_flDisplayEndTime = gpGlobals->curtime + 1.f; + } + // Weapon pickup hint + else if (pNeoWeapon->CanBePickedUpByClass(pLocalNeoPlayer->GetClass())) + { + V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"[%hs] pickup %hs", szUppercaseKeyBinding, pNeoWeapon->GetPrintName()); + m_flDisplayEndTime = gpGlobals->curtime + 1.f; + } + else if (pLocalNeoPlayer->m_nButtons & IN_USE) + { + V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"Cannot pickup weapon"); + m_flDisplayEndTime = gpGlobals->curtime + 1.f; + m_hUseEntity = INVALID_EHANDLE; + } + else + { + g_GlowObjectManager.ClearUseItemObject(); + m_hUseEntity = INVALID_EHANDLE; + } + } + else + { + // Juggernaut hint + if (CNEO_Juggernaut* pJuggernaut = dynamic_cast(pUseEntity); + pJuggernaut) + { + m_flDisplayEndTime = gpGlobals->curtime + 1.f; + if (pJuggernaut->m_bLocked) + { + V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"JGR56 is locked"); + } + else if (C_NEO_Player* pNeoJuggernautPlayer = pJuggernaut->m_hHoldingPlayer.Get(); + pNeoJuggernautPlayer && pJuggernaut->m_bIsHolding) + { + g_GlowObjectManager.ClearUseItemPlayer(); + m_flDisplayEndTime = gpGlobals->curtime; + } + else + { + V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"Hold [%hs] boot into JGR56", szUppercaseKeyBinding); + } + } + // Some other useable entity + else + { + V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"[%hs] use", szUppercaseKeyBinding); + m_flDisplayEndTime = gpGlobals->curtime + 1.f; + } + } } - - // If the hint is displaying and the timer hasn't expired, keep displaying it. - if (gpGlobals->curtime < m_flDisplayTime) + else { - return true; + m_flDisplayEndTime = gpGlobals->curtime; + if (!cl_neo_hud_context_hint_show_bot_interact_hint.GetBool()) + return; + + // Bot command hint + { + if (C_NEO_Player* pTargetPlayer = pLocalNeoPlayer->PlayerUseTraceLine(); + pTargetPlayer + && GameResources()->IsFakePlayer(pTargetPlayer->entindex()) + && NEORules()->IsTeamplay() && pTargetPlayer->GetTeamNumber() == pLocalNeoPlayer->GetTeamNumber()) + { + Vector teamGlowColor { 1.f, 1.f, 1.f }; + NEORules()->GetTeamGlowColor(pTargetPlayer->GetTeamNumber(), teamGlowColor[0], teamGlowColor[1], teamGlowColor[2]); + g_GlowObjectManager.SetUseItem(pTargetPlayer, teamGlowColor, g_SmokeFogOverlayThermalOverride ? 1.0f : Max(0.0f, 1.0f - g_SmokeFogOverlayAlpha), false, true); + + m_flDisplayEndTime = gpGlobals->curtime + 1.f; + if (sv_neo_bot_cmdr_enable.GetBool()) + { + V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"[%hs] %hs %hs", szUppercaseKeyBinding, pTargetPlayer->m_hCommandingPlayer.Get() == pLocalNeoPlayer ? "release" : "command", pTargetPlayer->GetNeoPlayerName()); + } + else + { + V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"[%hs] request primary weapon", szUppercaseKeyBinding); // NEO TODO (Adam) network primary weapon so can print its name here? + } + } + } } } - - // If conditions are not met, or timer has expired, hide the hint. - return false; } -void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() +struct UseEntity { - const char* useKeyBinding = engine->Key_LookupBinding("+use"); - if (useKeyBinding && useKeyBinding[0] != '\0') - { - char szUppercaseKeyBinding[16]; // Assuming keybinds won't exceed 15 characters + null terminator - V_strncpy(szUppercaseKeyBinding, useKeyBinding, sizeof(szUppercaseKeyBinding)); - V_strupr(szUppercaseKeyBinding); - V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"[%hs] Control Bot", szUppercaseKeyBinding); - } - else - { - V_wcsncpy(m_wszHintText, L"Press Use To Control Bot", sizeof(m_wszHintText)); - } -} + CHandle entity = INVALID_EHANDLE; +}; + +// NEO NOTE (Adam) MAX_SPHERE_QUERY client side is half the size compared to server side? Might cause problems in context hint in extreme cases where item closest to cursor doesn't get highlighted but gets used +UseEntity useEntityList[MAX_SPHERE_QUERY] = {}; +int useEntityListLastIndex = -1; + +void SetUseEntityListEntry(int index, CBaseEntity* entity) +{ + if (index < 0 || index >= ARRAYSIZE(useEntityList)) + return; + useEntityList[index].entity = entity; + useEntityListLastIndex = index; +}; + +void ClearUseEntityListEntry() +{ + useEntityListLastIndex = -1; +}; void CNEOHud_ContextHint::DrawNeoHudElement() { + if (m_wszHintText[0] == L'\0') + return; + int iScrWide, iScrTall; vgui::surface()->GetScreenSize(iScrWide, iScrTall); @@ -163,13 +282,48 @@ void CNEOHud_ContextHint::DrawNeoHudElement() int iBoxX = (iScrWide - iBoxWide) / 2; int iBoxY = iScrTall * m_flBoxYFactor - iBoxTall / 2; + + int textX = iBoxX + m_iPaddingX; + int textY = iBoxY + m_iPaddingY; + int x = 0; + int y = 0; + const int size = iTextTall / 8; + + vgui::surface()->DrawSetColor(m_TextColor); + for (int i = 0; i <= useEntityListLastIndex; i++) + { + if (useEntityList[i].entity.IsValid()) + { + if (C_BaseEntity* entity = useEntityList[i].entity.Get()) + { + GetVectorInScreenSpace(entity->CollisionProp()->WorldSpaceCenter(), x, y); + vgui::surface()->DrawOutlinedCircle(x, y, size, 12); + } + } + } + + if (m_flDisplayEndTime <= gpGlobals->curtime) + return; + + if (m_hUseEntity.IsValid()) + { + C_BaseEntity* useEntity = m_hUseEntity.Get(); + if (useEntity) + { + GetVectorInScreenSpace(useEntity->CollisionProp()->WorldSpaceCenter(), textX, textY); + + // text position + textX += iTextTall / 2; + textY -= iTextTall / 1.85f; + } + } vgui::surface()->DrawSetColor(m_BoxColor); vgui::surface()->DrawFilledRect(iBoxX, iBoxY, iBoxX + iBoxWide, iBoxY + iBoxTall); vgui::surface()->DrawSetTextFont(m_hHintFont); vgui::surface()->DrawSetTextColor(m_TextColor); - vgui::surface()->DrawSetTextPos(iBoxX + m_iPaddingX, iBoxY + m_iPaddingY); + vgui::surface()->DrawSetTextPos(textX, textY); vgui::surface()->DrawPrintText(m_wszHintText, static_cast(wcslen(m_wszHintText))); } diff --git a/src/game/client/neo/ui/neo_hud_context_hint.h b/src/game/client/neo/ui/neo_hud_context_hint.h index 18894a225..944677b7f 100644 --- a/src/game/client/neo/ui/neo_hud_context_hint.h +++ b/src/game/client/neo/ui/neo_hud_context_hint.h @@ -34,11 +34,11 @@ class CNEOHud_ContextHint : public CNEOHud_ChildElement, public CHudElement, pub virtual void Paint() override; private: - wchar_t m_wszHintText[32]; + wchar_t m_wszHintText[64]; - float m_flDisplayTime; - bool m_bHintShownForCurrentSpecTarget; - CHandle m_hLastSpecTarget; + float m_flDisplayEndTime; + CHandle m_hLastSpecTarget; + CHandle m_hUseEntity; CPanelAnimationVar(vgui::HFont, m_hHintFont, "font", "NeoUINormal"); CPanelAnimationVarAliasType(int, m_iPaddingX, "padding_x", "4", "proportional_xpos"); @@ -48,4 +48,7 @@ class CNEOHud_ContextHint : public CNEOHud_ChildElement, public CHudElement, pub CPanelAnimationVar(Color, m_TextColor, "text_color", "255 255 255 255"); }; +void SetUseEntityListEntry(int index, C_BaseEntity* entity); +void ClearUseEntityListEntry(); + #endif // NEO_HUD_CONTEXT_HINT_H \ No newline at end of file diff --git a/src/game/client/neo/ui/neo_root_settings.cpp b/src/game/client/neo/ui/neo_root_settings.cpp index ff384a678..a2623a02f 100644 --- a/src/game/client/neo/ui/neo_root_settings.cpp +++ b/src/game/client/neo/ui/neo_root_settings.cpp @@ -685,6 +685,11 @@ void NeoSettingsRestore(NeoSettings *ns, const NeoSettings::Keys::Flags flagsKey pHUD->iExtendedKillfeed = cvr->cl_neo_hud_extended_killfeed.GetInt(); pHUD->iKdinfoToggletype = cvr->cl_neo_kdinfo_toggletype.GetInt(); pHUD->bShowHudContextHints = cvr->cl_neo_hud_context_hint_enabled.GetBool(); + pHUD->bShowHudContextHintPlayerTakeover = cvr->cl_neo_hud_context_hint_show_player_takeover_hint.GetBool(); + pHUD->bShowHudContextHintObjectInteract = cvr->cl_neo_hud_context_hint_show_object_interact_hint.GetBool(); + pHUD->bShowHudContextHintBotInteract = cvr->cl_neo_hud_context_hint_show_bot_interact_hint.GetBool(); + pHUD->bShowHudContextHighlightObject = cvr->cl_neo_hud_context_hint_highlight_object.GetBool(); + pHUD->bShowHudContextHighlightPlayer = cvr->cl_neo_hud_context_hint_highlight_player.GetBool(); bool bImported = ImportMarker(&pHUD->options[NEOIFFMARKER_OPTION_SQUAD], cvr->cl_neo_squad_marker.GetString()); if (!bImported) @@ -945,6 +950,11 @@ void NeoSettingsSave(const NeoSettings *ns) cvr->cl_neo_hud_extended_killfeed.SetValue(pHUD->iExtendedKillfeed); cvr->cl_neo_kdinfo_toggletype.SetValue(pHUD->iKdinfoToggletype); cvr->cl_neo_hud_context_hint_enabled.SetValue(pHUD->bShowHudContextHints); + cvr->cl_neo_hud_context_hint_show_player_takeover_hint.SetValue(pHUD->bShowHudContextHintPlayerTakeover); + cvr->cl_neo_hud_context_hint_show_object_interact_hint.SetValue(pHUD->bShowHudContextHintObjectInteract); + cvr->cl_neo_hud_context_hint_show_bot_interact_hint.SetValue(pHUD->bShowHudContextHintBotInteract); + cvr->cl_neo_hud_context_hint_highlight_object.SetValue(pHUD->bShowHudContextHighlightObject); + cvr->cl_neo_hud_context_hint_highlight_player.SetValue(pHUD->bShowHudContextHighlightPlayer); char szSequence[NEO_IFFMARKER_SEQMAX]; ExportMarker(&pHUD->options[NEOIFFMARKER_OPTION_SQUAD], szSequence); @@ -1642,13 +1652,23 @@ void NeoSettings_HUD(NeoSettings *ns) NeoUI::RingBox(L"Health display mode", HEALTHMODE_LABELS, pHud->iHealthMode >= 2 ? ARRAYSIZE(HEALTHMODE_LABELS) : 2, &pHud->iHealthMode); NeoUI::RingBox(L"Objective verbosity", OBJVERBOSITY_LABELS, ARRAYSIZE(OBJVERBOSITY_LABELS), &pHud->iObjVerbosity); NeoUI::RingBoxBool(L"Show hints", &pHud->bShowHints); - NeoUI::RingBoxBool(L"Show HUD contextual hints", &pHud->bShowHudContextHints); NeoUI::RingBoxBool(L"Show position", &pHud->bShowPos); NeoUI::RingBox(L"Show FPS", SHOWFPS_LABELS, ARRAYSIZE(SHOWFPS_LABELS), &pHud->iShowFps); NeoUI::RingBoxBool(L"Show rangefinder", &pHud->bEnableRangeFinder); NeoUI::RingBox(L"Extended killfeed", EXT_KILLFEED_LABELS, ARRAYSIZE(EXT_KILLFEED_LABELS), &pHud->iExtendedKillfeed); NeoUI::RingBox(L"Killer damage info auto show", KDMGINFO_TOGGLETYPE_LABELS, KDMGINFO_TOGGLETYPE__TOTAL, &pHud->iKdinfoToggletype); + NeoUI::Divider(L"Contextual hints"); + NeoUI::RingBoxBool(L"Show HUD contextual hints", &pHud->bShowHudContextHints); + if (pHud->bShowHudContextHints) + { + NeoUI::RingBoxBool(L"Show player takeover contextual hint", &pHud->bShowHudContextHintPlayerTakeover); + NeoUI::RingBoxBool(L"Show object interact contextual hint", &pHud->bShowHudContextHintObjectInteract); + NeoUI::RingBoxBool(L"Show bot interact contextual hint", &pHud->bShowHudContextHintBotInteract); + } + NeoUI::RingBoxBool(L"Highlight interactable objects", &pHud->bShowHudContextHighlightObject); + NeoUI::RingBoxBool(L"Highlight interactable players", &pHud->bShowHudContextHighlightPlayer); + #ifdef GLOWS_ENABLE NeoUI::Divider(L"XRAY"); NeoUI::RingBoxBool(L"Enable Xray", &pHud->bEnableXray); diff --git a/src/game/client/neo/ui/neo_root_settings.h b/src/game/client/neo/ui/neo_root_settings.h index 577772a91..410933abd 100644 --- a/src/game/client/neo/ui/neo_root_settings.h +++ b/src/game/client/neo/ui/neo_root_settings.h @@ -207,6 +207,11 @@ struct NeoSettings bool bEnableRangeFinder; int iExtendedKillfeed; bool bShowHudContextHints; + bool bShowHudContextHintPlayerTakeover; + bool bShowHudContextHintObjectInteract; + bool bShowHudContextHintBotInteract; + bool bShowHudContextHighlightObject; + bool bShowHudContextHighlightPlayer; int iKdinfoToggletype; // IFF Markers @@ -272,6 +277,11 @@ struct NeoSettings CONVARREF_DEF(sv_unlockedchapters); CONVARREF_DEF(cl_neo_kdinfo_toggletype); CONVARREF_DEF(cl_neo_hud_context_hint_enabled); + CONVARREF_DEF(cl_neo_hud_context_hint_show_player_takeover_hint); + CONVARREF_DEF(cl_neo_hud_context_hint_show_object_interact_hint); + CONVARREF_DEF(cl_neo_hud_context_hint_show_bot_interact_hint); + CONVARREF_DEF(cl_neo_hud_context_hint_highlight_object); + CONVARREF_DEF(cl_neo_hud_context_hint_highlight_player); CONVARREF_DEF(cl_neo_equip_utility_priority); CONVARREF_DEF(cl_neo_taking_damage_sounds); diff --git a/src/game/server/baseentity.h b/src/game/server/baseentity.h index 4a112c1e5..1fc85890b 100644 --- a/src/game/server/baseentity.h +++ b/src/game/server/baseentity.h @@ -571,7 +571,11 @@ class CBaseEntity : public IServerEntity // These methods encapsulate MOVETYPE_FOLLOW, which became obsolete void FollowEntity( CBaseEntity *pBaseEntity, bool bBoneMerge = true ); void StopFollowingEntity( ); // will also change to MOVETYPE_NONE +#ifdef NEO + virtual bool IsFollowingEntity(); +#else bool IsFollowingEntity(); +#endif // NEO CBaseEntity *GetFollowedEntity(); // initialization diff --git a/src/game/server/buttons.cpp b/src/game/server/buttons.cpp index e044dd84e..b26532fd4 100644 --- a/src/game/server/buttons.cpp +++ b/src/game/server/buttons.cpp @@ -78,6 +78,11 @@ END_DATADESC() LINK_ENTITY_TO_CLASS( func_button, CBaseButton ); +#ifdef NEO +IMPLEMENT_SERVERCLASS_ST( CBaseButton, DT_BaseButton ) + SendPropInt( SENDINFO(m_spawnflags), 13, SPROP_UNSIGNED ) +END_SEND_TABLE() +#endif // NEO void CBaseButton::Precache( void ) diff --git a/src/game/server/buttons.h b/src/game/server/buttons.h index c78a8652d..6ba4a96c1 100644 --- a/src/game/server/buttons.h +++ b/src/game/server/buttons.h @@ -16,6 +16,11 @@ class CBaseButton : public CBaseToggle public: DECLARE_CLASS( CBaseButton, CBaseToggle ); +#ifdef NEO + DECLARE_SERVERCLASS(); + + int updateTransmitState() { return SetTransmitState(FL_EDICT_PVSCHECK); } +#endif // NEO void Spawn( void ); virtual void Precache( void ); diff --git a/src/game/server/hl2/hl2_player.cpp b/src/game/server/hl2/hl2_player.cpp index 8656ca90a..88f10a56f 100644 --- a/src/game/server/hl2/hl2_player.cpp +++ b/src/game/server/hl2/hl2_player.cpp @@ -3224,6 +3224,9 @@ void CHL2_Player::PlayerUse ( void ) // Robin: Don't play sounds for NPCs, because NPCs will allow respond with speech. if ( !pUseEntity->MyNPCPointer() ) { +#ifdef NEO + if (!pUseEntity->IsBaseCombatWeapon()) // Don't play sounds when picking up weapons, so the use sound doesn't overwrite the weapon pickup sound +#endif // NEO EmitSound( "HL2Player.Use" ); } } diff --git a/src/game/server/neo/neo_player.cpp b/src/game/server/neo/neo_player.cpp index 3d6c42f6d..c0cfdbfa3 100644 --- a/src/game/server/neo/neo_player.cpp +++ b/src/game/server/neo/neo_player.cpp @@ -40,6 +40,7 @@ #include "bot/neo_bot.h" #include "nav_mesh.h" #include "neo_spawn_manager.h" +#include "recipientfilter.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -507,6 +508,32 @@ CON_COMMAND_F(joinstar, "Join star", FCVAR_USERINFO) } } +CON_COMMAND_F(useplayer, "+use on a player", FCVAR_USERINFO) +{ + if (args.ArgC() < 2) + return; + + auto player = static_cast(UTIL_GetCommandClient()); + if (!player) + return; + + CNEO_Player* pTargetPlayer = ToNEOPlayer(UTIL_PlayerByIndex(atoi(args[1]))); + if ( pTargetPlayer && pTargetPlayer->IsBot()) + { + if (sv_neo_bot_cmdr_enable.GetBool()) + { + pTargetPlayer->ToggleBotFollowCommander( player ); + // TODO: Do we want to allow using players for some kind of communication? + } + else if (NEORules()->IsTeamplay() && pTargetPlayer->GetTeamNumber() == player->GetTeamNumber()) + { + // Alt: Triggers throwing primary weapon to user + // see neo_bot_scenario_monitor for behavior transition + pTargetPlayer->m_hCommandingPlayer = player; + } + } +} + static int GetNumOtherPlayersConnected(CNEO_Player *asker) { if (!asker) @@ -2405,6 +2432,33 @@ void CNEO_Player::Weapon_DropAllOnDeath( const CTakeDamageInfo &info ) continue; } + if (pNeoWeapon->GetSlot() == NEO_THROWABLES_WEAPON_SLOT) + { // drop individual throwables with ammo counts of 1 + int numThrowablesCreated = 0; + for (int i = 1; i < pNeoWeapon->m_iPrimaryAmmoCount; i++) + { + CBaseEntity* pEnt = CreateEntityByName(pNeoWeapon->GetClassname()); + if (!pEnt) + break; // Assuming this will not work for all subsequent tries + + CNEOBaseCombatWeapon* pNeoEnt = static_cast(pEnt); + if (!pNeoEnt) + { + UTIL_Remove(pEnt); + break; // ditto + } + + pNeoEnt->SetLocalOrigin( GetLocalOrigin() ); + pNeoEnt->AddSpawnFlags(SF_NORESPAWN); + DispatchSpawn( pNeoEnt ); + pNeoEnt->Equip(this); + pNeoEnt->m_iPrimaryAmmoCount = 1; + numThrowablesCreated++; + Weapon_DropOnDeath(pNeoEnt, damageForce); + } + pNeoWeapon->m_iPrimaryAmmoCount.Set(pNeoWeapon->m_iPrimaryAmmoCount - numThrowablesCreated); + } + // Nowhere in particular; just drop it. Weapon_DropOnDeath(pNeoWeapon, damageForce); } @@ -2582,6 +2636,8 @@ bool CNEO_Player::WantsLagCompensationOnEntity( const CBasePlayer *pPlayer, void CNEO_Player::FireBullets ( const FireBulletsInfo_t &info ) { + m_flLastInputTime = gpGlobals->curtime; + BaseClass::FireBullets(info); if (!((static_cast(GetActiveWeapon()))->GetNeoWepBits() & NEO_WEP_SUPPRESSED)) @@ -2665,14 +2721,38 @@ bool CNEO_Player::Weapon_CanSwitchTo(CBaseCombatWeapon *pWeapon) bool CNEO_Player::BumpWeapon( CBaseCombatWeapon *pWeapon ) { auto weaponSlot = pWeapon->GetSlot(); - // Only pick up grenades if we don't have grenades of that type NEOTODO (Adam) What if we have less than the maximum of that type (i.e one smoke grenade)? Can I carry more of a grenade than I spawn with? - if (weaponSlot == 3 && Weapon_GetPosition(weaponSlot, pWeapon->GetPosition())) + if (weaponSlot == NEO_THROWABLES_WEAPON_SLOT) { - return false; + if (CNEOBaseCombatWeapon* pNeoWeaponInSlot = Weapon_GetPosition(weaponSlot, pWeapon->GetPosition()); + pNeoWeaponInSlot) + { + if (pNeoWeaponInSlot->GetPrimaryAmmoCount() < pNeoWeaponInSlot->GetDefaultClip1()) + { + const int ammoToAdd = clamp(pNeoWeaponInSlot->GetDefaultClip1() - pNeoWeaponInSlot->GetPrimaryAmmoCount(), 0, pWeapon->GetPrimaryAmmoCount()); + pNeoWeaponInSlot->m_iPrimaryAmmoCount += ammoToAdd; + pWeapon->m_iPrimaryAmmoCount -= ammoToAdd; + if (pWeapon->GetPrimaryAmmoCount() <= 0) + { + UTIL_Remove(pWeapon); + } + if (ammoToAdd > 0) + { + CRecipientFilter filter; + filter.AddRecipient(this); + + EmitSound_t params; + params.m_pSoundName = "Player.PickupWeapon"; + params.m_nFlags |= SND_DO_NOT_OVERWRITE_EXISTING_ON_CHANNEL; // NEO TODO (Adam) This is silencing weapon pickup noise when picking up with the use key + + EmitSound(filter,entindex(), params); + } + } + return false; + } } // We already have a weapon in this slot - if (weaponSlot != 3 && Weapon_GetSlot(weaponSlot)) + if (weaponSlot != NEO_THROWABLES_WEAPON_SLOT && Weapon_GetSlot(weaponSlot)) { return false; } @@ -2686,19 +2766,19 @@ bool CNEO_Player::BumpWeapon( CBaseCombatWeapon *pWeapon ) } } - // We need to run this for its side-effects, even in the IsDead case below... should be refactored. - const bool okRet = BaseClass::BumpWeapon(pWeapon); - // We had some cases of dead players chilling around with visible guns. // While that will be addressed in ShouldDraw, here's a preventive measure // to avoid that situation from occurring altogether. if (IsDead()) return false; + // We need to run this for its side-effects, even in the IsDead case below... should be refactored. + const bool okRet = BaseClass::BumpWeapon(pWeapon); + return okRet; } -bool CNEO_Player::Weapon_GetPosition(int slot, int position) +CNEOBaseCombatWeapon* CNEO_Player::Weapon_GetPosition(int slot, int position) { // Check for that slot being occupied already for (int i = 0; i < MAX_WEAPONS; i++) @@ -2707,7 +2787,7 @@ bool CNEO_Player::Weapon_GetPosition(int slot, int position) { // If the slots match, it's already occupied if (m_hMyWeapons[i]->GetSlot() == slot && m_hMyWeapons[i]->GetPosition() == position) - return m_hMyWeapons[i]; + return static_cast(m_hMyWeapons[i].Get()); } } @@ -3448,8 +3528,15 @@ void CNEO_Player::GiveLoadoutWeapon(void) { RemoveAllItems(false); GiveDefaultItems(); - pEnt->Touch(this); - Weapon_Switch(Weapon_OwnsThisType(szWep)); + if (!BumpWeapon(pNeoWeapon)) + { + UTIL_Remove( pNeoWeapon ); + } + else + { + pEnt->Touch( this ); + Weapon_Switch(Weapon_OwnsThisType(szWep)); + } } } else @@ -3586,44 +3673,6 @@ void CNEO_Player::ToggleBotFollowCommander(CNEO_Player* pCommander) } } -void CNEO_Player::PlayerUse( void ) -{ - BaseClass::PlayerUse(); - - if ( (m_afButtonPressed & IN_USE) && !FindUseEntity() ) - { - // Select bot under cursor to follow/unfollow. - Vector eyePos = EyePosition(); - Vector forward; - EyeVectors( &forward ); - Vector traceEnd = eyePos + forward * MAX_COORD_RANGE; - - trace_t tr; - // MASK_SHOT_HULL to match friendly fire warning trace - UTIL_TraceLine( eyePos, traceEnd, MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, &tr ); - - if ( tr.DidHit() && tr.m_pEnt ) - { - CNEO_Player* pTargetPlayer = ToNEOPlayer(tr.m_pEnt); - if ( pTargetPlayer && pTargetPlayer->IsBot()) - { - if (sv_neo_bot_cmdr_enable.GetBool()) - { - // The hit entity is a bot! Now, toggle its follow state. - pTargetPlayer->ToggleBotFollowCommander( this ); - // TODO: Do we want to allow using players for some kind of communication? - } - else if (NEORules()->IsTeamplay() && pTargetPlayer->GetTeamNumber() == GetTeamNumber()) - { - // Alt: Triggers throwing primary weapon to user - // see neo_bot_scenario_monitor for behavior transition - pTargetPlayer->m_hCommandingPlayer = this; - } - } - } - } -} - void CNEO_Player::StartAutoSprint(void) { BaseClass::StartAutoSprint(); @@ -3700,8 +3749,12 @@ void CNEO_Player::CloakPower_Update(void) } } +ConVar cl_neo_infinite_cloak("cl_neo_infinite_cloak", "0", FCVAR_CHEAT); bool CNEO_Player::CloakPower_Drain(float flPower) { + if (cl_neo_infinite_cloak.GetBool()) + return true; + m_HL2Local.m_cloakPower -= flPower; if (m_HL2Local.m_cloakPower < 0.0) @@ -4057,111 +4110,14 @@ const char *CNEO_Player::GetOverrideStepSound(const char *pBaseStepSound) // Start spectator takeover of player related code: ConVar sv_neo_spec_replace_player_loadout_enable("sv_neo_spec_replace_player_loadout_enable", "0", FCVAR_NONE, "Allow loadout change after spectator takeover.", true, 0, true, 1); -ConVar sv_neo_spec_replace_player_bot_enable("sv_neo_spec_replace_player_bot_enable", "1", FCVAR_NONE, "Allow spectators to take over bots.", true, 0, true, 1); -ConVar sv_neo_spec_replace_player_afk_enable("sv_neo_spec_replace_player_afk_enable", "0", FCVAR_NONE, "Allow spectators to take over AFK players.", true, 0, true, 1); -ConVar sv_neo_spec_replace_player_afk_time_sec( "sv_neo_spec_replace_player_afk_time_sec", - "180", FCVAR_NONE, - "Seconds of inactivity before a player is considered AFK for spectator takeover.", - true, -1, true, 999); -ConVar sv_neo_spec_replace_player_min_exp("sv_neo_spec_replace_player_min_exp", - "0", FCVAR_NONE, - "Minimum experience allowed to takeover players ", - true, -999, true, 999); -int CNEO_Player::GetSecondsUntilAFK() const -{ - // NEO JANK GetTimeSinceLastUserCommand seems to return 0 as long as the player is connected, so use an alternative timer - // GetTimeSinceWeaponFired was the simplest timer that worked, but should choose more robust criteria later - // TODO: Identify when player has triggered significant inputs and reset an AFK timer - // > 0 means more time needs to elapse before considered AFK - // <= 0 means player is considered AFK - return sv_neo_spec_replace_player_afk_time_sec.GetInt() - GetTimeSinceWeaponFired(); -} - -bool CNEO_Player::IsAFK() const -{ - return GetSecondsUntilAFK() <= 0; -} void CNEO_Player::SpectatorTryReplacePlayer(CNEO_Player* pNeoPlayerToReplace) { - CSingleUserRecipientFilter filter(this); - - if (!IsObserver() && IsAlive()) - { - DevWarning("A client initiating player takeover without being in observer mode might indicate server command bugs or tampering.\n"); - UTIL_ClientPrintFilter(filter, HUD_PRINTCONSOLE, "Shell takeover failed: Not in observer mode."); - return; - } - - if (NEORules()->GetRoundStatus() == PostRound) - { - UTIL_ClientPrintFilter(filter, HUD_PRINTCONSOLE, "Shell takeover failed: The mission is over."); - return; - } - - if (m_iXP < sv_neo_spec_replace_player_min_exp.GetInt()) - { - if (m_iXP < 0) - { - UTIL_ClientPrintFilter(filter, HUD_PRINTCONSOLE, "Shell takeover failed: Rankless Dogs are not authorized."); - } - else - { - UTIL_ClientPrintFilter(filter, HUD_PRINTCONSOLE, - "Shell takeover failed: Requires at least %s1 XP for authorization.", - sv_neo_spec_replace_player_min_exp.GetString()); - } - return; - } - - if (!pNeoPlayerToReplace) - { - UTIL_ClientPrintFilter(filter, HUD_PRINTCONSOLE, "Shell takeover failed: The target is not a valid candidate."); - return; - } - - if (!pNeoPlayerToReplace->IsAlive()) - { - UTIL_ClientPrintFilter(filter, HUD_PRINTCONSOLE, "Shell takeover failed: The target is dead."); - return; - } - - if (!InSameTeam(pNeoPlayerToReplace) || !NEORules()->IsTeamplay()) - { - UTIL_ClientPrintFilter(filter, HUD_PRINTCONSOLE, "Shell takeover failed: Target is not friendly."); - return; - } - - const bool bIsTargetBot = pNeoPlayerToReplace->IsBot(); - const bool bIsTargetAFK = pNeoPlayerToReplace->IsAFK(); - const bool bAllowBotTakeover = sv_neo_spec_replace_player_bot_enable.GetBool(); - const bool bAllowAfkTakeover = sv_neo_spec_replace_player_afk_enable.GetBool(); - - // If no valid condition is met, determine the specific reason and inform the user. - if (bIsTargetBot) - { - if (!bAllowBotTakeover) - { - UTIL_ClientPrintFilter(filter, HUD_PRINTCONSOLE, "Shell takeover failed: Taking over bots is disabled."); - return; - } - } - else if (bIsTargetAFK) - { - if (!bAllowAfkTakeover) - { - UTIL_ClientPrintFilter(filter, HUD_PRINTCONSOLE, "Shell takeover failed: Taking over inactive shells is disabled."); - return; - } - } - else + if (!pNeoPlayerToReplace->ValidTakeoverTargetFor(this)) { - int secondsLeft = pNeoPlayerToReplace->GetSecondsUntilAFK(); - UTIL_ClientPrintFilter( - filter, - HUD_PRINTCONSOLE, - UTIL_VarArgs("Shell takeover failed: Shell is not considered inactive until %d seconds.", secondsLeft) ); + CSingleUserRecipientFilter filter(this); + UTIL_ClientPrintFilter(filter, HUD_PRINTCONSOLE, "Shell takeover failed"); return; } diff --git a/src/game/server/neo/neo_player.h b/src/game/server/neo/neo_player.h index ba3252822..18f7ea1c8 100644 --- a/src/game/server/neo/neo_player.h +++ b/src/game/server/neo/neo_player.h @@ -67,7 +67,6 @@ class CNEO_Player : public CHL2MP_Player virtual void CalculateSpeed(void); virtual void PreThink(void) OVERRIDE; virtual void PlayerDeathThink(void) OVERRIDE; - virtual void PlayerUse(void) OVERRIDE; virtual bool HandleCommand_JoinTeam(int team) OVERRIDE; virtual bool ClientCommand(const CCommand &args) OVERRIDE; virtual void CreateViewModel(int viewmodelindex = 0) OVERRIDE; @@ -81,7 +80,7 @@ class CNEO_Player : public CHL2MP_Player virtual bool Weapon_Switch(CBaseCombatWeapon *pWeapon, int viewmodelindex = 0) OVERRIDE; virtual bool Weapon_CanSwitchTo(CBaseCombatWeapon *pWeapon) OVERRIDE; virtual bool BumpWeapon(CBaseCombatWeapon *pWeapon) OVERRIDE; - bool Weapon_GetPosition(int slot, int position); + CNEOBaseCombatWeapon* Weapon_GetPosition(int slot, int position); virtual void ChangeTeam(int iTeam) OVERRIDE; virtual void PickupObject(CBaseEntity *pObject, bool bLimitMassAndSize) OVERRIDE; virtual void PlayStepSound(Vector &vecOrigin, surfacedata_t *psurface, float fvol, bool force) OVERRIDE; @@ -97,6 +96,7 @@ class CNEO_Player : public CHL2MP_Player virtual void GiveDefaultItems(void) OVERRIDE; virtual int OnTakeDamage_Alive(const CTakeDamageInfo& info) OVERRIDE; virtual CBaseEntity* GiveNamedItem(const char* szName, int iSubType = 0) override; + virtual CBaseEntity* FindUseEntity() override; virtual void InitVCollision(const Vector& vecAbsOrigin, const Vector& vecAbsVelocity) OVERRIDE; @@ -241,6 +241,8 @@ class CNEO_Player : public CHL2MP_Player void BecomeJuggernaut(); void SpawnJuggernautPostDeath(); + bool ValidTakeoverTargetFor(CNEO_Player* pPlayerTakingOver); + private: bool m_bAllowGibbing; @@ -324,6 +326,7 @@ class CNEO_Player : public CHL2MP_Player void ResetBotCommandState(); void ToggleBotFollowCommander( CNEO_Player *pCommander ); static const Vector VECTOR_INVALID_WAYPOINT; + float m_flLastInputTime = gpGlobals->curtime; private: bool m_bFirstDeathTick; @@ -349,8 +352,8 @@ class CNEO_Player : public CHL2MP_Player CNEO_Player(const CNEO_Player&); // Spectator takeover player related functionality - int GetSecondsUntilAFK() const; bool IsAFK() const; + bool IsFakePlayer() const; void SpectatorTryReplacePlayer(CNEO_Player* pNeoPlayerToReplace); void SpectatorTakeoverPlayerPreThink(); void SpectatorTakeoverPlayerInitiate(CNEO_Player* pPlayer); diff --git a/src/game/server/player_resource.cpp b/src/game/server/player_resource.cpp index 1a5225889..02ef36893 100644 --- a/src/game/server/player_resource.cpp +++ b/src/game/server/player_resource.cpp @@ -11,6 +11,7 @@ #ifdef NEO #include "neo_player.h" +#include "neo_player_shared.h" #endif // memdbgon must be the last include file in a .cpp file!!! @@ -31,6 +32,7 @@ IMPLEMENT_SERVERCLASS_ST_NOBASE(CPlayerResource, DT_PlayerResource) SendPropArray3(SENDINFO_ARRAY3(m_iStar), SendPropInt(SENDINFO_ARRAY(m_iStar), 12)), SendPropArray3(SENDINFO_ARRAY3(m_szNeoClantag), SendPropString(SENDINFO_ARRAY(m_szNeoClantag), 0, SendProxy_StringT_To_String)), SendPropArray3(SENDINFO_ARRAY3(m_iMaxHealth), SendPropInt(SENDINFO_ARRAY(m_iMaxHealth), -1, SPROP_VARINT | SPROP_UNSIGNED)), + SendPropArray3(SENDINFO_ARRAY3(m_bAfk), SendPropInt(SENDINFO_ARRAY(m_bAfk), 1, SPROP_UNSIGNED)), #endif SendPropArray3( SENDINFO_ARRAY3(m_iScore), SendPropInt( SENDINFO_ARRAY(m_iScore), 12 ) ), SendPropArray3( SENDINFO_ARRAY3(m_iDeaths), SendPropInt( SENDINFO_ARRAY(m_iDeaths), 12 ) ), @@ -96,6 +98,7 @@ void CPlayerResource::Init( int iIndex ) m_iStar.Set(iIndex, 0); m_szNeoClantag.Set(iIndex, m_szNeoNameNone); m_iMaxHealth.Set(iIndex, 1); + m_bAfk.Set(iIndex, 0); #endif m_iPing.Set( iIndex, 0 ); m_iScore.Set( iIndex, 0 ); @@ -176,6 +179,7 @@ void CPlayerResource::UpdatePlayerData( void ) m_szNeoClantag.Set(i, strt); } m_iNeoNameDupeIdx.Set(i, neoPlayer->NameDupePos()); + m_bAfk.Set(i, gpGlobals->curtime - neoPlayer->m_flLastInputTime > sv_neo_spec_replace_player_afk_time_sec.GetInt()); #endif UpdateConnectedPlayer( i, pPlayer ); } diff --git a/src/game/server/player_resource.h b/src/game/server/player_resource.h index 02f001954..1a43fa36c 100644 --- a/src/game/server/player_resource.h +++ b/src/game/server/player_resource.h @@ -57,6 +57,7 @@ class CPlayerResource : public CBaseEntity CNetworkArray(int, m_iStar, MAX_PLAYERS_ARRAY_SAFE); CNetworkArray(string_t, m_szNeoClantag, MAX_PLAYERS_ARRAY_SAFE); CNetworkArray(int, m_iMaxHealth, MAX_PLAYERS_ARRAY_SAFE); + CNetworkArray(int, m_bAfk, MAX_PLAYERS_ARRAY_SAFE); #endif CNetworkArray( int, m_iScore, MAX_PLAYERS_ARRAY_SAFE ); CNetworkArray( int, m_iDeaths, MAX_PLAYERS_ARRAY_SAFE ); diff --git a/src/game/shared/basecombatweapon_shared.cpp b/src/game/shared/basecombatweapon_shared.cpp index e72e024be..17296e9ca 100644 --- a/src/game/shared/basecombatweapon_shared.cpp +++ b/src/game/shared/basecombatweapon_shared.cpp @@ -228,7 +228,11 @@ void CBaseCombatWeapon::Spawn( void ) #endif // Bloat the box for player pickup +#ifdef NEO + CollisionProp()->UseTriggerBounds( true, 1, true ); +#else CollisionProp()->UseTriggerBounds( true, 36 ); +#endif // NEO // Use more efficient bbox culling on the client. Otherwise, it'll setup bones for most // characters even when they're not in the frustum. diff --git a/src/game/shared/baseplayer_shared.h b/src/game/shared/baseplayer_shared.h index 0e3400183..791122754 100644 --- a/src/game/shared/baseplayer_shared.h +++ b/src/game/shared/baseplayer_shared.h @@ -64,4 +64,8 @@ void CopySoundNameWithModifierToken( char *pchDest, const char *pchSource, int n #include "player.h" #endif +#ifdef NEO +float IntervalDistance(float x, float x0, float x1); +#endif // NEO + #endif // BASEPLAYER_SHARED_H diff --git a/src/game/shared/neo/neo_juggernaut.cpp b/src/game/shared/neo/neo_juggernaut.cpp index 3e7498663..afc0879c4 100644 --- a/src/game/shared/neo/neo_juggernaut.cpp +++ b/src/game/shared/neo/neo_juggernaut.cpp @@ -29,6 +29,8 @@ float CNEO_Juggernaut::GetUseDistanceSquared() #ifdef GAME_DLL IMPLEMENT_SERVERCLASS_ST(CNEO_Juggernaut, DT_NEO_Juggernaut) SendPropBool(SENDINFO(m_bLocked)), + SendPropBool(SENDINFO(m_bIsHolding)), + SendPropEHandle(SENDINFO(m_hHoldingPlayer)), END_SEND_TABLE() #else #ifdef CNEO_Juggernaut @@ -36,6 +38,8 @@ END_SEND_TABLE() #endif IMPLEMENT_CLIENTCLASS_DT(C_NEO_Juggernaut, DT_NEO_Juggernaut, CNEO_Juggernaut) RecvPropBool(RECVINFO(m_bLocked)), + RecvPropBool(RECVINFO(m_bIsHolding)), + RecvPropEHandle(RECVINFO(m_hHoldingPlayer)), END_RECV_TABLE() #define CNEO_Juggernaut C_NEO_Juggernaut #endif @@ -181,12 +185,12 @@ void CNEO_Juggernaut::Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYP if (pNEOPlayer->m_afButtonPressed & IN_USE) { m_bIsHolding = true; - m_hHoldingPlayer = pNEOPlayer; + m_hHoldingPlayer.Set(pNEOPlayer); m_flHoldStartTime = gpGlobals->curtime; SetNextThink(gpGlobals->curtime + 0.1f); SetPlaybackRate(m_flWarpedPlaybackRate); - m_hHoldingPlayer->AddFlag(FL_FROZEN); - UTIL_HudMessage(m_hHoldingPlayer, m_textParms, "BOOTING JGR56"); // TODO localise this text + pNEOPlayer->AddFlag(FL_FROZEN); + UTIL_HudMessage(pNEOPlayer, m_textParms, "BOOTING JGR56"); // TODO localise this text EmitSound("HUD.CPCharge"); } else @@ -197,13 +201,14 @@ void CNEO_Juggernaut::Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYP void CNEO_Juggernaut::Think(void) { - if (!m_bIsHolding || !m_hHoldingPlayer || !m_hHoldingPlayer->IsAlive() || !(m_hHoldingPlayer->m_nButtons & IN_USE)) + CNEO_Player* pNeoPlayer = m_hHoldingPlayer.Get(); + if (!m_bIsHolding || !pNeoPlayer || !pNeoPlayer->IsAlive() || !(pNeoPlayer->m_nButtons & IN_USE)) { HoldCancel(); return; } - if (((m_hHoldingPlayer->GetAbsOrigin() - GetAbsOrigin()).LengthSqr()) > USE_DISTANCE_SQUARED) + if (((pNeoPlayer->GetAbsOrigin() - GetAbsOrigin()).LengthSqr()) > USE_DISTANCE_SQUARED) { HoldCancel(); return; @@ -216,16 +221,16 @@ void CNEO_Juggernaut::Think(void) SetNextThink(TICK_NEVER_THINK); StopSound("HUD.CPCharge"); EmitSound("HUD.CPCaptured"); - UTIL_HudMessage(m_hHoldingPlayer, m_textParms, ""); // Find a better way of hiding the text. This doesn't remove the old message from the user messages list and thus makes a weird overlapping visual bug + UTIL_HudMessage(pNeoPlayer, m_textParms, ""); // Find a better way of hiding the text. This doesn't remove the old message from the user messages list and thus makes a weird overlapping visual bug - m_hHoldingPlayer->RemoveFlag(FL_FROZEN); - m_hHoldingPlayer->CreateRagdollEntity(); - m_hHoldingPlayer->Weapon_DropAllOnDeath(CTakeDamageInfo(this, this, 0, DMG_GENERIC)); - m_hHoldingPlayer->Teleport(&GetAbsOrigin(), &GetAbsAngles(), &vec3_origin); + pNeoPlayer->RemoveFlag(FL_FROZEN); + pNeoPlayer->CreateRagdollEntity(); + pNeoPlayer->Weapon_DropAllOnDeath(CTakeDamageInfo(this, this, 0, DMG_GENERIC)); + pNeoPlayer->Teleport(&GetAbsOrigin(), &GetAbsAngles(), &vec3_origin); - m_hHoldingPlayer->BecomeJuggernaut(); + pNeoPlayer->BecomeJuggernaut(); - m_OnPlayerActivate.FireOutput(m_hHoldingPlayer, this); + m_OnPlayerActivate.FireOutput(pNeoPlayer, this); m_bActivationRemoval = true; UTIL_Remove(this); @@ -293,10 +298,11 @@ void CNEO_Juggernaut::DisableSoftCollisionsThink() void CNEO_Juggernaut::HoldCancel(void) { - if (m_hHoldingPlayer) + CNEO_Player* pNeoPlayer = m_hHoldingPlayer.Get(); + if (pNeoPlayer) { - m_hHoldingPlayer->RemoveFlag(FL_FROZEN); - UTIL_HudMessage(m_hHoldingPlayer, m_textParms, ""); + pNeoPlayer->RemoveFlag(FL_FROZEN); + UTIL_HudMessage(pNeoPlayer, m_textParms, ""); } SetNextThink(TICK_NEVER_THINK); SetPlaybackRate(-m_flWarpedPlaybackRate); @@ -333,10 +339,11 @@ void CNEO_Juggernaut::SetSoftCollision(bool soft) const bool CNEO_Juggernaut::IsBeingActivatedByLosingTeam() { - if (m_bIsHolding && m_hHoldingPlayer) + CNEO_Player* pNeoPlayer = m_hHoldingPlayer.Get(); + if (m_bIsHolding && pNeoPlayer) { - const int playerTeam = m_hHoldingPlayer->GetTeamNumber(); - const int oppositeTeam = (m_hHoldingPlayer->GetTeamNumber() == TEAM_JINRAI ? TEAM_NSF : TEAM_JINRAI); + const int playerTeam = pNeoPlayer->GetTeamNumber(); + const int oppositeTeam = (pNeoPlayer->GetTeamNumber() == TEAM_JINRAI ? TEAM_NSF : TEAM_JINRAI); if (GetGlobalTeam(playerTeam)->GetScore() < GetGlobalTeam(oppositeTeam)->GetScore()) { return true; diff --git a/src/game/shared/neo/neo_juggernaut.h b/src/game/shared/neo/neo_juggernaut.h index c1740eba7..86a9b4155 100644 --- a/src/game/shared/neo/neo_juggernaut.h +++ b/src/game/shared/neo/neo_juggernaut.h @@ -18,6 +18,7 @@ class CNEO_Juggernaut : public CBaseAnimating static float GetUseDuration(); static float GetUseDistanceSquared(); + CNEO_Juggernaut() { m_bIsHolding = false; } #ifdef GAME_DLL virtual ~CNEO_Juggernaut(); DECLARE_SERVERCLASS(); @@ -30,7 +31,6 @@ class CNEO_Juggernaut : public CBaseAnimating void Precache(void); void Spawn(void); void Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value); - virtual int ObjectCaps(void) { return BaseClass::ObjectCaps() | FCAP_ONOFF_USE; } virtual int UpdateTransmitState() override; CNEO_Player* GetActivatingPlayer() const { return m_hHoldingPlayer.Get(); } @@ -39,11 +39,14 @@ class CNEO_Juggernaut : public CBaseAnimating bool m_bPostDeath = false; #endif + virtual int ObjectCaps(void) { return BaseClass::ObjectCaps() | FCAP_ONOFF_USE; } virtual unsigned int PhysicsSolidMaskForEntity() const final override { return MASK_PLAYERSOLID; } virtual void UpdateOnRemove() override; CNetworkVar(bool, m_bLocked); + CNetworkVar(CHandle, m_hHoldingPlayer); + CNetworkVar(bool, m_bIsHolding); private: #ifdef GAME_DLL @@ -60,11 +63,9 @@ class CNEO_Juggernaut : public CBaseAnimating #endif #ifdef GAME_DLL - CHandle m_hHoldingPlayer; EHANDLE m_hPush; float m_flWarpedPlaybackRate; float m_flHoldStartTime = 0.0f; - bool m_bIsHolding = false; bool m_bActivationRemoval = false; hudtextparms_t m_textParms; diff --git a/src/game/shared/neo/neo_player_shared.cpp b/src/game/shared/neo/neo_player_shared.cpp index 7de2bb4de..d0b73b9d8 100644 --- a/src/game/shared/neo/neo_player_shared.cpp +++ b/src/game/shared/neo/neo_player_shared.cpp @@ -15,6 +15,7 @@ #ifdef CLIENT_DLL #include "c_neo_player.h" #include "c_playerresource.h" +#include "ui/neo_hud_context_hint.h" #define CNEO_Player C_NEO_Player #else #include "neo_player.h" @@ -26,8 +27,13 @@ #include "convar.h" #include "neo_weapon_loadout.h" - #include "weapon_neobasecombatweapon.h" +#include "igameresources.h" +#ifdef GAME_DLL +#include "ai_basenpc.h" +#else +#include "c_ai_basenpc.h" +#endif // GAME_DLL // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -51,6 +57,17 @@ ConVar sv_neo_ghost_delay_secs("sv_neo_ghost_delay_secs", "3.3", FCVAR_NOTIFY | ConVar sv_neo_serverside_beacons("sv_neo_serverside_beacons", "1", FCVAR_NOTIFY | FCVAR_REPLICATED, "Whether ghost beacons should be processed server-side.", true, false, true, true); +ConVar sv_neo_spec_replace_player_bot_enable("sv_neo_spec_replace_player_bot_enable", "1", FCVAR_REPLICATED, "Allow spectators to take over bots.", true, 0, true, 1); +ConVar sv_neo_spec_replace_player_afk_enable("sv_neo_spec_replace_player_afk_enable", "0", FCVAR_REPLICATED, "Allow spectators to take over AFK players.", true, 0, true, 1); +ConVar sv_neo_spec_replace_player_min_exp("sv_neo_spec_replace_player_min_exp", + "0", FCVAR_REPLICATED, + "Minimum experience allowed to takeover players ", + true, -999, true, 999); +ConVar sv_neo_spec_replace_player_afk_time_sec( "sv_neo_spec_replace_player_afk_time_sec", + "180", FCVAR_NONE, + "Seconds of inactivity before a player is considered AFK for spectator takeover.", + true, -1, true, 999); + bool IsAllowedToZoom(CNEOBaseCombatWeapon *pWep) { if (!pWep || pWep->m_bInReload || !pWep->CanAim()) @@ -393,3 +410,263 @@ void CNEO_Player::CheckAimButtons() Weapon_SetZoom(false); } } + +bool CNEO_Player::IsAFK() const +{ +#ifdef GAME_DLL + return gpGlobals->curtime - m_flLastInputTime > sv_neo_spec_replace_player_afk_time_sec.GetInt(); +#else + return GameResources()->IsAfk(entindex()); +#endif // GAME_DLL +} + +bool CNEO_Player::IsFakePlayer() const +{ +#ifdef GAME_DLL + return IsBot(); +#else + return GameResources()->IsFakePlayer(entindex()); +#endif // GAME_DLL +} + +bool CNEO_Player::ValidTakeoverTargetFor(CNEO_Player *pPlayerTakingOver) +{ + return pPlayerTakingOver && pPlayerTakingOver->IsObserver() && !pPlayerTakingOver->IsAlive() + && NEORules()->GetRoundStatus() != PostRound + && pPlayerTakingOver->m_iXP >= sv_neo_spec_replace_player_min_exp.GetInt() + && IsAlive() + && InSameTeam(pPlayerTakingOver) && NEORules()->IsTeamplay() + && (sv_neo_spec_replace_player_bot_enable.GetBool() && IsFakePlayer() || + sv_neo_spec_replace_player_afk_enable.GetBool() && IsAFK()); +} + +#ifdef CLIENT_DLL +ConVar cl_neo_hud_context_hint_show_adjacent_interactable_objects("cl_neo_hud_context_hint_show_adjacent_interactable_objects", "1", FCVAR_ARCHIVE, "Show adjacent interactable objects", true, 0.f, true, 1.f); +static bool gAddUseItemsToUseItemsList = false; +bool SetAddUseEntitysToUseEntityList(bool addUseItemsToUseItemsList) +{ + if (cl_neo_hud_context_hint_show_adjacent_interactable_objects.GetBool()) + gAddUseItemsToUseItemsList = addUseItemsToUseItemsList; + return true; +}; +#endif // CLIENT_DLL + +extern ConVar sv_debug_player_use; +CBaseEntity *CNEO_Player::FindUseEntity() +{ + Vector forward; + EyeVectors( &forward, nullptr, nullptr ); + + trace_t tr; + // Search for objects in a sphere (tests for entities that are not solid, yet still useable) + Vector searchCenter = EyePosition(); + + // NOTE: Some debris objects are useable too, so hit those as well + // A button, etc. can be made out of clip brushes, make sure it's +useable via a traceline, too. + int useableContents = MASK_SOLID | CONTENTS_DEBRIS | CONTENTS_PLAYERCLIP; + +#ifndef CLIENT_DLL + CBaseEntity *pFoundByTrace = nullptr; +#endif + + // UNDONE: Might be faster to just fold this range into the sphere query + CBaseEntity *pObject = nullptr; + + float nearestDot = -1; + + // try the hit entity if there is one, or the ground entity if there isn't. + CBaseEntity *pNearest = nullptr; + + { + UTIL_TraceLine( searchCenter, searchCenter + forward * 1024, useableContents, this, COLLISION_GROUP_NONE, &tr ); + pObject = tr.m_pEnt; + +#ifndef CLIENT_DLL + pFoundByTrace = pObject; +#endif + bool bUsable = IsUseableEntity(pObject, 0); + while ( pObject && !bUsable && pObject->GetMoveParent() ) + { + pObject = pObject->GetMoveParent(); + bUsable = IsUseableEntity(pObject, 0); + } + + if ( bUsable ) + { + Vector delta = tr.endpos - tr.startpos; + float centerZ = CollisionProp()->WorldSpaceCenter().z; + delta.z = IntervalDistance( tr.endpos.z, centerZ + CollisionProp()->OBBMins().z, centerZ + CollisionProp()->OBBMaxs().z ); + float dist = delta.Length(); + if ( dist < PLAYER_USE_RADIUS ) + { +#ifndef CLIENT_DLL + if ( sv_debug_player_use.GetBool() ) + { + NDebugOverlay::Line( searchCenter, tr.endpos, 0, 255, 0, true, 30 ); + NDebugOverlay::Cross3D( tr.endpos, 16, 0, 255, 0, true, 30 ); + } + + if ( pObject->MyNPCPointer() && pObject->MyNPCPointer()->IsPlayerAlly( this ) ) + { + // If about to select an NPC, do a more thorough check to ensure + // that we're selecting the right one from a group. + pObject = DoubleCheckUseNPC( pObject, searchCenter, forward ); + } +#endif + if ( sv_debug_player_use.GetBool() ) + { + Msg( "Trace using: %s\n", pObject ? pObject->GetDebugName() : "no usable entity found" ); + } + + pNearest = pObject; + + // if there is an entity directly under the cursor just return it now + // NEO NOTE (Adam) weapon axis aligned collision bounds are usually far removed from where the weapon is visually. If a weapon can be interacted with in a radius, use that instead + if (pObject && !((pObject->ObjectCaps() & FCAP_USE_IN_RADIUS) && pObject->IsBaseCombatWeapon())) + { +#ifdef CLIENT_DLL + // Client side do the sphere query anyway to show adjacent items + if (gAddUseItemsToUseItemsList) + nearestDot = 0.f; + else +#endif // CLIENT_DLL + return pObject; + } + } + } + } + + // check ground entity first + // if you've got a useable ground entity, then shrink the cone of this search to 45 degrees + // otherwise, search out in a 90 degree cone (hemisphere) + if ( GetGroundEntity() && IsUseableEntity(GetGroundEntity(), FCAP_USE_ONGROUND) ) + { + pNearest = GetGroundEntity(); + } + if ( pNearest ) + { + // estimate nearest object by distance from the view vector + nearestDot = DotProduct((pNearest->CollisionProp()->WorldSpaceCenter() - searchCenter).Normalized(), forward); + if ( sv_debug_player_use.GetBool() ) + { + Vector point; + pNearest->CollisionProp()->CalcNearestPoint( searchCenter, &point ); + Msg("Trace found %s, dist %.2f\n", pNearest->GetClassname(), CalcDistanceToLine( point, searchCenter, forward ) ); + } + } + +#ifdef CLIENT_DLL + int useEntityListIndex = 0; + ClearUseEntityListEntry(); +#endif // CLIENT_DLL + for ( CEntitySphereQuery sphere( searchCenter, PLAYER_USE_RADIUS ); ( pObject = sphere.GetCurrentEntity() ) != nullptr; sphere.NextEntity() ) + { + if ( !pObject ) + continue; + +#ifdef CLIENT_DLL + Vector point; + pObject->CollisionProp()->CalcNearestPoint( searchCenter, &point ); + float dot = -1; + dot = DotProduct((pObject->CollisionProp()->WorldSpaceCenter() - searchCenter).Normalized(), forward); + + if (gAddUseItemsToUseItemsList && IsUseableEntity(pObject, 0) && dot >= 0.8) + { + pObject->CollisionProp()->CalcNearestPoint( searchCenter, &point ); + trace_t trCheckOccluded; + const int useableOccluder = MASK_SOLID | CONTENTS_PLAYERCLIP; + UTIL_TraceLine( searchCenter, point, useableOccluder, this, COLLISION_GROUP_PLAYER, &trCheckOccluded ); + + if (trCheckOccluded.fraction == 1.0 || trCheckOccluded.m_pEnt == pObject) + { + SetUseEntityListEntry(useEntityListIndex, pObject); + useEntityListIndex ++; + } + } +#endif // CLIENT_DLL + + if ( !IsUseableEntity( pObject, FCAP_USE_IN_RADIUS ) ) + continue; + +#ifdef GAME_DLL + float dot = -1; + dot = DotProduct((pObject->CollisionProp()->WorldSpaceCenter() - searchCenter).Normalized(), forward); +#endif // GAME_DLL + + // Need to be looking at the object more or less + if ( dot < 0.8 ) + continue; + +#ifdef GAME_DLL + Vector point; + pObject->CollisionProp()->CalcNearestPoint( searchCenter, &point ); +#endif // GAME_DLL + + if ( sv_debug_player_use.GetBool() ) + { + // NEO NOTE (Adam) looks like CEntitySphereQuery is using surrounding bounds instead of collision bounds. This distance could be significantly greater than PLAYER_USE_RADIUS + Msg("Radius found %s, dist %.2f\n", pObject->GetClassname(), CalcDistanceToLine( point, searchCenter, forward ) ); + } + + if ( dot > nearestDot ) + { + // Since this has purely been a radius search to this point, we now + // make sure the object isn't behind glass or a grate. + trace_t trCheckOccluded; + const int useableOccluder = MASK_SOLID | CONTENTS_PLAYERCLIP; + UTIL_TraceLine( searchCenter, point, useableOccluder, this, COLLISION_GROUP_PLAYER, &trCheckOccluded ); + + if ( trCheckOccluded.fraction == 1.0 || trCheckOccluded.m_pEnt == pObject ) + { + pNearest = pObject; + nearestDot = dot; + } + } + } + +#ifndef CLIENT_DLL + if ( !pNearest ) + { + // Haven't found anything near the player to use, nor any NPC's at distance. + // Check to see if the player is trying to select an NPC through a rail, fence, or other 'see-though' volume. + trace_t trAllies; + UTIL_TraceLine( searchCenter, searchCenter + forward * PLAYER_USE_RADIUS, MASK_OPAQUE_AND_NPCS, this, COLLISION_GROUP_NONE, &trAllies ); + + if ( trAllies.m_pEnt && IsUseableEntity( trAllies.m_pEnt, 0 ) && trAllies.m_pEnt->MyNPCPointer() && trAllies.m_pEnt->MyNPCPointer()->IsPlayerAlly( this ) ) + { + // This is an NPC, take it! + pNearest = trAllies.m_pEnt; + } + } + + if ( pNearest && pNearest->MyNPCPointer() && pNearest->MyNPCPointer()->IsPlayerAlly( this ) ) + { + pNearest = DoubleCheckUseNPC( pNearest, searchCenter, forward ); + } + + if ( sv_debug_player_use.GetBool() ) + { + if ( !pNearest ) + { + NDebugOverlay::Line( searchCenter, tr.endpos, 255, 0, 0, true, 30 ); + NDebugOverlay::Cross3D( tr.endpos, 16, 255, 0, 0, true, 30 ); + } + else if ( pNearest == pFoundByTrace ) + { + NDebugOverlay::Line( searchCenter, tr.endpos, 0, 255, 0, true, 30 ); + NDebugOverlay::Cross3D( tr.endpos, 16, 0, 255, 0, true, 30 ); + } + else + { + NDebugOverlay::Box( pNearest->WorldSpaceCenter(), Vector(-8, -8, -8), Vector(8, 8, 8), 0, 255, 0, true, 30 ); + } + } +#endif + + if ( sv_debug_player_use.GetBool() ) + { + Msg( "Radial using: %s\n", pNearest ? pNearest->GetDebugName() : "no usable entity found" ); + } + + return pNearest; +} diff --git a/src/game/shared/neo/neo_player_shared.h b/src/game/shared/neo/neo_player_shared.h index 48aa6ac09..9c268839c 100644 --- a/src/game/shared/neo/neo_player_shared.h +++ b/src/game/shared/neo/neo_player_shared.h @@ -20,6 +20,11 @@ extern ConVar sv_neo_ghost_delay_secs; extern ConVar sv_neo_ghost_view_distance; extern ConVar sv_neo_serverside_beacons; +extern ConVar sv_neo_spec_replace_player_bot_enable; +extern ConVar sv_neo_spec_replace_player_afk_enable; +extern ConVar sv_neo_spec_replace_player_min_exp; +extern ConVar sv_neo_spec_replace_player_afk_time_sec; + ////////////////////////////////////////////////////// // NEO MOVEMENT DEFINITIONS @@ -408,6 +413,10 @@ static constexpr const SZWSZTexts SZWSZ_NEO_TEAM_STRS[TEAM__TOTAL] = { X_SZWSZ_INIT(TEAM_STR_NSF), // TEAM_NSF }; +#ifdef CLIENT_DLL +bool SetAddUseEntitysToUseEntityList(bool addUseItemsToUseItemsList); +#endif // CLIENT_DLL + #define NEO_GAME_NAME "NEOTOKYO;REBUILD" #endif // NEO_PLAYER_SHARED_H diff --git a/src/game/shared/neo/weapons/weapon_ghost.h b/src/game/shared/neo/weapons/weapon_ghost.h index dffbe25ca..db576612c 100644 --- a/src/game/shared/neo/weapons/weapon_ghost.h +++ b/src/game/shared/neo/weapons/weapon_ghost.h @@ -63,7 +63,6 @@ class CWeaponGhost : public CNEOBaseCombatWeapon virtual void Drop(const Vector &vecVelocity) override; virtual void ItemHolsterFrame(void); virtual void Equip(CBaseCombatCharacter *pNewOwner) override; - virtual int ObjectCaps(void) { return BaseClass::ObjectCaps() | FCAP_IMPULSE_USE;}; void HandleGhostUnequip(void); bool CanBePickedUpByClass(int classId) OVERRIDE; virtual bool CanAim() final { return false; } diff --git a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp index d9783b1b2..d1ea3abde 100644 --- a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp +++ b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp @@ -41,12 +41,14 @@ BEGIN_NETWORK_TABLE( CNEOBaseCombatWeapon, DT_NEOBaseCombatWeapon ) RecvPropFloat(RECVINFO(m_flAccuracyPenalty)), RecvPropInt(RECVINFO(m_nNumShotsFired)), RecvPropBool(RECVINFO(m_bTriggerReset)), + RecvPropInt(RECVINFO(m_spawnflags)), #else SendPropTime(SENDINFO(m_flSoonestAttack)), SendPropTime(SENDINFO(m_flLastAttackTime)), SendPropFloat(SENDINFO(m_flAccuracyPenalty)), SendPropInt(SENDINFO(m_nNumShotsFired)), SendPropBool(SENDINFO(m_bTriggerReset)), + SendPropInt(SENDINFO(m_spawnflags)), SendPropExclude("DT_BaseAnimating", "m_nSequence"), #endif END_NETWORK_TABLE() @@ -1375,16 +1377,26 @@ void CNEOBaseCombatWeapon::SetPickupTouch(void) #ifdef GAME_DLL void CNEOBaseCombatWeapon::Use(CBaseEntity* pActivator, CBaseEntity* pCaller, USE_TYPE useType, float value) { - auto* neoPlayer = ToNEOPlayer(pActivator); + m_OnPlayerUse.FireOutput( pActivator, pCaller ); - if (neoPlayer && neoPlayer->Weapon_CanSwitchTo(this) && CanBePickedUpByClass(neoPlayer->GetClass())) + if (m_pfnTouch) { - neoPlayer->Weapon_DropSlot(GetSlot()); - neoPlayer->Weapon_Equip(this); + if (CNEO_Player* pNeoPlayer = ToNEOPlayer(pActivator); + pNeoPlayer && CanBePickedUpByClass(pNeoPlayer->GetClass())) + { + CBaseCombatWeapon* pActiveWeapon = pNeoPlayer->GetActiveWeapon(); + const int activeSlot = pActiveWeapon ? pActiveWeapon->GetSlot() : -1; + pNeoPlayer->Weapon_DropSlot(GetSlot()); + + (this->*m_pfnTouch)(pActivator); - RemoveEffects(EF_BONEMERGE); + if (GetOwner() == pNeoPlayer && activeSlot == GetSlot()) + { + pNeoPlayer->Weapon_Switch(this); + } + } } - BaseClass::Use(pActivator, pCaller, useType, value); + // Calling BaseClass::Use will pick the weapon up without waiting for the touch cooldown, don't see anything important there that we need to do that we aren't doing here } #endif diff --git a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h index 563fd62cd..089d21652 100644 --- a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h +++ b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h @@ -87,6 +87,7 @@ struct WeaponSeeds_t }; #define SOUNDENT_VOLUME_NEO_SUPPRESSED 900.0 +#define NEO_THROWABLES_WEAPON_SLOT 3 #if(1) // This does nothing; dummy value for network test. Remove when not needed anymore. @@ -140,6 +141,25 @@ class CNEOBaseCombatWeapon : public CBaseHL2MPCombatWeapon virtual void FinishReload(void) override; virtual bool CanBeSelected(void) override; + virtual bool IsFollowingEntity() override { + return (GetMoveType() == MOVETYPE_NONE) && GetMoveParent(); + }; + virtual int ObjectCaps(void) override + { + int caps = BaseClass::ObjectCaps(); + if (!IsFollowingEntity() +#ifdef GAME_DLL + && !HasSpawnFlags(SF_WEAPON_NO_PLAYER_PICKUP) +#else + && !(m_spawnflags & SF_WEAPON_NO_PLAYER_PICKUP) +#endif // GAME_DLL + ) + { + caps |= FCAP_IMPULSE_USE|FCAP_USE_IN_RADIUS; + } + + return caps; + }; CNEOWeaponInfo const &GetNEOWpnData() const; @@ -160,6 +180,7 @@ class CNEOBaseCombatWeapon : public CBaseHL2MPCombatWeapon #ifdef GAME_DLL virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) override; #endif + virtual void DryFire(void); virtual Activity GetPrimaryAttackActivity(void) override; @@ -235,6 +256,7 @@ class CNEOBaseCombatWeapon : public CBaseHL2MPCombatWeapon float GetPenetration() const; #ifdef CLIENT_DLL float m_flTemperature; + int m_spawnflags; #endif // CLIENT_DLL protected: diff --git a/src/public/igameresources.h b/src/public/igameresources.h index 2646aa0df..ea6ae5ed0 100644 --- a/src/public/igameresources.h +++ b/src/public/igameresources.h @@ -36,6 +36,7 @@ abstract_class IGameResources #ifdef NEO virtual int GetXP(int index) = 0; virtual int GetDisplayedHealth(int index, int mode) = 0; + virtual bool IsAfk(int index) = 0; #endif virtual int GetFrags( int index ) = 0; virtual int GetTeam( int index ) = 0;