diff --git a/src/game/client/c_baseanimating.cpp b/src/game/client/c_baseanimating.cpp index e4297c0e0f..0c7cb8a9c1 100644 --- a/src/game/client/c_baseanimating.cpp +++ b/src/game/client/c_baseanimating.cpp @@ -3619,16 +3619,19 @@ int C_BaseAnimating::InternalDrawModel( int flags ) #ifdef NEO if (IsViewModel()) { // view models become dark when standing close to and facing a wall, change lighting origin - auto pOwner = UTIL_PlayerByIndex(GetLocalPlayerIndex()); - if (pOwner) + if (!engine->IsHLTV()) { - static Vector ownerOrigin; - ownerOrigin = pOwner->EyePosition(); - pInfo->pLightingOrigin = &ownerOrigin; + auto pOwner = UTIL_PlayerByIndex(GetLocalPlayerIndex()); + if (pOwner) + { + static Vector ownerOrigin; + ownerOrigin = pOwner->EyePosition(); + pInfo->pLightingOrigin = &ownerOrigin; + } } } - else if (IsBaseCombatWeapon()) - { + else if (IsBaseCombatWeapon() && !GetMoveParent()) + { // dropped weapons can become dark when they rotate such that their origin falls through the floor static Vector worldSpaceCenter; worldSpaceCenter = WorldSpaceCenter(); pInfo->pLightingOrigin = &worldSpaceCenter; diff --git a/src/game/client/cdll_client_int.cpp b/src/game/client/cdll_client_int.cpp index 18bb8fb60d..7c39d5fa0b 100644 --- a/src/game/client/cdll_client_int.cpp +++ b/src/game/client/cdll_client_int.cpp @@ -150,6 +150,10 @@ #endif +#ifdef NEO +#include +#include +#endif extern vgui::IInputInternal *g_InputInternal; @@ -1943,6 +1947,79 @@ void CHLClient::DecodeUserCmdFromBuffer( bf_read& buf, int slot ) input->DecodeUserCmdFromBuffer( buf, slot ); } +#ifdef NEO +inline static vgui::VPANEL ChildOf(const vgui::VPANEL parent, const char* childName) +{ + Assert(parent); + const auto& children = vgui::ipanel()->GetChildren(parent); + for (const auto& child : children) + { + const char* name = vgui::ipanel()->GetName(child); + Assert(childName && *childName); + if (name && V_strcmp(name, childName) == 0) + return child; + } + return {}; +} + +static void FixupDemoSmoother() +{ + VPROF_BUDGET(__FUNCTION__, VPROF_BUDGETGROUP_REPLAY); + + static bool alreadyDone = false; + if (alreadyDone) + return; + + // Don't spam the vgui iteration attempts too often... + static float lastAttemptTime{}; + if (gpGlobals->curtime - lastAttemptTime < 1) + { + // ...except if we're not ticking, since who knows how long it's been. + // Luckily perf doesn't really matter if the entire game is currently frozen. + if (!engine->IsPaused()) + { + return; + } + } + + lastAttemptTime = gpGlobals->curtime; + + Assert(enginevgui); + const auto toolsPanel = enginevgui->GetPanel(PANEL_TOOLS); + const auto demoUiPanel = ChildOf(toolsPanel, "DemoUIPanel"); + const auto demoSmootherPanel = ChildOf(demoUiPanel, "DemoSmootherPanel"); + if (!demoSmootherPanel) + return; + + constexpr const char* demoSmootherPanelButtonsToFix[]{ + "DemoSmoothFixFrameButton", + "DemoSmootherType" }; + + int numFixed = 0; + for (const auto& buttonName : demoSmootherPanelButtonsToFix) + { + auto button = vgui::ipanel()->GetPanel(ChildOf(demoSmootherPanel, buttonName), "BaseUI"); + if (!button) + continue; + + // The buttons live in engine dll, so this could theoretically change in SDK update. + using ExpectedButtonType = vgui::MenuButton; + // And so we check + if (!dynamic_cast(button)) + { + Assert(false); + alreadyDone = true; // nothing we can do until code fix... just mark as done + return; + } + + ((ExpectedButtonType*)button)->SetButtonActivationType(vgui::Button::ACTIVATE_ONPRESSED); + ++numFixed; + } + + alreadyDone = (numFixed == ARRAYSIZE(demoSmootherPanelButtonsToFix)); +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -1956,6 +2033,12 @@ void CHLClient::View_Render( vrect_t *rect ) view->Render( rect ); UpdatePerfStats(); +#ifdef NEO + if (engine->IsPlayingDemo()) + { + FixupDemoSmoother(); + } +#endif } diff --git a/src/game/client/game_controls/SpectatorGUI.cpp b/src/game/client/game_controls/SpectatorGUI.cpp index 29414422f4..cb9c7af4b3 100644 --- a/src/game/client/game_controls/SpectatorGUI.cpp +++ b/src/game/client/game_controls/SpectatorGUI.cpp @@ -50,6 +50,8 @@ void AddSubKeyNamed( KeyValues *pKeys, const char *pszName ); #include "c_team.h" #include "neo_gamerules.h" #include "c_neo_player.h" +#include "view.h" +#include "hltvcamera.h" #endif // memdbgon must be the last include file in a .cpp file!!! @@ -986,48 +988,77 @@ CON_COMMAND_F( spec_player, "Spectate player by partial name, steamid, or userid } } -#ifdef NEO +#ifdef NEO CON_COMMAND_F( spec_player_under_mouse, "Spectate player by partial name, steamid, or userid", FCVAR_CLIENTCMD_CAN_EXECUTE ) { + if (engine->IsHLTV() && HLTVCamera()->IsPVSLocked()) + { + ConMsg( "%s: HLTV Camera is PVS locked\n", __FUNCTION__ ); + return; + } + C_NEO_Player *pNeoPlayer = C_NEO_Player::GetLocalNEOPlayer(); if ( !pNeoPlayer || !pNeoPlayer->IsObserver() ) return; - if (!engine->IsHLTV() || !HLTVCamera()->IsPVSLocked()) + C_BaseEntity* currentTarget = pNeoPlayer->GetObserverTarget(); + C_NEO_Player *target = nullptr; + float targetDotProduct = -1; + for (int i = 1; i <= gpGlobals->maxClients; i++) { - C_BaseEntity* currentTarget = pNeoPlayer->GetObserverTarget(); - C_NEO_Player *target = nullptr; - float targetDotProduct = -1; - for (int i = 1; i < gpGlobals->maxClients; i++) + C_NEO_Player* pPlayer = ToNEOPlayer(UTIL_PlayerByIndex(i)); + if (currentTarget != pPlayer && pNeoPlayer->IsValidObserverTarget(pPlayer) && pPlayer->IsAlive()) { - C_NEO_Player* pPlayer = ToNEOPlayer(UTIL_PlayerByIndex(i)); - if (currentTarget != pPlayer && pNeoPlayer->IsValidObserverTarget(pPlayer) && pPlayer->IsAlive()) + Vector vecToTarget = pPlayer->WorldSpaceCenter() - MainViewOrigin(); + vecToTarget.NormalizeInPlace(); + float dotProduct = DotProduct(MainViewForward(), vecToTarget); + if (dotProduct > targetDotProduct && dotProduct > 0.5) { - Vector vecForward; - AngleVectors( pNeoPlayer->EyeAngles(), &vecForward ); - - Vector vecToTarget = pPlayer->WorldSpaceCenter() - pNeoPlayer->EyePosition(); - vecToTarget.NormalizeInPlace(); - float dotProduct = DotProduct(vecForward, vecToTarget); - if (dotProduct > targetDotProduct && dotProduct > 0.5) - { - targetDotProduct = dotProduct; - target = pPlayer; - } + targetDotProduct = dotProduct; + target = pPlayer; } } + } + + if (target) + { + engine->IsHLTV() ? HLTVCamera()->SetPrimaryTarget(target->entindex()) : engine->ClientCmd(VarArgs("spec_player_entity_number %d", target->entindex())); + } +} - if (target) +CON_COMMAND_F( spec_fastest_player, "Spectate the fastest player", FCVAR_CLIENTCMD_CAN_EXECUTE ) +{ + C_NEO_Player *pNeoPlayer = C_NEO_Player::GetLocalNEOPlayer(); + if ( !pNeoPlayer || !pNeoPlayer->IsObserver() ) + return; + + if (engine->IsHLTV()) + { + if (HLTVCamera()->IsPVSLocked()) { - if (engine->IsHLTV()) - { - HLTVCamera()->SetPrimaryTarget(target->entindex()); - } - else + ConMsg( "%s: HLTV Camera is PVS locked\n", __FUNCTION__ ); + return; + } + + // We have up to date information on all the players, just do it here + float fastestSpeedSquared = 0; + CBasePlayer* pFastestEntity = nullptr; + for (int i = 1; i <= gpGlobals->maxClients; i++) + { + CBasePlayer* pPlayer = UTIL_PlayerByIndex(i); + if (pPlayer && !pPlayer->IsObserver() && pPlayer->GetAbsVelocity().LengthSqr() > fastestSpeedSquared) { - engine->ClientCmd( VarArgs("spec_player_entity_number %d", target->entindex()) ); + fastestSpeedSquared = pPlayer->GetAbsVelocity().LengthSqr(); + pFastestEntity = pPlayer; } } + + if (pFastestEntity) + HLTVCamera()->SetPrimaryTarget(pFastestEntity->entindex()); + } + else + { + engine->ClientCmd(VarArgs("spectate_fastest_player")); } } #endif // NEO diff --git a/src/game/client/glow_overlay.cpp b/src/game/client/glow_overlay.cpp index 7edecd8f0c..628a3bd7a0 100644 --- a/src/game/client/glow_overlay.cpp +++ b/src/game/client/glow_overlay.cpp @@ -159,7 +159,11 @@ void CGlowOverlay::UpdateSkyGlowObstruction( float zFar, bool bCacheFullSceneSta if ( PixelVisibility_IsAvailable() ) { // Trace a ray at the object. +#ifdef NEO + Vector pos = CurrentViewOrigin() + m_vDirection * zFar * 0.99f; +#else Vector pos = CurrentViewOrigin() + m_vDirection * zFar * 0.999f; +#endif // UNDONE: Can probably do only the pixelvis query in this case if you can figure out where // to put it - or save the position of this trace diff --git a/src/game/client/hltvcamera.cpp b/src/game/client/hltvcamera.cpp index 7fab97c4e9..b3b39ba8aa 100644 --- a/src/game/client/hltvcamera.cpp +++ b/src/game/client/hltvcamera.cpp @@ -22,6 +22,12 @@ #include "c_cs_player.h" #endif +#ifdef NEO +#include "neo_gamerules.h" +#include "c_neo_player.h" +#include "shareddefs.h" +#endif // NEO + ConVar spec_autodirector( "spec_autodirector", "1", FCVAR_CLIENTDLL | FCVAR_CLIENTCMD_CAN_EXECUTE, "Auto-director chooses best view modes while spectating" ); // memdbgon must be the last include file in a .cpp file!!! @@ -74,6 +80,9 @@ void C_HLTVCamera::Init() ListenForGameEvent( "hltv_message" ); ListenForGameEvent( "hltv_title" ); ListenForGameEvent( "hltv_status" ); +#ifdef NEO + ListenForGameEvent( "player_death" ); +#endif // NEO Reset(); @@ -306,7 +315,11 @@ C_BaseEntity *C_HLTVCamera::GetCameraMan() void C_HLTVCamera::CalcInEyeCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov ) { +#ifdef NEO + C_NEO_Player *pPlayer = static_cast(UTIL_PlayerByIndex( m_iTraget1 )); +#else C_BasePlayer *pPlayer = UTIL_PlayerByIndex( m_iTraget1 ); +#endif // NEO if ( !pPlayer ) return; @@ -324,11 +337,19 @@ void C_HLTVCamera::CalcInEyeCamView( Vector& eyeOrigin, QAngle& eyeAngles, float if ( pPlayer->GetFlags() & FL_DUCKING ) { +#ifdef NEO + m_vCamOrigin += VEC_DUCK_VIEW_NEOSCALE(pPlayer); +#else m_vCamOrigin += VEC_DUCK_VIEW; +#endif // NEO } else { +#ifdef NEO + m_vCamOrigin += VEC_VIEW_NEOSCALE(pPlayer); +#else m_vCamOrigin += VEC_VIEW; +#endif // NEO } eyeOrigin = m_vCamOrigin; @@ -375,6 +396,9 @@ void C_HLTVCamera::Accelerate( Vector& wishdir, float wishspeed, float accel ) } +#ifdef NEO +extern ConVar neo_fov; +#endif // NEO // movement code is a copy of CGameMovement::FullNoClipMove() void C_HLTVCamera::CalcRoamingView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov) { @@ -399,7 +423,22 @@ void C_HLTVCamera::CalcRoamingView(Vector& eyeOrigin, QAngle& eyeAngles, float& // Copy movement amounts float fmove = m_LastCmd.forwardmove * factor; float smove = m_LastCmd.sidemove * factor; - + +#ifdef NEO + const bool bDroneMove = m_LastCmd.buttons & IN_WALK; + if (bDroneMove) + { + forward.z = 0; + if (fmove && smove) + { + const float absFMove = fabs(fmove); + const float absSMove = fabs(smove); + const float moveMagnitude = FastSqrt((absFMove * absFMove) + (absSMove * absSMove)); + fmove *= absFMove / moveMagnitude; + smove *= absSMove / moveMagnitude; + } + } +#endif // NEO VectorNormalize (forward); // Normalize remainder of vectors VectorNormalize (right); // @@ -470,7 +509,11 @@ void C_HLTVCamera::CalcRoamingView(Vector& eyeOrigin, QAngle& eyeAngles, float& eyeOrigin = m_vCamOrigin; eyeAngles = m_aCamAngle; +#ifdef NEO + fov = neo_fov.GetFloat(); +#else fov = m_flFOV; +#endif // NEO } void C_HLTVCamera::CalcFixedView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov) @@ -551,6 +594,10 @@ void C_HLTVCamera::CalcView(Vector& origin, QAngle& angles, float& fov) } } +#ifdef NEO +ConVar cl_neo_hltvcamera_spectate_next_target_on_set_mode("cl_neo_hltvcamera_spectate_next_target_on_set_mode", "1", FCVAR_ARCHIVE, "Spectate next target when changing to in-eye or chase and don't have a valid target", true, 0, true, 1); +static bool bAllowChangingModeWhenSettingPrimaryTarget = true; +#endif // NEO void C_HLTVCamera::SetMode(int iMode) { if ( m_nCameraMode == iMode ) @@ -561,6 +608,14 @@ void C_HLTVCamera::SetMode(int iMode) int iOldMode = m_nCameraMode; m_nCameraMode = iMode; +#ifdef NEO + if (cl_neo_hltvcamera_spectate_next_target_on_set_mode.GetBool() && (iMode == OBS_MODE_IN_EYE || iMode == OBS_MODE_CHASE) && !GetPrimaryTarget()) + { + bAllowChangingModeWhenSettingPrimaryTarget = false; + SpecNextPlayer(false); + bAllowChangingModeWhenSettingPrimaryTarget = true; + } +#endif // NEO IGameEvent *event = gameeventmanager->CreateEvent( "hltv_changed_mode" ); if ( event ) { @@ -571,6 +626,17 @@ void C_HLTVCamera::SetMode(int iMode) } } +#ifdef NEO +enum +{ + NEO_HLTV_ON_TARGET_MODE_NOTHING = 0, + NEO_HLTV_ON_TARGET_MODE_IN_EYE, + NEO_HLTV_ON_TARGET_MODE_CHASE, + + NEO_HLTV_ON_TARGET_MODE__TOTAL = NEO_HLTV_ON_TARGET_MODE_CHASE +}; +ConVar cl_neo_hltvcamera_default_mode_when_setting_primary_target("cl_neo_hltvcamera_default_mode_when_setting_primary_target", "1", FCVAR_ARCHIVE, "What to do if changing primary targets and not in in-eye or chase. 0 = do nothing, 1 = switch to in-eye, 2 = switch to chase", true, 0, true, NEO_HLTV_ON_TARGET_MODE__TOTAL); +#endif // NEO void C_HLTVCamera::SetPrimaryTarget( int nEntity ) { if ( m_iTraget1 == nEntity ) @@ -579,6 +645,24 @@ void C_HLTVCamera::SetPrimaryTarget( int nEntity ) int iOldTarget = m_iTraget1; m_iTraget1 = nEntity; +#ifdef NEO + if (GetMode() != OBS_MODE_IN_EYE && GetMode() != OBS_MODE_CHASE && bAllowChangingModeWhenSettingPrimaryTarget) + { + switch (cl_neo_hltvcamera_default_mode_when_setting_primary_target.GetInt()) + { + case NEO_HLTV_ON_TARGET_MODE_IN_EYE: + SetMode(OBS_MODE_IN_EYE); + break; + case NEO_HLTV_ON_TARGET_MODE_CHASE: + SetMode(OBS_MODE_CHASE); + break; + case NEO_HLTV_ON_TARGET_MODE_NOTHING: + default: + break; + } + } + +#endif // NEO if ( GetMode() == OBS_MODE_ROAMING ) { Vector vOrigin; @@ -609,6 +693,37 @@ void C_HLTVCamera::SetPrimaryTarget( int nEntity ) gameeventmanager->FireEventClientSide( event ); } } +#ifdef NEO +void C_HLTVCamera::SpectateEvent(NeoSpectateEvent event) +{ + int entIndexLastPlayerMatchingEvent = -1; + switch (event) + { + case NEO_SPECTATE_EVENT_LAST_HURT: + entIndexLastPlayerMatchingEvent = NEORules()->GetLastHurt(); + break; + case NEO_SPECTATE_EVENT_LAST_SHOOTER: + entIndexLastPlayerMatchingEvent = NEORules()->GetLastShooter(); + break; + case NEO_SPECTATE_EVENT_LAST_ATTACKER: + entIndexLastPlayerMatchingEvent = NEORules()->GetLastAttacker(); + break; + case NEO_SPECTATE_EVENT_LAST_KILLER: + entIndexLastPlayerMatchingEvent = NEORules()->GetLastKiller(); + break; + case NEO_SPECTATE_EVENT_LAST_GHOSTER: + entIndexLastPlayerMatchingEvent = NEORules()->GetLastGhoster(); + break; + case NEO_SPECTATE_EVENT_LAST_EVENT: + default: + entIndexLastPlayerMatchingEvent = NEORules()->GetLastEvent(); + break; + } + + if (entIndexLastPlayerMatchingEvent > 0) + SetPrimaryTarget(entIndexLastPlayerMatchingEvent); +} +#endif // NEO void C_HLTVCamera::SpecNextPlayer( bool bInverse ) { @@ -642,6 +757,10 @@ void C_HLTVCamera::SpecNextPlayer( bool bInverse ) continue; // only follow living players +#ifdef NEO + if ( pPlayer->IsPlayerDead() ) + continue; +#endif if ( pPlayer->IsObserver() ) continue; @@ -668,6 +787,9 @@ void C_HLTVCamera::SpecPlayerByPredicate( const char *szSearch ) return; } +#ifdef NEO +ConVar cl_neo_hltvcamera_auto_observe_killer_if_observing_victim("cl_neo_hltvcamera_auto_observe_killer_if_observing_victim", "1", FCVAR_ARCHIVE, "If the current observer target is killed when in eye or following, switch observer target to the killer", true, 0, true, 1); +#endif // NEO void C_HLTVCamera::FireGameEvent( IGameEvent * event) { if ( !engine->IsHLTV() ) @@ -683,6 +805,7 @@ void C_HLTVCamera::FireGameEvent( IGameEvent * event) if ( !gViewPortInterface ) return; +#ifndef NEO if ( engine->IsPlayingDemo() ) { // for demo playback show full menu @@ -691,6 +814,7 @@ void C_HLTVCamera::FireGameEvent( IGameEvent * event) SetMode( OBS_MODE_ROAMING ); } else +#endif // NEO { // during live broadcast only show black bars gViewPortInterface->ShowPanel( PANEL_SPECGUI, true ); @@ -735,6 +859,22 @@ void C_HLTVCamera::FireGameEvent( IGameEvent * event) return; } +#ifdef NEO + if (Q_strcmp("player_death", type) == 0) + { + if (!cl_neo_hltvcamera_auto_observe_killer_if_observing_victim.GetBool() || (m_nCameraMode != OBS_MODE_IN_EYE && m_nCameraMode != OBS_MODE_CHASE)) + return; + + const int victimIndex = engine->GetPlayerForUserID(event->GetInt("userid")); + const int killerIndex = engine->GetPlayerForUserID(event->GetInt("attacker")); + if (victimIndex && m_iTraget1 == victimIndex && killerIndex) + { + SetPrimaryTarget( killerIndex ); + } + return; + } + +#endif // NEO // after this only auto-director commands follow // don't execute them if autodirector is off and PVS is unlocked if ( !spec_autodirector.GetBool() && !IsPVSLocked() ) diff --git a/src/game/client/hltvcamera.h b/src/game/client/hltvcamera.h index a6a90a45c9..5cfe354b13 100644 --- a/src/game/client/hltvcamera.h +++ b/src/game/client/hltvcamera.h @@ -11,6 +11,9 @@ #endif #include "GameEventListener.h" +#ifdef NEO +#include "neo_gamerules.h" +#endif // NEO class C_HLTVCamera : CGameEventListener { @@ -36,6 +39,9 @@ class C_HLTVCamera : CGameEventListener int GetMode(); // returns current camera mode C_BaseEntity *GetPrimaryTarget(); // return primary target void SetPrimaryTarget( int nEntity); // set the primary obs target +#ifdef NEO + void SpectateEvent(NeoSpectateEvent event); +#endif // NEO C_BaseEntity *GetCameraMan(); // return camera entity if any void CreateMove(CUserCmd *cmd); diff --git a/src/game/client/hud_crosshair.cpp b/src/game/client/hud_crosshair.cpp index a6016b3ae3..d956a1a998 100644 --- a/src/game/client/hud_crosshair.cpp +++ b/src/game/client/hud_crosshair.cpp @@ -168,9 +168,14 @@ bool CHudCrosshair::ShouldDraw( void ) { case OBS_MODE_IN_EYE: player = ToNEOPlayer(player->GetObserverTarget()); + if (engine->IsHLTV() && player && player->IsObserver()) + { // HLTV can spectate other spectators (and doesn't switch away from dead players by default) + return false; + } break; - case OBS_MODE_ROAMING: - return cl_observercrosshair.GetBool(); + // NEO NOTE (Adam) technically cl_observercrosshair should allow us to see a crosshair when roaming, but we're early returning in the draw function anyway if we dont have a valid target so + // I removed the OBS_MODE_ROAMING clause here so stv demo watchers, who still have an observer target even when roaming, dont see a crosshair when normal spectators watching live dont. + // NEO TODO Fix cl_observercrosshair for both default: return false; } diff --git a/src/game/client/neo/c_neo_player.cpp b/src/game/client/neo/c_neo_player.cpp index 4faa485307..6e97c7421b 100644 --- a/src/game/client/neo/c_neo_player.cpp +++ b/src/game/client/neo/c_neo_player.cpp @@ -1444,7 +1444,7 @@ void C_NEO_Player::UpdateGlowEffects(int iNewTeam) }; if (IsLocalPlayer()) { - for (int i = 1; i < gpGlobals->maxClients; i++) { + for (int i = 1; i <= gpGlobals->maxClients; i++) { CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); if (!pPlayer || pPlayer == this) { continue; diff --git a/src/game/client/neo/ui/neo_hud_ghost_beacons.cpp b/src/game/client/neo/ui/neo_hud_ghost_beacons.cpp index d17c8d3bec..c3a4c3058b 100644 --- a/src/game/client/neo/ui/neo_hud_ghost_beacons.cpp +++ b/src/game/client/neo/ui/neo_hud_ghost_beacons.cpp @@ -102,7 +102,8 @@ void CNEOHud_GhostBeacons::DrawNeoHudElement() if (!ghoster || !ghoster->m_bCarryingGhost || ghoster->GetTeamNumber() < FIRST_GAME_TEAM || - !ghoster->IsAlive() || NEORules()->IsRoundOver()) + !ghoster->IsAlive() || NEORules()->IsRoundOver() || + (engine->IsHLTV() && (localPlayer->GetObserverMode() != OBS_MODE_IN_EYE && localPlayer->GetObserverMode() != OBS_MODE_CHASE))) { return; } diff --git a/src/game/client/neo/ui/neo_hud_ghost_cap_point.cpp b/src/game/client/neo/ui/neo_hud_ghost_cap_point.cpp index f252720f00..ad1d049968 100644 --- a/src/game/client/neo/ui/neo_hud_ghost_cap_point.cpp +++ b/src/game/client/neo/ui/neo_hud_ghost_cap_point.cpp @@ -10,8 +10,18 @@ ConVar neo_ghost_cap_point_hud_scale_factor("neo_ghost_cap_point_hud_scale_factor", "1", FCVAR_ARCHIVE, "Ghost cap HUD element scaling factor", true, 0.01, false, 0); -ConVar cl_neo_hud_center_ghost_cap_size("cl_neo_hud_center_ghost_cap_size", "12.5", FCVAR_ARCHIVE, - "HUD center size in percentage to fade ghost cap point.", true, 1, false, 0); + +static float ghostCapViewCentreSize = 0; +extern ConVar cl_neo_hud_centre_ghost_cap_size; +void ghostCapViewCentreSizeChangeCallBack(IConVar* pConVar [[maybe_unused]] = nullptr, char const* pOldString [[maybe_unused]] = nullptr, float flOldValue [[maybe_unused]] = 0.f) { + int w, h; + vgui::surface()->GetScreenSize(w, h); + + const auto widerAxis = Max(w, h); + ghostCapViewCentreSize = widerAxis * (cl_neo_hud_centre_ghost_cap_size.GetFloat() / 100); +} +ConVar cl_neo_hud_centre_ghost_cap_size("cl_neo_hud_centre_ghost_cap_size", "12.5", FCVAR_ARCHIVE, + "HUD center size in percentage to fade ghost cap point.", true, 1, false, 0, ghostCapViewCentreSizeChangeCallBack); NEO_HUD_ELEMENT_DECLARE_FREQ_CVAR(GhostCapPoint, 0.01) @@ -48,10 +58,13 @@ void CNEOHud_GhostCapPoint::ApplySchemeSettings(vgui::IScheme *pScheme) vgui::surface()->GetScreenSize(m_iPosX, m_iPosY); SetBounds(0, 0, m_iPosX, m_iPosY); + + ghostCapViewCentreSizeChangeCallBack(); +} - // Override CNEOHud_WorldPosMarker's sizing with our own - const int widerAxis = Max(m_viewWidth, m_viewHeight); - m_viewCentreSize = widerAxis * (cl_neo_hud_center_ghost_cap_size.GetFloat() / 100.0f); +float CNEOHud_GhostCapPoint::GetHudCentreSize() const +{ + return ghostCapViewCentreSize; } extern ConVar cl_neo_hud_worldpos_verbose; diff --git a/src/game/client/neo/ui/neo_hud_ghost_cap_point.h b/src/game/client/neo/ui/neo_hud_ghost_cap_point.h index 95baf6132b..cd1b88cfc7 100644 --- a/src/game/client/neo/ui/neo_hud_ghost_cap_point.h +++ b/src/game/client/neo/ui/neo_hud_ghost_cap_point.h @@ -19,6 +19,8 @@ class CNEOHud_GhostCapPoint : public CNEOHud_WorldPosMarker virtual void UpdateStateForNeoHudElementDraw() override; virtual void DrawNeoHudElement() override; virtual ConVar *GetUpdateFrequencyConVar() const override; + + float GetHudCentreSize() const override; void SetTeam(const int team) { m_iCapTeam = team; } void SetRadius(const float radius) { m_flMyRadius = radius; } diff --git a/src/game/client/neo/ui/neo_hud_ghost_marker.cpp b/src/game/client/neo/ui/neo_hud_ghost_marker.cpp index b0cff5c79e..bfb9372712 100644 --- a/src/game/client/neo/ui/neo_hud_ghost_marker.cpp +++ b/src/game/client/neo/ui/neo_hud_ghost_marker.cpp @@ -25,9 +25,18 @@ using vgui::surface; ConVar neo_ghost_marker_hud_scale_factor("neo_ghost_marker_hud_scale_factor", "1", FCVAR_ARCHIVE, "Ghost marker HUD element scaling factor", true, 0.01, false, 0); -ConVar cl_neo_hud_center_ghost_marker_size("cl_neo_hud_center_ghost_marker_size", "12.5", FCVAR_ARCHIVE, - "HUD center size in percentage to fade ghost marker.", true, 1, false, 0); +static float ghostViewCentreSize = 0; +extern ConVar cl_neo_hud_centre_ghost_marker_size; +void ghostViewCentreSizeChangeCallBack(IConVar* pConVar [[maybe_unused]] = nullptr, char const* pOldString [[maybe_unused]] = nullptr, float flOldValue [[maybe_unused]] = 0.f) { + int w, h; + vgui::surface()->GetScreenSize(w, h); + + const auto widerAxis = Max(w, h); + ghostViewCentreSize = widerAxis * (cl_neo_hud_centre_ghost_marker_size.GetFloat() / 100); +} +ConVar cl_neo_hud_centre_ghost_marker_size("cl_neo_hud_centre_ghost_marker_size", "12.5", FCVAR_ARCHIVE, + "HUD center size in percentage to fade ghost marker.", true, 1, false, 0, ghostViewCentreSizeChangeCallBack); DECLARE_NAMED_HUDELEMENT(CNEOHud_GhostMarker, neo_ghost_marker); @@ -71,13 +80,16 @@ void CNEOHud_GhostMarker::ApplySchemeSettings(vgui::IScheme *pScheme) int wide, tall; surface()->GetScreenSize(wide, tall); SetBounds(0, 0, wide, tall); + + ghostViewCentreSizeChangeCallBack(); SetFgColor(COLOR_TRANSPARENT); SetBgColor(COLOR_TRANSPARENT); +} - // Override CNEOHud_WorldPosMarker's sizing with our own - const int widerAxis = Max(m_viewWidth, m_viewHeight); - m_viewCentreSize = widerAxis * (cl_neo_hud_center_ghost_marker_size.GetFloat() / 100.0f); +float CNEOHud_GhostMarker::GetHudCentreSize() const +{ + return ghostViewCentreSize; } extern ConVar cl_neo_hud_worldpos_verbose; diff --git a/src/game/client/neo/ui/neo_hud_ghost_marker.h b/src/game/client/neo/ui/neo_hud_ghost_marker.h index fdd9ed8c55..7840006f90 100644 --- a/src/game/client/neo/ui/neo_hud_ghost_marker.h +++ b/src/game/client/neo/ui/neo_hud_ghost_marker.h @@ -19,6 +19,8 @@ class CNEOHud_GhostMarker : public CNEOHud_WorldPosMarker virtual void ApplySchemeSettings(vgui::IScheme *pScheme) override; virtual void Paint() override; + + float GetHudCentreSize() const override; protected: virtual void UpdateStateForNeoHudElementDraw() override; diff --git a/src/game/client/neo/ui/neo_hud_round_state.cpp b/src/game/client/neo/ui/neo_hud_round_state.cpp index aebb5b5bae..7543dc33a5 100644 --- a/src/game/client/neo/ui/neo_hud_round_state.cpp +++ b/src/game/client/neo/ui/neo_hud_round_state.cpp @@ -21,6 +21,8 @@ #include "vgui_avatarimage.h" #include "neo_scoreboard.h" +#include "hltvcamera.h" + // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -35,7 +37,11 @@ ConVar cl_neo_hud_team_swap_sides("cl_neo_hud_team_swap_sides", "1", FCVAR_ARCHI g_pNeoScoreBoard->UpdateTeamColumnsPosition(GetLocalPlayerTeam()); }); ConVar cl_neo_squad_hud_original("cl_neo_squad_hud_original", "1", FCVAR_ARCHIVE, "Use the old squad HUD", true, 0.0, true, 1.0); -ConVar cl_neo_squad_hud_star_scale("cl_neo_squad_hud_star_scale", "0", FCVAR_ARCHIVE, "Scaling to apply from 1080p, 0 disables scaling"); +ConVar cl_neo_squad_hud_star_scale("cl_neo_squad_hud_star_scale", "0", FCVAR_ARCHIVE, "Scaling to apply from 1080p, 0 disables scaling", + [](IConVar* pConVar, char const* pOldString, float flOldValue) -> void { + if (g_pNeoHudRoundState) + g_pNeoHudRoundState->UpdateStarSize(); +}); extern ConVar sv_neo_dm_win_xp; extern ConVar cl_neo_streamermode; extern ConVar sv_neo_readyup_countdown; @@ -160,6 +166,20 @@ void CNEOHud_RoundState::UpdateAvatarSize() } } +void CNEOHud_RoundState::UpdateStarSize() +{ + IntDim res = {}; + surface()->GetScreenSize(res.w, res.h); + const float scale = cl_neo_squad_hud_star_scale.GetFloat() != 0 ? cl_neo_squad_hud_star_scale.GetFloat() * (res.h / 1080.0f) + : 1.f; + + for (auto* star : m_ipStars) + { + star->SetWide(192 * scale); + star->SetTall(48 * scale); + } +} + void CNEOHud_RoundState::ApplySchemeSettings(vgui::IScheme* pScheme) { BaseClass::ApplySchemeSettings(pScheme); @@ -179,23 +199,6 @@ void CNEOHud_RoundState::ApplySchemeSettings(vgui::IScheme* pScheme) surface()->GetScreenSize(res.w, res.h); m_iXpos = (res.w / 2); - if (cl_neo_squad_hud_star_scale.GetFloat()) - { - const float scale = cl_neo_squad_hud_star_scale.GetFloat() * (res.h / 1080.0); - for (auto* star : m_ipStars) - { - star->SetWide(192 * scale); - star->SetTall(48 * scale); - } - } - else { - for (auto* star : m_ipStars) - { - star->SetWide(192); - star->SetTall(48); - } - } - // Box dimensions [[maybe_unused]] int iSmallFontWidth = 0; int iFontHeight = 0; @@ -214,6 +217,7 @@ void CNEOHud_RoundState::ApplySchemeSettings(vgui::IScheme* pScheme) m_iBoxYEnd = Y_POS + iBoxHeight; UpdateAvatarSize(); + UpdateStarSize(); m_rectLeftTeamTotalLogo = vgui::IntRect{ .x0 = m_iLeftOffset, @@ -1388,6 +1392,12 @@ int CNEOHud_RoundState::GetSelectedPlayerInHud() CON_COMMAND_F( spec_player_by_hud_position, "Spectate player by position in the top hud", FCVAR_CLIENTCMD_CAN_EXECUTE ) { + if (engine->IsHLTV() && HLTVCamera()->IsPVSLocked()) + { + ConMsg( "%s: HLTV Camera is PVS locked\n", __FUNCTION__ ); + return; + } + if ( args.ArgC() != 2 ) { ConMsg( "Usage: spec_player_by_hud_position { player position in top hud, 0 indexed }\n" ); @@ -1411,12 +1421,18 @@ CON_COMMAND_F( spec_player_by_hud_position, "Spectate player by position in the const int entityIndex = g_pNeoHudRoundState->GetEntityIndexAtPositionInHud(positionInHud, true); if (entityIndex) { - engine->ClientCmd( VarArgs("spec_player_entity_number %d", entityIndex) ); + engine->IsHLTV() ? HLTVCamera()->SetPrimaryTarget(entityIndex) : engine->ClientCmd(VarArgs("spec_player_entity_number %d", entityIndex)); } } CON_COMMAND_F( spec_next_entity_in_hud, "Spectate next valid player to the right of the current spectate target", FCVAR_CLIENTCMD_CAN_EXECUTE ) { + if (engine->IsHLTV() && HLTVCamera()->IsPVSLocked()) + { + ConMsg( "%s: HLTV Camera is PVS locked\n", __FUNCTION__ ); + return; + } + if (!g_pNeoHudRoundState) return; @@ -1434,12 +1450,18 @@ CON_COMMAND_F( spec_next_entity_in_hud, "Spectate next valid player to the right const int playerIndex = g_pNeoHudRoundState->GetEntityIndexAtPositionInHud(g_pNeoHudRoundState->GetNextAlivePlayerInHud(spectateTargetMinusIndexedPositionInHud, false)); if (playerIndex) { - engine->ClientCmd(VarArgs("spec_player_entity_number %d", playerIndex)); + engine->IsHLTV() ? HLTVCamera()->SetPrimaryTarget(playerIndex) : engine->ClientCmd(VarArgs("spec_player_entity_number %d", playerIndex)); } } CON_COMMAND_F( spec_previous_entity_in_hud, "Spectate next valid player to the left of the current spectate target", FCVAR_CLIENTCMD_CAN_EXECUTE ) { + if (engine->IsHLTV() && HLTVCamera()->IsPVSLocked()) + { + ConMsg( "%s: HLTV Camera is PVS locked\n", __FUNCTION__ ); + return; + } + if (!g_pNeoHudRoundState) return; @@ -1457,12 +1479,18 @@ CON_COMMAND_F( spec_previous_entity_in_hud, "Spectate next valid player to the l const int playerIndex = g_pNeoHudRoundState->GetEntityIndexAtPositionInHud(g_pNeoHudRoundState->GetNextAlivePlayerInHud(spectateTargetMinusIndexedPositionInHud, true)); if (playerIndex) { - engine->ClientCmd(VarArgs("spec_player_entity_number %d", playerIndex)); + engine->IsHLTV() ? HLTVCamera()->SetPrimaryTarget(playerIndex) : engine->ClientCmd(VarArgs("spec_player_entity_number %d", playerIndex)); } } CON_COMMAND_F( select_next_alive_player_in_hud, "Select the next alive player in the top hud", FCVAR_CLIENTCMD_CAN_EXECUTE ) { + if (engine->IsHLTV() && HLTVCamera()->IsPVSLocked()) + { + ConMsg( "%s: Selection is used to switch observer target in spectate_player_selected_in_hud, but HLTV Camera is PVS locked\n", __FUNCTION__ ); + return; + } + if (!g_pNeoHudRoundState) return; @@ -1475,6 +1503,12 @@ CON_COMMAND_F( select_next_alive_player_in_hud, "Select the next alive player in CON_COMMAND_F( select_previous_alive_player_in_hud, "Select the previous alive player in the top hud", FCVAR_CLIENTCMD_CAN_EXECUTE ) { + if (engine->IsHLTV() && HLTVCamera()->IsPVSLocked()) + { + ConMsg( "%s: Selection is used to switch observer target in spectate_player_selected_in_hud, but HLTV Camera is PVS locked\n", __FUNCTION__ ); + return; + } + if (!g_pNeoHudRoundState) return; @@ -1487,6 +1521,12 @@ CON_COMMAND_F( select_previous_alive_player_in_hud, "Select the previous alive p CON_COMMAND_F( spectate_player_selected_in_hud, "Spectate entity selected in the top hud", FCVAR_CLIENTCMD_CAN_EXECUTE ) { + if (engine->IsHLTV() && HLTVCamera()->IsPVSLocked()) + { + ConMsg( "%s: HLTV Camera is PVS locked\n", __FUNCTION__ ); + return; + } + if (!g_pNeoHudRoundState) return; @@ -1497,6 +1537,6 @@ CON_COMMAND_F( spectate_player_selected_in_hud, "Spectate entity selected in the const int entityIndex = g_pNeoHudRoundState->GetSelectedPlayerInHud(); if (entityIndex) { - engine->ClientCmd( VarArgs("spec_player_entity_number %d", entityIndex) ); + engine->IsHLTV() ? HLTVCamera()->SetPrimaryTarget(entityIndex) : engine->ClientCmd(VarArgs("spec_player_entity_number %d", entityIndex)); } } \ No newline at end of file diff --git a/src/game/client/neo/ui/neo_hud_round_state.h b/src/game/client/neo/ui/neo_hud_round_state.h index 5d8fdf7589..9cd375cf4d 100644 --- a/src/game/client/neo/ui/neo_hud_round_state.h +++ b/src/game/client/neo/ui/neo_hud_round_state.h @@ -30,6 +30,7 @@ class CNEOHud_RoundState : public CNEOHud_ChildElement, public CHudElement, publ virtual void Paint(); void UpdateAvatarSize(); + void UpdateStarSize(); // sunk cost fallacy and all that, in hindsight I should have made all of this only work with two teams and split players into separate resizeable cutlvectors by team instead of doing this minus index stuff. // basically any minusindexed n index works such that negative values give the -nth player in the player list, and any positive values give the (n - leftTeamTotal)th player in the list int GetEntityIndexAtPositionInHud(int position, bool positionIsZeroIndexed = false); diff --git a/src/game/client/neo/ui/neo_hud_worldpos_marker.cpp b/src/game/client/neo/ui/neo_hud_worldpos_marker.cpp index da281bf1aa..6de58bc8c5 100644 --- a/src/game/client/neo/ui/neo_hud_worldpos_marker.cpp +++ b/src/game/client/neo/ui/neo_hud_worldpos_marker.cpp @@ -6,8 +6,16 @@ using vgui::surface; -ConVar cl_neo_hud_centre_size("cl_neo_hud_centre_size", "25", FCVAR_ARCHIVE, - "HUD centre size in percentage to fade markers.", true, 1, false, 0); +static float viewCentreSize = 0; +extern ConVar cl_neo_hud_centre_size; +void viewCentreSizeChangeCallBack(IConVar* pConVar [[maybe_unused]] = nullptr, char const* pOldString [[maybe_unused]] = nullptr, float flOldValue [[maybe_unused]] = 0.f) { + int w, h; + vgui::surface()->GetScreenSize(w, h); + + const auto widerAxis = Max(w, h); + viewCentreSize = widerAxis * (cl_neo_hud_centre_size.GetFloat() / 100); +} +ConVar cl_neo_hud_centre_size("cl_neo_hud_centre_size", "25", FCVAR_ARCHIVE, "HUD centre size in percentage to fade markers.", true, 1, false, 0, viewCentreSizeChangeCallBack); ConVar cl_neo_hud_worldpos_verbose("cl_neo_hud_worldpos_verbose", "1", FCVAR_ARCHIVE, "Display full world pos marker string", true, 0, true, 1); CNEOHud_WorldPosMarker::CNEOHud_WorldPosMarker(const char* pElementName, Panel* parent) @@ -15,7 +23,6 @@ CNEOHud_WorldPosMarker::CNEOHud_WorldPosMarker(const char* pElementName, Panel* Panel(parent, pElementName), m_viewWidth(0), m_viewHeight(0), - m_viewCentreSize(0), m_viewCentreX(0), m_viewCentreY(0) { @@ -30,12 +37,16 @@ void CNEOHud_WorldPosMarker::ApplySchemeSettings(vgui::IScheme* pScheme) m_viewCentreX = m_viewWidth / 2; m_viewCentreY = m_viewHeight / 2; - auto widerAxis = Max(m_viewWidth, m_viewHeight); - m_viewCentreSize = widerAxis * (static_cast(cl_neo_hud_centre_size.GetInt()) / 100); + viewCentreSizeChangeCallBack(); // value of viewCentreSize depends on screen size, so update during applySchemeSettings BaseClass::ApplySchemeSettings(pScheme); } +float CNEOHud_WorldPosMarker::GetHudCentreSize() const +{ + return viewCentreSize; +} + void CNEOHud_WorldPosMarker::RectToPoint(int x0, int x1, int y0, int y1, int& x, int& y) { x = (x0 - x1) / 2; @@ -50,14 +61,14 @@ float CNEOHud_WorldPosMarker::DistanceToCentre(int x, int y) const float CNEOHud_WorldPosMarker::GetFadeValueTowardsScreenCentre(int x, int y) const { - float innerArea = m_viewCentreSize / 2.f; + float innerArea = GetHudCentreSize() / 2.f; auto dist = DistanceToCentre(x, y); if(dist <= innerArea) { return 1; } - if(dist <= m_viewCentreSize) + if(dist <= GetHudCentreSize()) { return (innerArea - (dist - innerArea)) / innerArea; } @@ -67,14 +78,14 @@ float CNEOHud_WorldPosMarker::GetFadeValueTowardsScreenCentre(int x, int y) cons float CNEOHud_WorldPosMarker::GetFadeValueTowardsScreenCentreInverted(int x, int y, float min) const { - float innerArea = m_viewCentreSize / 2.f; + float innerArea = GetHudCentreSize() / 2.f; auto dist = DistanceToCentre(x, y); if(dist <= innerArea) { return min; } - if(dist <= m_viewCentreSize) + if(dist <= GetHudCentreSize()) { return Max(min, (dist - innerArea) / innerArea); } @@ -84,14 +95,14 @@ float CNEOHud_WorldPosMarker::GetFadeValueTowardsScreenCentreInverted(int x, int float CNEOHud_WorldPosMarker::GetFadeValueTowardsScreenCentreInAndOut(int x, int y, float min) const { - float innerArea = m_viewCentreSize / 2.f; + float innerArea = GetHudCentreSize() / 2.f; auto dist = DistanceToCentre(x, y); if(dist <= innerArea) { return Max(min, dist / innerArea); } - if(dist <= m_viewCentreSize) + if(dist <= GetHudCentreSize()) { return (innerArea - (dist - innerArea)) / innerArea; } diff --git a/src/game/client/neo/ui/neo_hud_worldpos_marker.h b/src/game/client/neo/ui/neo_hud_worldpos_marker.h index 82099c22cd..7447e30a9d 100644 --- a/src/game/client/neo/ui/neo_hud_worldpos_marker.h +++ b/src/game/client/neo/ui/neo_hud_worldpos_marker.h @@ -21,7 +21,8 @@ abstract_class CNEOHud_WorldPosMarker : public CNEOHud_ChildElement, public CHud protected: int m_viewWidth, m_viewHeight; - float m_viewCentreSize; + + virtual float GetHudCentreSize() const; static Color FadeColour(const Color& originalColour, float alphaMultiplier); diff --git a/src/game/client/neo/ui/neo_hud_worldpos_marker_generic.cpp b/src/game/client/neo/ui/neo_hud_worldpos_marker_generic.cpp index 27904eeac7..1c52fd816f 100644 --- a/src/game/client/neo/ui/neo_hud_worldpos_marker_generic.cpp +++ b/src/game/client/neo/ui/neo_hud_worldpos_marker_generic.cpp @@ -7,7 +7,6 @@ #include "neo_gamerules.h" extern ConVar neo_ghost_cap_point_hud_scale_factor; -extern ConVar cl_neo_hud_center_ghost_cap_size; NEO_HUD_ELEMENT_DECLARE_FREQ_CVAR( WorldPosMarker_Generic, 0 ) @@ -52,9 +51,6 @@ void CNEOHud_WorldPosMarker_Generic::ApplySchemeSettings( vgui::IScheme *pScheme vgui::surface()->GetScreenSize(m_iPosX, m_iPosY); SetBounds(0, 0, m_iPosX, m_iPosY); - - const int widerAxis = max(m_viewWidth, m_viewHeight); - m_viewCentreSize = widerAxis * (cl_neo_hud_center_ghost_cap_size.GetFloat() / 100.0f); } void CNEOHud_WorldPosMarker_Generic::UpdateStateForNeoHudElementDraw() diff --git a/src/game/server/neo/bot/neo_bot.cpp b/src/game/server/neo/bot/neo_bot.cpp index 696b3c953d..c3a1d8199f 100644 --- a/src/game/server/neo/bot/neo_bot.cpp +++ b/src/game/server/neo/bot/neo_bot.cpp @@ -550,36 +550,6 @@ CNEOBot::CNEOBot() SetAutoJump(0.f, 0.f); V_memcpy(&m_profile, &FIXED_DEFAULT_PROFILE, sizeof(CNEOBotProfile)); - - // set default values for convars only present on the client - edict_t* edict = GetEntity()->edict(); - if (edict) - { - { - char szCrhSerial[NEO_XHAIR_SEQMAX] = {}; - DefaultCrosshairSerial(szCrhSerial); - engine->SetFakeClientConVarValue(edict, "cl_neo_crosshair", szCrhSerial); - } - - constexpr struct { - const char* name, *value; - } convars[] = { - { "cl_neo_pvs_cull_roaming_observer", "0" }, - { "cl_neo_streamermode", "0" }, - { "cl_neo_tachi_prefer_auto", "1" }, - { "cl_neo_taking_damage_sounds", "0" }, - { "cl_onlysteamnick", "0" }, - { "hap_HasDevice", "0" }, - { "neo_clantag", "" }, - { "neo_fov", "90" }, - { "neo_name", "" }, - }; - - for (const auto& convar : convars) - { - engine->SetFakeClientConVarValue(edict, convar.name, convar.value); - } - } } diff --git a/src/game/server/neo/bot/neo_bot_manager.cpp b/src/game/server/neo/bot/neo_bot_manager.cpp index 6b5fdaeffa..2bd6ecc8c4 100644 --- a/src/game/server/neo/bot/neo_bot_manager.cpp +++ b/src/game/server/neo/bot/neo_bot_manager.cpp @@ -316,7 +316,7 @@ void CNEOBotManager::MaintainBotQuota() nNonNEOBotsOnGameTeams++; naCountClasses[iPlayerTeam][iClass]++; } - else if ( iPlayerTeam == TEAM_SPECTATOR ) + else if ( iPlayerTeam == TEAM_SPECTATOR && !pPlayer->IsHLTV() ) { nSpectators++; } diff --git a/src/game/server/neo/neo_player.cpp b/src/game/server/neo/neo_player.cpp index cf2660fef9..d1dc54d1d0 100644 --- a/src/game/server/neo/neo_player.cpp +++ b/src/game/server/neo/neo_player.cpp @@ -141,13 +141,6 @@ END_SCRIPTDESC(); static constexpr int SHOWMENU_STRLIMIT = 512; -int CNEO_Player::m_iLastHurt = -1; -int CNEO_Player::m_iLastShooter = -1; -int CNEO_Player::m_iLastEvent = -1; -int CNEO_Player::m_iLastAttacker = -1; -int CNEO_Player::m_iLastKiller = -1; -int CNEO_Player::m_iLastGhoster = -1; - const Vector CNEO_Player::VECTOR_INVALID_WAYPOINT = vec3_invalid; CBaseEntity *g_pLastJinraiSpawn, *g_pLastNSFSpawn; @@ -582,6 +575,36 @@ CNEO_Player::CNEO_Player() m_flNextPingTime = 0; ResetBotCommandState(); + + // set default values for convars only present on the client and read by the server + edict_t* pEdict = edict(); + if (pEdict) + { + { + char szCrhSerial[NEO_XHAIR_SEQMAX] = {}; + DefaultCrosshairSerial(szCrhSerial); + engine->SetFakeClientConVarValue(pEdict, "cl_neo_crosshair", szCrhSerial); + } + + constexpr struct { + const char* name, *value; + } convars[] = { + { "cl_neo_pvs_cull_roaming_observer", "0" }, + { "cl_neo_streamermode", "0" }, + { "cl_neo_tachi_prefer_auto", "1" }, + { "cl_neo_taking_damage_sounds", "0" }, + { "cl_onlysteamnick", "0" }, + { "hap_HasDevice", "0" }, + { "neo_clantag", "" }, + { "neo_fov", "90" }, + { "neo_name", "" }, + }; + + for (const auto& convar : convars) + { + engine->SetFakeClientConVarValue(pEdict, convar.name, convar.value); + } + } } CNEO_Player::~CNEO_Player( void ) @@ -1942,12 +1965,12 @@ bool CNEO_Player::ClientCommand( const CCommand &args ) } return true; } - else if ( FStrEq(args[0], "spec_fastest_player" )) + else if ( FStrEq(args[0], "spectate_fastest_player" )) { int observerMode = GetObserverMode(); if ( observerMode > OBS_MODE_FIXED ) { - int fastestSpeedSquared = 0; + float fastestSpeedSquared = 0; CBaseEntity* pFastestEntity = nullptr; for (int i = 1; i <= gpGlobals->maxClients; i++) { @@ -1970,12 +1993,12 @@ bool CNEO_Player::ClientCommand( const CCommand &args ) return true; } - else if ( FStrEq(args[0], "spec_last_hurt" )) + else if ( FStrEq(args[0], "spectate_last_hurt" )) { int observerMode = GetObserverMode(); if ( observerMode > OBS_MODE_FIXED ) { - CBaseEntity* pPlayer = UTIL_EntityByIndex(m_iLastHurt); + CBaseEntity* pPlayer = UTIL_EntityByIndex(NEORules()->GetLastHurt()); if (SetObserverTarget( pPlayer )) { m_bForcedObserverMode = false; if (observerMode != OBS_MODE_IN_EYE && observerMode != OBS_MODE_CHASE) @@ -1987,12 +2010,12 @@ bool CNEO_Player::ClientCommand( const CCommand &args ) return true; } - else if ( FStrEq(args[0], "spec_last_shooter" )) + else if ( FStrEq(args[0], "spectate_last_shooter" )) { int observerMode = GetObserverMode(); if ( observerMode > OBS_MODE_FIXED ) { - CBaseEntity* pPlayer = UTIL_EntityByIndex(m_iLastShooter); + CBaseEntity* pPlayer = UTIL_EntityByIndex(NEORules()->GetLastShooter()); if (SetObserverTarget( pPlayer )) { m_bForcedObserverMode = false; if (observerMode != OBS_MODE_IN_EYE && observerMode != OBS_MODE_CHASE) @@ -2004,12 +2027,12 @@ bool CNEO_Player::ClientCommand( const CCommand &args ) return true; } - else if ( FStrEq(args[0], "spec_last_event" )) + else if ( FStrEq(args[0], "spectate_last_event" )) { int observerMode = GetObserverMode(); if ( observerMode > OBS_MODE_FIXED ) { - CBaseEntity* pPlayer = UTIL_EntityByIndex(m_iLastEvent); + CBaseEntity* pPlayer = UTIL_EntityByIndex(NEORules()->GetLastEvent()); if (SetObserverTarget( pPlayer )) { m_bForcedObserverMode = false; if (observerMode != OBS_MODE_IN_EYE && observerMode != OBS_MODE_CHASE) @@ -2021,12 +2044,12 @@ bool CNEO_Player::ClientCommand( const CCommand &args ) return true; } - else if ( FStrEq(args[0], "spec_last_attacker" )) + else if ( FStrEq(args[0], "spectate_last_attacker" )) { int observerMode = GetObserverMode(); if ( observerMode > OBS_MODE_FIXED ) { - CBaseEntity* pPlayer = UTIL_EntityByIndex(m_iLastAttacker); + CBaseEntity* pPlayer = UTIL_EntityByIndex(NEORules()->GetLastAttacker()); if (SetObserverTarget( pPlayer )) { m_bForcedObserverMode = false; if (observerMode != OBS_MODE_IN_EYE && observerMode != OBS_MODE_CHASE) @@ -2038,12 +2061,12 @@ bool CNEO_Player::ClientCommand( const CCommand &args ) return true; } - else if ( FStrEq(args[0], "spec_last_killer" )) + else if ( FStrEq(args[0], "spectate_last_killer" )) { int observerMode = GetObserverMode(); if ( observerMode > OBS_MODE_FIXED ) { - CBaseEntity* pPlayer = UTIL_EntityByIndex(m_iLastKiller); + CBaseEntity* pPlayer = UTIL_EntityByIndex(NEORules()->GetLastKiller()); if (SetObserverTarget( pPlayer )) { m_bForcedObserverMode = false; if (observerMode != OBS_MODE_IN_EYE && observerMode != OBS_MODE_CHASE) @@ -2055,12 +2078,12 @@ bool CNEO_Player::ClientCommand( const CCommand &args ) return true; } - else if ( FStrEq(args[0], "spec_last_ghoster" )) + else if ( FStrEq(args[0], "spectate_last_ghoster" )) { int observerMode = GetObserverMode(); if ( observerMode > OBS_MODE_FIXED ) { - CBaseEntity* pPlayer = UTIL_EntityByIndex(m_iLastGhoster); + CBaseEntity* pPlayer = UTIL_EntityByIndex(NEORules()->GetLastGhoster()); if (SetObserverTarget( pPlayer )) { m_bForcedObserverMode = false; if (observerMode != OBS_MODE_IN_EYE && observerMode != OBS_MODE_CHASE) @@ -2240,7 +2263,7 @@ void CNEO_Player::Event_Killed( const CTakeDamageInfo &info ) CBaseEntity* pAttacker = info.GetAttacker(); if (pAttacker && pAttacker->IsPlayer()) // we can only have players as a spectate target atm { - m_iLastKiller = m_iLastEvent = pAttacker->entindex(); + NEORules()->SetLastKiller(pAttacker->entindex()); for (int i = 1; i <= gpGlobals->maxClients; i++) { CBasePlayer* pObserver = dynamic_cast(UTIL_EntityByIndex(i)); @@ -2519,6 +2542,8 @@ void CNEO_Player::FireBullets ( const FireBulletsInfo_t &info ) // effect lasts 0.5 seconds, but allow 200-300ms leeway with GetFogObscuredRatio cache window m_botThermOpticCamoDisruptedTimer.Start(0.2f); } + + NEORules()->SetLastShooter(entindex()); } void CNEO_Player::Weapon_Equip(CBaseCombatWeapon* pWeapon) @@ -3154,7 +3179,7 @@ AttackersTotals CNEO_Player::GetAttackersTotals() const int CNEO_Player::OnTakeDamage_Alive(const CTakeDamageInfo& info) { - m_iLastHurt = entindex(); + NEORules()->SetLastHurt(entindex()); if (m_takedamage != DAMAGE_EVENTS_ONLY) { if (sv_neo_warmup_godmode.GetBool()) @@ -3175,7 +3200,7 @@ int CNEO_Player::OnTakeDamage_Alive(const CTakeDamageInfo& info) if (auto *attacker = ToNEOPlayer(info.GetAttacker())) { const int attackerIdx = attacker->entindex(); - m_iLastAttacker = m_iLastEvent = attackerIdx; // NEO TODO (Adam) Once we can spectate non-players, let last attacker be non-neoplayer (Jeff) + NEORules()->SetLastAttacker(entindex()); // NEO TODO (Adam) Once we can spectate non-players, let last attacker be non-neoplayer (Jeff) // Separate the fractional amount of damage from the whole const float flFractionalDamage = info.GetDamage() - floor(info.GetDamage()); diff --git a/src/game/server/neo/neo_player.h b/src/game/server/neo/neo_player.h index 6e9a15f7b4..ab80573be2 100644 --- a/src/game/server/neo/neo_player.h +++ b/src/game/server/neo/neo_player.h @@ -339,19 +339,6 @@ class CNEO_Player : public CHL2MP_Player // Cache for GetFogObscuredRatio for each player mutable CNEO_Player_FogCacheEntry m_playerFogCache[MAX_PLAYERS_ARRAY_SAFE]; - - static int m_iLastHurt; - static int m_iLastShooter; -public: - void SetLastShooter() { m_iLastShooter = entindex(); }; - -private: - static int m_iLastEvent; - static int m_iLastAttacker; - static int m_iLastKiller; - static int m_iLastGhoster; -public: - void SetLastGhoster() { m_iLastGhoster = m_iLastEvent = entindex(); }; private: CNEO_Player(const CNEO_Player&); diff --git a/src/game/shared/neo/neo_gamerules.cpp b/src/game/shared/neo/neo_gamerules.cpp index 3057b64758..fb4548fb4b 100644 --- a/src/game/shared/neo/neo_gamerules.cpp +++ b/src/game/shared/neo/neo_gamerules.cpp @@ -15,6 +15,7 @@ #include "engine/SndInfo.h" #include "engine/IEngineSound.h" #include "filesystem.h" +#include "hltvcamera.h" #else #include "neo_player.h" #include "team.h" @@ -147,6 +148,7 @@ ConVar sv_neo_ghost_spawn_bias("sv_neo_ghost_spawn_bias", "0", FCVAR_REPLICATED, ConVar sv_neo_juggernaut_spawn_bias("sv_neo_juggernaut_spawn_bias", "0", FCVAR_REPLICATED, "Spawn juggernaut in the same location as the previous round on odd-indexed rounds (Round 1 = index 0)", true, 0, true, 1); ConVar sv_neo_teamdamage_assists("sv_neo_teamdamage_assists", "0", FCVAR_REPLICATED, "Whether to drain XP when assisting the death of a teammate.", true, 0.0f, true, 1.0f); ConVar sv_neo_client_autorecord("sv_neo_client_autorecord", "0", FCVAR_REPLICATED | FCVAR_DONTRECORD, "Record demos clientside", true, 0, true, 1); +ConVar sv_neo_server_autorecord("sv_neo_server_autorecord", "0", FCVAR_NONE, "Automatically record demos serverside", true, 0, true, 1); #ifdef CLIENT_DLL ConVar cl_neo_client_autorecord_allow("cl_neo_client_autorecord_allow", "1", FCVAR_ARCHIVE, "Allow servers to automatically record demos on the client", true, 0, true, 1); #endif @@ -160,9 +162,10 @@ ConVar sv_neo_reject_opengl_mesa_check("sv_neo_reject_opengl_mesa_check", "0", 0 , true, 0.0f, true, 1.0f); #endif +extern ConVar sv_neo_comp; static void neoSvCompCallback(IConVar* var, const char* pOldValue, float flOldValue) { - const bool bCurrentValue = !(bool)flOldValue; + const bool bCurrentValue = sv_neo_comp.GetBool(); sv_neo_readyup_lobby.SetValue(bCurrentValue); mp_forcecamera.SetValue(bCurrentValue); // 0 = OBS_ALLOWS_ALL, 1 = OBS_ALLOW_TEAM. For strictly original neotokyo spectator experience, 2 = OBS_ALLOW_NONE sv_neo_spraydisable.SetValue(bCurrentValue); @@ -283,6 +286,12 @@ BEGIN_NETWORK_TABLE_NOBASE( CNEORules, DT_NEORules ) RecvPropInt(RECVINFO(m_iJuggernautPlayerIndex)), RecvPropBool(RECVINFO(m_bJuggernautItemExists)), RecvPropEHandle(RECVINFO(m_hJuggernaut)), + RecvPropInt(RECVINFO(m_iLastHurt)), + RecvPropInt(RECVINFO(m_iLastShooter)), + RecvPropInt(RECVINFO(m_iLastEvent)), + RecvPropInt(RECVINFO(m_iLastAttacker)), + RecvPropInt(RECVINFO(m_iLastKiller)), + RecvPropInt(RECVINFO(m_iLastGhoster)), #else SendPropTime(SENDINFO(m_flNeoNextRoundStartTime)), SendPropTime(SENDINFO(m_flNeoRoundStartTime)), @@ -311,6 +320,12 @@ BEGIN_NETWORK_TABLE_NOBASE( CNEORules, DT_NEORules ) SendPropInt(SENDINFO(m_iJuggernautPlayerIndex), NumBitsForCount(MAX_PLAYERS_ARRAY_SAFE), SPROP_UNSIGNED), SendPropBool(SENDINFO(m_bJuggernautItemExists)), SendPropEHandle(SENDINFO(m_hJuggernaut)), + SendPropInt(SENDINFO(m_iLastHurt), NumBitsForCount(MAX_PLAYERS_ARRAY_SAFE), SPROP_UNSIGNED), + SendPropInt(SENDINFO(m_iLastShooter), NumBitsForCount(MAX_PLAYERS_ARRAY_SAFE), SPROP_UNSIGNED), + SendPropInt(SENDINFO(m_iLastEvent), NumBitsForCount(MAX_PLAYERS_ARRAY_SAFE), SPROP_UNSIGNED), + SendPropInt(SENDINFO(m_iLastAttacker), NumBitsForCount(MAX_PLAYERS_ARRAY_SAFE), SPROP_UNSIGNED), + SendPropInt(SENDINFO(m_iLastKiller), NumBitsForCount(MAX_PLAYERS_ARRAY_SAFE), SPROP_UNSIGNED), + SendPropInt(SENDINFO(m_iLastGhoster), NumBitsForCount(MAX_PLAYERS_ARRAY_SAFE), SPROP_UNSIGNED), #endif END_NETWORK_TABLE() @@ -370,8 +385,8 @@ const NeoGameTypeSettings NEO_GAME_TYPE_SETTINGS[NEO_GAME_TYPE__TOTAL] = { } BEGIN_RECV_TABLE( CNEOGameRulesProxy, DT_NEOGameRulesProxy ) - RecvPropDataTable( "neo_gamerules_data", 0, 0, - &REFERENCE_RECV_TABLE( DT_NEORules ), + RecvPropDataTable( "neo_gamerules_data", 0, 0, + &REFERENCE_RECV_TABLE( DT_NEORules ), RecvProxy_NEORules ) END_RECV_TABLE() #else @@ -1264,6 +1279,13 @@ void CNEORules::Think(void) { if (m_bGotMatchWinner) { + IGameEvent *event = gameeventmanager->CreateEvent("game_end"); + if (event) + { + event->SetInt("winner", m_iMatchWinner); + gameeventmanager->FireEvent(event); + } + if (sv_neo_readyup_lobby.GetBool() && !sv_neo_readyup_autointermission.GetBool()) { ResetMapSessionCommon(); @@ -1793,6 +1815,9 @@ float CNEORules::MirrorDamageMultiplier() const void CNEORules::FireGameEvent(IGameEvent* event) { +#ifdef GAME_DLL + static bool isServerRecording = false; +#endif // GAME_DLL const char *type = event->GetName(); if (Q_strcmp(type, "round_start") == 0) @@ -1803,24 +1828,32 @@ void CNEORules::FireGameEvent(IGameEvent* event) if (!engine->IsRecordingDemo() && sv_neo_client_autorecord.GetBool() && cl_neo_client_autorecord_allow.GetBool()) { - StartAutoClientRecording(); + StartAutoRecording(); } #endif #ifdef GAME_DLL m_flNeoRoundStartTime = gpGlobals->curtime; m_flNeoNextRoundStartTime = 0; + if (sv_neo_server_autorecord.GetBool() && !isServerRecording) + { + isServerRecording = StartAutoRecording(); + } #endif } -#ifdef CLIENT_DLL if (Q_strcmp(type, "game_end") == 0) { +#ifdef CLIENT_DLL if (sv_neo_client_autorecord.GetBool() && cl_neo_client_autorecord_allow.GetBool()) { engine->StopDemoRecording(); } - } #endif +#ifdef GAME_DLL + engine->ServerCommand("tv_stoprecord;"); + isServerRecording = false; +#endif // GAME_DLL + } } #ifdef GAME_DLL @@ -3767,16 +3800,8 @@ void CNEORules::SetWinningTeam(int team, int iWinReason, bool bForceMapReset, bo (team == TEAM_JINRAI ? "NSF" : "Jinrai"), (team == TEAM_JINRAI ? "Jinrai" : "NSF")); } - if (gotMatchWinner) - { - IGameEvent *event = gameeventmanager->CreateEvent("game_end"); - if (event) - { - event->SetInt("winner", team); - gameeventmanager->FireEvent(event); - } - } m_bGotMatchWinner = gotMatchWinner; + m_iMatchWinner = team; } #endif @@ -4803,3 +4828,68 @@ void CNEORules::InitDefaultAIRelationships( void ) CBaseCombatCharacter::SetDefaultRelationship(CLASS_EARTH_FAUNA, CLASS_PLAYER_ALLY_VITAL,D_HT, 0); } #endif + +#ifdef CLIENT_DLL +auto spectateChecks = []() { + if (engine->IsHLTV() && HLTVCamera()->IsPVSLocked()) + { + ConMsg("%s: HLTV Camera is PVS locked\n", __FUNCTION__); + return false; + } + + C_NEO_Player* pNeoPlayer = C_NEO_Player::GetLocalNEOPlayer(); + if (!pNeoPlayer || !pNeoPlayer->IsObserver()) + return false; + + return true; + +}; + +CON_COMMAND_F( spec_last_hurt, "Spectate the last hurt player", FCVAR_CLIENTCMD_CAN_EXECUTE ) +{ + if (!spectateChecks()) + return; + + engine->IsHLTV() ? HLTVCamera()->SpectateEvent(NEO_SPECTATE_EVENT_LAST_HURT) : engine->ClientCmd(VarArgs("spectate_last_hurt")); +} + +CON_COMMAND_F( spec_last_shooter, "Spectate the last shooter", FCVAR_CLIENTCMD_CAN_EXECUTE ) +{ + if (!spectateChecks()) + return; + + engine->IsHLTV() ? HLTVCamera()->SpectateEvent(NEO_SPECTATE_EVENT_LAST_SHOOTER) : engine->ClientCmd(VarArgs("spectate_last_shooter")); +} + +CON_COMMAND_F( spec_last_event, "Spectate the last attacker, killer or ghoster", FCVAR_CLIENTCMD_CAN_EXECUTE ) +{ + if (!spectateChecks()) + return; + + engine->IsHLTV() ? HLTVCamera()->SpectateEvent(NEO_SPECTATE_EVENT_LAST_EVENT) : engine->ClientCmd(VarArgs("spectate_last_event")); +} + +CON_COMMAND_F( spec_last_attacker, "Spectate the last attacker", FCVAR_CLIENTCMD_CAN_EXECUTE ) +{ + if (!spectateChecks()) + return; + + engine->IsHLTV() ? HLTVCamera()->SpectateEvent(NEO_SPECTATE_EVENT_LAST_ATTACKER) : engine->ClientCmd(VarArgs("spectate_last_attacker")); +} + +CON_COMMAND_F( spec_last_killer, "Spectate the last killer", FCVAR_CLIENTCMD_CAN_EXECUTE ) +{ + if (!spectateChecks()) + return; + + engine->IsHLTV() ? HLTVCamera()->SpectateEvent(NEO_SPECTATE_EVENT_LAST_KILLER) : engine->ClientCmd(VarArgs("spectate_last_killer")); +} + +CON_COMMAND_F( spec_last_ghoster, "Spectate the last ghoster", FCVAR_CLIENTCMD_CAN_EXECUTE ) +{ + if (!spectateChecks()) + return; + + engine->IsHLTV() ? HLTVCamera()->SpectateEvent(NEO_SPECTATE_EVENT_LAST_GHOSTER) : engine->ClientCmd(VarArgs("spectate_last_ghoster")); +} +#endif // CLIENT_DLL diff --git a/src/game/shared/neo/neo_gamerules.h b/src/game/shared/neo/neo_gamerules.h index c8cbfd2d3e..f930314683 100644 --- a/src/game/shared/neo/neo_gamerules.h +++ b/src/game/shared/neo/neo_gamerules.h @@ -160,6 +160,15 @@ enum NeoHudElements : NEO_HUD_BITS_UNDERLYING_TYPE { NEO_HUD_ELEMENT_WORLDPOS_MARKER_ENT = (static_cast(1) << 16), }; +enum NeoSpectateEvent { + NEO_SPECTATE_EVENT_LAST_HURT = 0, + NEO_SPECTATE_EVENT_LAST_SHOOTER, + NEO_SPECTATE_EVENT_LAST_EVENT, + NEO_SPECTATE_EVENT_LAST_ATTACKER, + NEO_SPECTATE_EVENT_LAST_KILLER, + NEO_SPECTATE_EVENT_LAST_GHOSTER, +}; + class CNEORules : public CHL2MPRules, public CGameEventListener { public: @@ -435,6 +444,21 @@ class CNEORules : public CHL2MPRules, public CGameEventListener void JuggernautActivated(CNEO_Player *pPlayer); void JuggernautDeactivated(CNEO_Juggernaut *pJuggernaut); void JuggernautTotalRemoval(CNEO_Juggernaut *pJuggernaut); + + void SetLastHurt(const int index) { m_iLastHurt = index; } + void SetLastShooter(const int index) { m_iLastShooter = index; } + void SetLastAttacker(const int index) { m_iLastAttacker = m_iLastEvent = index; } + void SetLastKiller(const int index) { m_iLastKiller = m_iLastEvent = index; } + void SetLastGhoster(const int index) { m_iLastGhoster = m_iLastEvent = index; } +#endif // GAME_DLL +public: + const int GetLastHurt() const { return m_iLastHurt; } + const int GetLastShooter() const { return m_iLastShooter; } + const int GetLastEvent() const { return m_iLastEvent; } + const int GetLastAttacker() const { return m_iLastAttacker; } + const int GetLastKiller() const { return m_iLastKiller; } + const int GetLastGhoster() const { return m_iLastGhoster; } +#ifdef GAME_DLL private: CNEO_Juggernaut *m_pJuggernautItem = nullptr; CNEO_Player *m_pJuggernautPlayer = nullptr; @@ -466,6 +490,7 @@ class CNEORules : public CHL2MPRules, public CGameEventListener Vector m_vecPreviousGhostSpawn = vec3_origin; Vector m_vecPreviousJuggernautSpawn = vec3_origin; bool m_bGotMatchWinner = false; + int m_iMatchWinner = TEAM_UNASSIGNED; #endif CNetworkVar(int, m_nRoundStatus); CNetworkVar(int, m_iHiddenHudElements); @@ -498,6 +523,14 @@ class CNEORules : public CHL2MPRules, public CGameEventListener CNetworkVar(float, m_flNeoRoundStartTime); CNetworkVar(float, m_flNeoNextRoundStartTime); + // For spectator commands. Networked so can be saved in demos for hltv + CNetworkVar(int, m_iLastHurt); + CNetworkVar(int, m_iLastShooter); + CNetworkVar(int, m_iLastEvent); + CNetworkVar(int, m_iLastAttacker); + CNetworkVar(int, m_iLastKiller); + CNetworkVar(int, m_iLastGhoster); + public: // VIP networked variables CNetworkVar(int, m_iEscortingTeam); diff --git a/src/game/shared/neo/neo_misc.cpp b/src/game/shared/neo/neo_misc.cpp index 7516656300..2fab2da642 100644 --- a/src/game/shared/neo/neo_misc.cpp +++ b/src/game/shared/neo/neo_misc.cpp @@ -2,14 +2,20 @@ #ifdef CLIENT_DLL #include "steamclientpublic.h" +#endif // CLIENT_DLL +#include "cbase.h" +#include "neo_gamerules.h" #include "vgui/ISystem.h" #include "tier3.h" #include extern ConVar sv_neo_comp_name; +#ifdef CLIENT_DLL #define DEMOS_DIRECTORY_NAME "demos" -#endif +#else +#define DEMOS_DIRECTORY_NAME "serverdemos" +#endif // CLIENT_DLL // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -37,9 +43,27 @@ extern ConVar sv_neo_comp_name; return LoopAroundMinMax(iValue, 0, iSize - 1); } -#ifdef CLIENT_DLL -void StartAutoClientRecording() +bool StartAutoRecording() { +#ifdef GAME_DLL + CBasePlayer* sourceTV = nullptr; + for (int i = 1; i <= gpGlobals->maxClients; i++) + { + CBasePlayer* pPlayer = UTIL_PlayerByIndex(i); + if (pPlayer && pPlayer->IsHLTV()) + { + sourceTV = pPlayer; + break; + } + } + if (!sourceTV) + { + ConLog("SourceTV client not found, server side demo not recording.\n"); + return false; + } +#endif // GAME_DLL + +#ifdef CLIENT_DLL // SteamID char steamSection[64]; const CSteamID steamID = GetSteamIDForPlayerIndex(GetLocalPlayerIndex()); @@ -52,10 +76,15 @@ void StartAutoClientRecording() { V_strncpy(steamSection, "nosteamid", sizeof(steamSection)); } +#endif // CLIENT_DLL // Map name char mapSection[256]; +#ifdef CLIENT_DLL V_strncpy(mapSection, GameRules()->MapName(), sizeof(mapSection)); +#else + V_strncpy(mapSection, gpGlobals->mapname.ToCStr(), sizeof(mapSection)); +#endif // CLIENT_DLL // Time and date char timeSection[16]; @@ -72,11 +101,19 @@ void StartAutoClientRecording() if (compSection[0] != '\0') { +#ifdef CLIENT_DLL V_snprintf(replayName, sizeof(replayName), "%s_%s_%s_%s", compSection, timeSection, mapSection, steamSection); +#else + V_snprintf(replayName, sizeof(replayName), "%s_%s_%s", compSection, timeSection, mapSection); +#endif // CLIENT_DLL } else { +#ifdef CLIENT_DLL V_snprintf(replayName, sizeof(replayName), "%s_%s_%s", timeSection, mapSection, steamSection); +#else + V_snprintf(replayName, sizeof(replayName), "%s_%s", timeSection, mapSection); +#endif // CLIENT_DLL } if (!g_pFullFileSystem->IsDirectory(DEMOS_DIRECTORY_NAME)) @@ -84,6 +121,11 @@ void StartAutoClientRecording() g_pFullFileSystem->CreateDirHierarchy(DEMOS_DIRECTORY_NAME); } +#ifdef CLIENT_DLL engine->StartDemoRecording(replayName, DEMOS_DIRECTORY_NAME); // Start recording -} -#endif \ No newline at end of file + return engine->IsRecordingDemo(); +#else + engine->ServerCommand(UTIL_VarArgs("tv_stoprecord;tv_record \"%s/%s\";\n", DEMOS_DIRECTORY_NAME, replayName)); + return true; // hopefully we're recording. Last line of tv_status prints if a demo is being recorded but don't see how to get at that value here +#endif // CLIENT_DLL +} \ No newline at end of file diff --git a/src/game/shared/neo/neo_misc.h b/src/game/shared/neo/neo_misc.h index 817eb15978..b359a67441 100644 --- a/src/game/shared/neo/neo_misc.h +++ b/src/game/shared/neo/neo_misc.h @@ -21,6 +21,4 @@ struct SZWSZTexts #define SZWSZ_INIT(STR) {.szStr = STR, .wszStr = L"" STR} #define X_SZWSZ_INIT(STR) SZWSZ_INIT(STR) -#ifdef CLIENT_DLL -void StartAutoClientRecording(); -#endif \ No newline at end of file +bool StartAutoRecording(); \ No newline at end of file diff --git a/src/game/shared/neo/weapons/weapon_ghost.cpp b/src/game/shared/neo/weapons/weapon_ghost.cpp index 027260a212..7f99233e33 100644 --- a/src/game/shared/neo/weapons/weapon_ghost.cpp +++ b/src/game/shared/neo/weapons/weapon_ghost.cpp @@ -227,7 +227,7 @@ void CWeaponGhost::Equip(CBaseCombatCharacter *pNewOwner) auto neoOwner = assert_cast(pNewOwner); #ifdef GAME_DLL - neoOwner->SetLastGhoster(); + NEORules()->SetLastGhoster(neoOwner->entindex()); #endif // GAME_DLL // Prevent ghoster from sprinting diff --git a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp index 4f459615ab..d2b4b22187 100644 --- a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp +++ b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp @@ -956,9 +956,6 @@ void CNEOBaseCombatWeapon::PrimaryAttack(void) return; } -#ifdef GAME_DLL - pPlayer->SetLastShooter(); -#endif // GAME_DLL if (!(GetNeoWepBits() & NEO_WEP_SUPPRESSED)) { pPlayer->DoMuzzleFlash(); diff --git a/src/game/shared/sdk/sdk_basegrenade_projectile.cpp b/src/game/shared/sdk/sdk_basegrenade_projectile.cpp index e4f0991cdd..5a8948431b 100644 --- a/src/game/shared/sdk/sdk_basegrenade_projectile.cpp +++ b/src/game/shared/sdk/sdk_basegrenade_projectile.cpp @@ -14,6 +14,8 @@ float GetCurrentGravity( void ); #ifndef NEO #include "c_sdk_player.h" +#else +#include "neo_gamerules.h" #endif // NEO #else @@ -42,7 +44,14 @@ BEGIN_NETWORK_TABLE( CBaseGrenadeProjectile, DT_BaseGrenadeProjectile ) END_NETWORK_TABLE() +#ifdef NEO +ConVar sv_neo_grenade_show_path("sv_neo_grenade_show_path", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "Whether to show a grenade's path to all players, and for how long", true, 0, true, 15.f); +#endif // NEO #ifdef CLIENT_DLL +#ifdef NEO +// We're not clearing the debug overlay atm, make sure this is very short +ConVar cl_neo_grenade_show_path("cl_neo_grenade_show_path", "0", FCVAR_ARCHIVE | FCVAR_USERINFO, "Whether to show a grenade's path when spectating, and for how long", true, 0, true, 2.f); +#endif // NEO void CBaseGrenadeProjectile::PostDataUpdate( DataUpdateType_t type ) @@ -127,6 +136,48 @@ END_NETWORK_TABLE() { SetNextClientThink(gpGlobals->curtime + TICK_INTERVAL); } + if (cl_neo_grenade_show_path.GetBool() || sv_neo_grenade_show_path.GetBool()) + { + DrawPath(); + SetNextClientThink(gpGlobals->curtime + TICK_INTERVAL); + } + } + + void CBaseGrenadeProjectile::DrawPath() + { + CBasePlayer *player = UTIL_PlayerByIndex(GetLocalPlayerIndex()); + if ( player == NULL ) + return; + + const bool showPathInSpec = cl_neo_grenade_show_path.GetBool() && player->GetTeamNumber() == TEAM_SPECTATOR; + const bool showPathWhenServerEnabled = sv_neo_grenade_show_path.GetBool(); + if (!showPathInSpec && !showPathWhenServerEnabled) + return; + + const Vector origin = GetAbsOrigin(); + if (!m_vLastDrawPosition.IsValid()) + { + m_vLastDrawPosition = origin; + return; + } + + if (m_vLastDrawPosition.DistToSqr(origin) < 0.1f) + return; + + float r = 0, g = 0, b = 0; + NEORules()->GetTeamGlowColor(GetTeamNumber(), r, g, b); + r *= 255; + g *= 255; + b *= 255; + if (GetDamage()) + { + const float timeAlive = (gpGlobals->curtime - m_flNeoCreateTime) * 0.5f; + r = clamp(r * (1 - timeAlive) + (255.f * timeAlive), 0.f, 255.f); + g = clamp(g * (1 - timeAlive), 0.f, 255.f); + b = clamp(b * (1 - timeAlive), 0.f, 255.f); + } + DebugDrawLine(m_vLastDrawPosition, origin, r, g, b, true, showPathWhenServerEnabled ? sv_neo_grenade_show_path.GetFloat() : cl_neo_grenade_show_path.GetFloat()); + m_vLastDrawPosition = origin; } #endif // NEO @@ -142,6 +193,15 @@ END_NETWORK_TABLE() // smaller, cube bounding box so we rest on the ground SetSize( Vector ( -2, -2, -2 ), Vector ( 2, 2, 2 ) ); + + if (sv_neo_grenade_show_path.GetBool()) + { // Transmit the grenade to all players so they can see the path outside their pvs + SetTransmitState(FL_EDICT_ALWAYS); + } + else + { // Spectators will still want to see the grenade if they have cl_neo_grenade_show_path set + SetTransmitState(FL_EDICT_FULLCHECK); + } } void CBaseGrenadeProjectile::DangerSoundThink( void ) diff --git a/src/game/shared/sdk/sdk_basegrenade_projectile.h b/src/game/shared/sdk/sdk_basegrenade_projectile.h index 3e7b016ac1..4a932f888d 100644 --- a/src/game/shared/sdk/sdk_basegrenade_projectile.h +++ b/src/game/shared/sdk/sdk_basegrenade_projectile.h @@ -43,6 +43,10 @@ class CBaseGrenadeProjectile : public CBaseGrenade #ifdef NEO virtual void ClientThink() override; float m_flTemperature; +private: + void DrawPath(); + Vector m_vLastDrawPosition = Vector(VEC_T_NAN, VEC_T_NAN, VEC_T_NAN); +public: #endif // NEO float m_flSpawnTime; #else @@ -63,6 +67,21 @@ class CBaseGrenadeProjectile : public CBaseGrenade // sit still until it had gotten a few updates from the server. void SetupInitialTransmittedGrenadeVelocity( const Vector &velocity ); + int ShouldTransmit(const CCheckTransmitInfo* pInfo) override + { + CBaseEntity *pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt ); + + if ( pRecipientEntity && pRecipientEntity->GetTeamNumber() == TEAM_SPECTATOR ) + { + const char* wantsToSeeProjectilePath = engine->GetClientConVarValue(pRecipientEntity->entindex(), "cl_neo_grenade_show_path"); + if (wantsToSeeProjectilePath && *wantsToSeeProjectilePath && (V_atof(wantsToSeeProjectilePath) != 0.f)) + { + return FL_EDICT_ALWAYS; + } + } + return FL_EDICT_PVSCHECK; + } + protected: #ifdef NEO diff --git a/src/utils/serverplugin_sample/serverplugin_empty.cpp b/src/utils/serverplugin_sample/serverplugin_empty.cpp index 42e4c1d3bf..aa70191546 100644 --- a/src/utils/serverplugin_sample/serverplugin_empty.cpp +++ b/src/utils/serverplugin_sample/serverplugin_empty.cpp @@ -337,6 +337,9 @@ CON_COMMAND( DoAskConnect, "Server plugin example of using the ask connect dialo kv->SetString( "title", pServerIP ); // The IP address of the server to connect to goes in the "title" field. kv->SetInt( "time", 3 ); +#ifdef NEO + // If using this as an example consider the below will miss the client with entindex gpGlobals->maxClients +#endif // NEO for ( int i=1; i < gpGlobals->maxClients; i++ ) { edict_t *pEdict = engine->PEntityOfEntIndex( i );