From 242840ddc54d9f1e1c54bdaf8f70a9d915cb89ea Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Sat, 11 Apr 2026 15:54:02 +0100 Subject: [PATCH 01/19] init --- src/game/shared/neo/weapons/weapon_ghost.h | 1 - src/game/shared/neo/weapons/weapon_neobasecombatweapon.h | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/shared/neo/weapons/weapon_ghost.h b/src/game/shared/neo/weapons/weapon_ghost.h index dffbe25ca7..db576612c4 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.h b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h index 563fd62cd4..32df8e448d 100644 --- a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h +++ b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h @@ -140,6 +140,7 @@ class CNEOBaseCombatWeapon : public CBaseHL2MPCombatWeapon virtual void FinishReload(void) override; virtual bool CanBeSelected(void) override; + virtual int ObjectCaps(void) { return CBaseCombatWeapon::ObjectCaps();}; CNEOWeaponInfo const &GetNEOWpnData() const; From 3c7d09c699d1224422da62fc7a303280a72bdf31 Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Mon, 13 Apr 2026 16:13:37 +0100 Subject: [PATCH 02/19] init --- src/game/client/c_baseplayer.cpp | 13 ++ src/game/client/c_baseplayer.h | 4 + src/game/client/neo/c_neo_player.cpp | 38 ++++ src/game/client/neo/c_neo_player.h | 4 + .../client/neo/ui/neo_hud_context_hint.cpp | 184 ++++++++++++------ src/game/client/neo/ui/neo_hud_context_hint.h | 7 +- src/game/server/neo/neo_player.cpp | 69 +++---- src/game/server/neo/neo_player.h | 1 - src/game/shared/neo/neo_player_shared.cpp | 6 + src/game/shared/neo/neo_player_shared.h | 3 + .../neo/weapons/weapon_neobasecombatweapon.h | 15 +- 11 files changed, 233 insertions(+), 111 deletions(-) diff --git a/src/game/client/c_baseplayer.cpp b/src/game/client/c_baseplayer.cpp index dabd3125b0..665b6ec17f 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 be8c78f157..6c1e46db62 100644 --- a/src/game/client/c_baseplayer.h +++ b/src/game/client/c_baseplayer.h @@ -162,7 +162,11 @@ class C_BasePlayer : public C_BaseCombatCharacter, public CGameEventListener bool IsHLTV() const; bool IsReplay() const; void ResetObserverMode(); +#ifdef NEO + virtual bool IsBot( void ) const { return false; } +#else bool IsBot( void ) const { return false; } +#endif // NEO // Eye position.. virtual Vector EyePosition(); diff --git a/src/game/client/neo/c_neo_player.cpp b/src/game/client/neo/c_neo_player.cpp index c473a99797..85dc94a5f0 100644 --- a/src/game/client/neo/c_neo_player.cpp +++ b/src/game/client/neo/c_neo_player.cpp @@ -2104,3 +2104,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 590769b11b..17d6539415 100644 --- a/src/game/client/neo/c_neo_player.h +++ b/src/game/client/neo/c_neo_player.h @@ -166,6 +166,8 @@ class C_NEO_Player : public C_HL2MP_Player bool IsAirborne() const { return (!(GetFlags() & FL_ONGROUND)); } bool IsInVision() const { return m_bInVision; } bool IsInAim() const { return m_bInAim; } + + virtual bool IsBot(void) const override { return GetFlags() & FL_FAKECLIENT; } int GetAttackersScores(const int attackerIdx) const; int GetAttackerHits(const int attackerIdx) const; @@ -188,6 +190,8 @@ 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; private: 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 dfb5975451..68b76db88b 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" @@ -22,7 +23,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 +31,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 +49,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); } @@ -69,89 +68,150 @@ void CNEOHud_ContextHint::ApplySchemeSettings(vgui::IScheme* pScheme) bool CNEOHud_ContextHint::ShouldDraw() { if (!cl_neo_hud_context_hint_enabled.GetBool()) - { return false; - } - C_BasePlayer* pLocalPlayer = C_BasePlayer::GetLocalPlayer(); - if (!pLocalPlayer) - { - return false; - } - - C_BaseEntity* pObserverTargetEntity = pLocalPlayer->GetObserverTarget(); - C_BasePlayer* pObserverTargetPlayer = (pObserverTargetEntity && pObserverTargetEntity->IsPlayer()) ? ToBasePlayer(pObserverTargetEntity) : nullptr; + return true; +} - auto eObserverMode = pLocalPlayer->GetObserverMode(); - bool bIsSpectating = (eObserverMode == OBS_MODE_CHASE || eObserverMode == OBS_MODE_IN_EYE); +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; +void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() +{ + 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') + { + V_strncpy(szUppercaseKeyBinding, useKeyBinding, sizeof(szUppercaseKeyBinding)); + V_strupr(szUppercaseKeyBinding); + } + else { - m_bHintShownForCurrentSpecTarget = false; - m_hLastSpecTarget = pObserverTargetPlayer; + 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())) + // 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_BaseEntity* pObserverTargetEntity = pLocalNeoPlayer->GetObserverTarget(); + pObserverTargetEntity && pObserverTargetEntity->IsPlayer()) { - // 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); + if (NEORules()->GetRoundStatus() != PostRound + && GameResources()->IsFakePlayer(pObserverTargetEntity->entindex()) + && pLocalNeoPlayer->InSameTeam(pObserverTargetEntity) && NEORules()->IsTeamplay() + && sv_neo_spec_replace_player_bot_enable.GetBool() + && pLocalNeoPlayer->m_iXP > sv_neo_spec_replace_player_min_exp.GetInt()) + { + // update hint duration + if (pObserverTargetEntity != m_hLastSpecTarget.Get()) + { + m_flDisplayEndTime = gpGlobals->curtime + cl_neo_spec_takeover_player_hint_time_sec.GetFloat(); + m_hLastSpecTarget = pObserverTargetEntity; + } + + V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"[%hs] Control Bot", szUppercaseKeyBinding); + showTakeOverHint = true; + } } } + + if (!showTakeOverHint) + { + 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) + m_flDisplayEndTime = gpGlobals->curtime; + if (CBaseEntity *pUseEntity = pLocalNeoPlayer->FindUseEntity(); + pUseEntity) { - m_flDisplayTime = gpGlobals->curtime + cl_neo_spec_takeover_player_hint_time_sec.GetFloat(); - m_bHintShownForCurrentSpecTarget = true; + // 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 + // Clear hint + { + } + } + // Juggernaut hint + else if (Q_strcmp(pUseEntity->GetClassname(), "neo_juggernaut") == 0) + { + if (CNEO_Juggernaut* pJuggernaut = static_cast(pUseEntity); + pJuggernaut) + { + m_flDisplayEndTime = gpGlobals->curtime + 1.f; + if (pJuggernaut->m_bLocked) + { + V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"Juggernaut is locked"); + } + else // NEO TODO (Adam) network and check m_hHoldingPlayer, time left until juggernaut taken? + { + V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"Hold [%hs] take the Juggernaut", szUppercaseKeyBinding); + } + } + } } - - // If the hint is displaying and the timer hasn't expired, keep displaying it. - if (gpGlobals->curtime < m_flDisplayTime) + else { - return true; + // Bot command hint + { + if (C_NEO_Player* pTargetPlayer = pLocalNeoPlayer->PlayerUseTraceLine(); + pTargetPlayer + && pTargetPlayer->IsBot() + && NEORules()->IsTeamplay() && pTargetPlayer->GetTeamNumber() == pLocalNeoPlayer->GetTeamNumber()) + { + m_flDisplayEndTime = gpGlobals->curtime + 1.f; + if (sv_neo_bot_cmdr_enable.GetBool()) + { + V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"[%hs] command %hs", szUppercaseKeyBinding, 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? + } + // else if NEO TODO (Adam) check if bots are frozen because of no navigation mesh or nb_player_stop, show appropriate message here? + } + } } - } - // If conditions are not met, or timer has expired, hide the hint. - return false; -} - -void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() -{ - 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)); } } void CNEOHud_ContextHint::DrawNeoHudElement() { + if (m_wszHintText[0] == L'\0') + return; + + if (m_flDisplayEndTime <= gpGlobals->curtime) + return; + int iScrWide, iScrTall; vgui::surface()->GetScreenSize(iScrWide, iScrTall); 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 18894a2255..08cee2cee3 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,10 @@ 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; CPanelAnimationVar(vgui::HFont, m_hHintFont, "font", "NeoUINormal"); CPanelAnimationVarAliasType(int, m_iPaddingX, "padding_x", "4", "proportional_xpos"); diff --git a/src/game/server/neo/neo_player.cpp b/src/game/server/neo/neo_player.cpp index 3d6c42f6d4..7e807f01e3 100644 --- a/src/game/server/neo/neo_player.cpp +++ b/src/game/server/neo/neo_player.cpp @@ -507,6 +507,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) @@ -3586,44 +3612,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(); @@ -4057,16 +4045,11 @@ 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 { diff --git a/src/game/server/neo/neo_player.h b/src/game/server/neo/neo_player.h index ba3252822b..507a41d907 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; diff --git a/src/game/shared/neo/neo_player_shared.cpp b/src/game/shared/neo/neo_player_shared.cpp index 7de2bb4dec..2ea7c4b167 100644 --- a/src/game/shared/neo/neo_player_shared.cpp +++ b/src/game/shared/neo/neo_player_shared.cpp @@ -51,6 +51,12 @@ 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_min_exp("sv_neo_spec_replace_player_min_exp", + "0", FCVAR_REPLICATED, + "Minimum experience allowed to takeover players ", + true, -999, true, 999); + bool IsAllowedToZoom(CNEOBaseCombatWeapon *pWep) { if (!pWep || pWep->m_bInReload || !pWep->CanAim()) diff --git a/src/game/shared/neo/neo_player_shared.h b/src/game/shared/neo/neo_player_shared.h index 48aa6ac098..9d0009b0a1 100644 --- a/src/game/shared/neo/neo_player_shared.h +++ b/src/game/shared/neo/neo_player_shared.h @@ -20,6 +20,9 @@ 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_min_exp; + ////////////////////////////////////////////////////// // NEO MOVEMENT DEFINITIONS diff --git a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h index 32df8e448d..f47850e854 100644 --- a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h +++ b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h @@ -140,7 +140,20 @@ class CNEOBaseCombatWeapon : public CBaseHL2MPCombatWeapon virtual void FinishReload(void) override; virtual bool CanBeSelected(void) override; - virtual int ObjectCaps(void) { return CBaseCombatWeapon::ObjectCaps();}; + virtual int ObjectCaps(void) override + { + int caps = BaseClass::ObjectCaps(); + if (!IsFollowingEntity() +#ifdef GAME_DLL + && !HasSpawnFlags(SF_WEAPON_NO_PLAYER_PICKUP) +#endif // GAME_DLL + ) + { + caps |= FCAP_IMPULSE_USE; + } + + return caps; + }; CNEOWeaponInfo const &GetNEOWpnData() const; From 325384553d2d0a92d54c0c6c03cea5a1948a46fe Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Mon, 13 Apr 2026 16:14:29 +0100 Subject: [PATCH 03/19] comment --- src/game/client/neo/ui/neo_hud_context_hint.cpp | 2 ++ 1 file changed, 2 insertions(+) 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 68b76db88b..5b715dd5bc 100644 --- a/src/game/client/neo/ui/neo_hud_context_hint.cpp +++ b/src/game/client/neo/ui/neo_hud_context_hint.cpp @@ -231,6 +231,8 @@ void CNEOHud_ContextHint::DrawNeoHudElement() vgui::surface()->DrawSetTextColor(m_TextColor); vgui::surface()->DrawSetTextPos(iBoxX + m_iPaddingX, iBoxY + m_iPaddingY); vgui::surface()->DrawPrintText(m_wszHintText, static_cast(wcslen(m_wszHintText))); + + // NEO TODO (Adam) different text colour for keybind, instruction and target name? } void CNEOHud_ContextHint::Paint() From 0774aa0e9237fc1693445f96359a4b2ad77f48a1 Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Mon, 13 Apr 2026 20:00:54 +0100 Subject: [PATCH 04/19] use touch instead of equip, bunch of other things ive since forgotten --- src/game/client/c_baseplayer.h | 4 - src/game/client/c_playerresource.cpp | 11 ++ src/game/client/c_playerresource.h | 2 + src/game/client/neo/c_neo_player.h | 9 +- .../client/neo/ui/neo_hud_context_hint.cpp | 38 +++--- src/game/server/neo/neo_player.cpp | 108 +++--------------- src/game/server/neo/neo_player.h | 6 +- src/game/server/player_resource.cpp | 4 + src/game/server/player_resource.h | 1 + src/game/shared/neo/neo_player_shared.cpp | 36 +++++- src/game/shared/neo/neo_player_shared.h | 2 + .../weapons/weapon_neobasecombatweapon.cpp | 23 ++-- src/public/igameresources.h | 1 + 13 files changed, 114 insertions(+), 131 deletions(-) diff --git a/src/game/client/c_baseplayer.h b/src/game/client/c_baseplayer.h index 6c1e46db62..be8c78f157 100644 --- a/src/game/client/c_baseplayer.h +++ b/src/game/client/c_baseplayer.h @@ -162,11 +162,7 @@ class C_BasePlayer : public C_BaseCombatCharacter, public CGameEventListener bool IsHLTV() const; bool IsReplay() const; void ResetObserverMode(); -#ifdef NEO - virtual bool IsBot( void ) const { return false; } -#else bool IsBot( void ) const { return false; } -#endif // NEO // Eye position.. virtual Vector EyePosition(); diff --git a/src/game/client/c_playerresource.cpp b/src/game/client/c_playerresource.cpp index 03e7ef1e5c..42c107dc0d 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 13402ce816..12ff142aa4 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/neo/c_neo_player.h b/src/game/client/neo/c_neo_player.h index 17d6539415..a95e97a3c8 100644 --- a/src/game/client/neo/c_neo_player.h +++ b/src/game/client/neo/c_neo_player.h @@ -166,8 +166,6 @@ class C_NEO_Player : public C_HL2MP_Player bool IsAirborne() const { return (!(GetFlags() & FL_ONGROUND)); } bool IsInVision() const { return m_bInVision; } bool IsInAim() const { return m_bInAim; } - - virtual bool IsBot(void) const override { return GetFlags() & FL_FAKECLIENT; } int GetAttackersScores(const int attackerIdx) const; int GetAttackerHits(const int attackerIdx) const; @@ -192,7 +190,8 @@ class C_NEO_Player : public C_HL2MP_Player #endif // GLOWS_ENABLE C_NEO_Player* PlayerUseTraceLine(); virtual void PlayerUse() override; - + + bool ValidTakeoverTargetFor(CNEO_Player* pPlayerTakingOver); private: char m_sNameWithTakeoverContextProcessingBuffer[MAX_PLAYER_NAME_LENGTH]; @@ -205,6 +204,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 5b715dd5bc..98d133e8ff 100644 --- a/src/game/client/neo/ui/neo_hud_context_hint.cpp +++ b/src/game/client/neo/ui/neo_hud_context_hint.cpp @@ -104,31 +104,27 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() if (auto eObserverMode = pLocalNeoPlayer->GetObserverMode(); (eObserverMode == OBS_MODE_CHASE || eObserverMode == OBS_MODE_IN_EYE)) { - if (C_BaseEntity* pObserverTargetEntity = pLocalNeoPlayer->GetObserverTarget(); - pObserverTargetEntity && pObserverTargetEntity->IsPlayer()) + if (C_NEO_Player* pObserverTargetPlayer = ToNEOPlayer(pLocalNeoPlayer->GetObserverTarget()); + pObserverTargetPlayer && pObserverTargetPlayer->ValidTakeoverTargetFor(pLocalNeoPlayer)) { - if (NEORules()->GetRoundStatus() != PostRound - && GameResources()->IsFakePlayer(pObserverTargetEntity->entindex()) - && pLocalNeoPlayer->InSameTeam(pObserverTargetEntity) && NEORules()->IsTeamplay() - && sv_neo_spec_replace_player_bot_enable.GetBool() - && pLocalNeoPlayer->m_iXP > sv_neo_spec_replace_player_min_exp.GetInt()) + // update hint duration + if (pObserverTargetPlayer != m_hLastSpecTarget.Get()) { - // update hint duration - if (pObserverTargetEntity != m_hLastSpecTarget.Get()) - { - m_flDisplayEndTime = gpGlobals->curtime + cl_neo_spec_takeover_player_hint_time_sec.GetFloat(); - m_hLastSpecTarget = pObserverTargetEntity; - } - + m_flDisplayEndTime = gpGlobals->curtime + cl_neo_spec_takeover_player_hint_time_sec.GetFloat(); + m_hLastSpecTarget = pObserverTargetPlayer; + + // NEO NOTE (Adam) currently this is the only hint we can show when observing. If the hint text ever changes while observer target remains the same, will need to update this more often V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"[%hs] Control Bot", szUppercaseKeyBinding); - showTakeOverHint = true; } + + showTakeOverHint = true; } } if (!showTakeOverHint) { m_flDisplayEndTime = gpGlobals->curtime; + m_hLastSpecTarget = INVALID_EHANDLE; } } } @@ -155,10 +151,6 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"[%hs] pickup %hs", szUppercaseKeyBinding, pNeoWeapon->GetPrintName()); m_flDisplayEndTime = gpGlobals->curtime + 1.f; } - else - // Clear hint - { - } } // Juggernaut hint else if (Q_strcmp(pUseEntity->GetClassname(), "neo_juggernaut") == 0) @@ -177,6 +169,12 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() } } } + // Some other useable entity + else + { + V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"[%hs] use", szUppercaseKeyBinding); + m_flDisplayEndTime = gpGlobals->curtime + 1.f; + } } else { @@ -184,7 +182,7 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() { if (C_NEO_Player* pTargetPlayer = pLocalNeoPlayer->PlayerUseTraceLine(); pTargetPlayer - && pTargetPlayer->IsBot() + && GameResources()->IsFakePlayer(pTargetPlayer->entindex()) && NEORules()->IsTeamplay() && pTargetPlayer->GetTeamNumber() == pLocalNeoPlayer->GetTeamNumber()) { m_flDisplayEndTime = gpGlobals->curtime + 1.f; diff --git a/src/game/server/neo/neo_player.cpp b/src/game/server/neo/neo_player.cpp index 7e807f01e3..17d7901583 100644 --- a/src/game/server/neo/neo_player.cpp +++ b/src/game/server/neo/neo_player.cpp @@ -763,6 +763,16 @@ void CNEO_Player::Spawn(void) m_iBotDetectableBleedingInjuryEvents = 0; } +void CNEO_Player::PlayerRunCommand(CUserCmd* ucmd, IMoveHelper* moveHelper) +{ + if (ucmd->forwardmove || ucmd->sidemove || ucmd->upmove || ucmd->buttons) + { + m_flLastInput = gpGlobals->curtime; + } + + BaseClass::PlayerRunCommand(ucmd, moveHelper); +} + extern ConVar neo_lean_angle; ConVar neo_lean_thirdperson_roll_lerp_scale("neo_lean_thirdperson_roll_lerp_scale", "5", @@ -4045,106 +4055,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_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); - -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 507a41d907..6ad9aa259d 100644 --- a/src/game/server/neo/neo_player.h +++ b/src/game/server/neo/neo_player.h @@ -63,6 +63,7 @@ class CNEO_Player : public CHL2MP_Player virtual void Precache(void) OVERRIDE; virtual void Spawn(void) OVERRIDE; + virtual void PlayerRunCommand(CUserCmd* ucmd, IMoveHelper* moveHelper) override; virtual void PostThink(void) OVERRIDE; virtual void CalculateSpeed(void); virtual void PreThink(void) OVERRIDE; @@ -240,6 +241,8 @@ class CNEO_Player : public CHL2MP_Player void BecomeJuggernaut(); void SpawnJuggernautPostDeath(); + bool ValidTakeoverTargetFor(CNEO_Player* pPlayerTakingOver); + private: bool m_bAllowGibbing; @@ -323,6 +326,7 @@ class CNEO_Player : public CHL2MP_Player void ResetBotCommandState(); void ToggleBotFollowCommander( CNEO_Player *pCommander ); static const Vector VECTOR_INVALID_WAYPOINT; + float m_flLastInput = gpGlobals->curtime; private: bool m_bFirstDeathTick; @@ -348,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 1a5225889e..c9b4e7e484 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_flLastInput > 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 02f0019546..1a43fa36c3 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/neo/neo_player_shared.cpp b/src/game/shared/neo/neo_player_shared.cpp index 2ea7c4b167..6230db84cf 100644 --- a/src/game/shared/neo/neo_player_shared.cpp +++ b/src/game/shared/neo/neo_player_shared.cpp @@ -26,8 +26,8 @@ #include "convar.h" #include "neo_weapon_loadout.h" - #include "weapon_neobasecombatweapon.h" +#include "igameresources.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -52,10 +52,15 @@ ConVar sv_neo_serverside_beacons("sv_neo_serverside_beacons", "1", FCVAR_NOTIFY "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) { @@ -399,3 +404,32 @@ void CNEO_Player::CheckAimButtons() Weapon_SetZoom(false); } } + +bool CNEO_Player::IsAFK() const +{ +#ifdef GAME_DLL + return gpGlobals->curtime - m_flLastInput > 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()); +} \ No newline at end of file diff --git a/src/game/shared/neo/neo_player_shared.h b/src/game/shared/neo/neo_player_shared.h index 9d0009b0a1..b450ccad02 100644 --- a/src/game/shared/neo/neo_player_shared.h +++ b/src/game/shared/neo/neo_player_shared.h @@ -21,7 +21,9 @@ 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 diff --git a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp index d9783b1b2c..891d169887 100644 --- a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp +++ b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp @@ -1375,16 +1375,25 @@ void CNEOBaseCombatWeapon::SetPickupTouch(void) #ifdef GAME_DLL void CNEOBaseCombatWeapon::Use(CBaseEntity* pActivator, CBaseEntity* pCaller, USE_TYPE useType, float value) { - auto* neoPlayer = ToNEOPlayer(pActivator); - - if (neoPlayer && neoPlayer->Weapon_CanSwitchTo(this) && CanBePickedUpByClass(neoPlayer->GetClass())) + bool bPickedUp = false; + if (m_pfnTouch) { - neoPlayer->Weapon_DropSlot(GetSlot()); - neoPlayer->Weapon_Equip(this); + if (CNEO_Player* pNeoPlayer = ToNEOPlayer(pActivator); + pNeoPlayer) + { + pNeoPlayer->Weapon_DropSlot(GetSlot()); + + (this->*m_pfnTouch)(pActivator); + m_OnPlayerUse.FireOutput( pActivator, pCaller ); + bPickedUp = true; - RemoveEffects(EF_BONEMERGE); + RemoveEffects(EF_BONEMERGE); + } } - BaseClass::Use(pActivator, pCaller, useType, value); + if (!bPickedUp) + { + BaseClass::Use(pActivator, pCaller, useType, value); + } } #endif diff --git a/src/public/igameresources.h b/src/public/igameresources.h index 2646aa0df5..ea6ae5ed0b 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; From 25d92be2a47901588967c18bd0668a60ecc99e70 Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Mon, 13 Apr 2026 20:47:31 +0100 Subject: [PATCH 05/19] cannot pickup weapon hint --- .../client/neo/ui/neo_hud_context_hint.cpp | 14 ++++++++++-- src/game/server/neo/neo_player.cpp | 6 ++--- .../weapons/weapon_neobasecombatweapon.cpp | 22 ++++++++++--------- 3 files changed, 27 insertions(+), 15 deletions(-) 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 98d133e8ff..ea4fa8c46f 100644 --- a/src/game/client/neo/ui/neo_hud_context_hint.cpp +++ b/src/game/client/neo/ui/neo_hud_context_hint.cpp @@ -130,7 +130,6 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() } else { - m_flDisplayEndTime = gpGlobals->curtime; if (CBaseEntity *pUseEntity = pLocalNeoPlayer->FindUseEntity(); pUseEntity) { @@ -151,6 +150,12 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() 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"Weapon too heavy"); // NEO TODO (Adam) Replace with a more generic message if we have weapons that can't be picked up for other reasons ("weapon too unwieldly", or simply "cannot pick up") + m_flDisplayEndTime = gpGlobals->curtime + 1.f; + } + // else don't touch m_flDisplayEndTime to keep weapon too heavy message around for a bit } // Juggernaut hint else if (Q_strcmp(pUseEntity->GetClassname(), "neo_juggernaut") == 0) @@ -158,7 +163,6 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() if (CNEO_Juggernaut* pJuggernaut = static_cast(pUseEntity); pJuggernaut) { - m_flDisplayEndTime = gpGlobals->curtime + 1.f; if (pJuggernaut->m_bLocked) { V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"Juggernaut is locked"); @@ -167,6 +171,11 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() { V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"Hold [%hs] take the Juggernaut", szUppercaseKeyBinding); } + m_flDisplayEndTime = gpGlobals->curtime + 1.f; + } + else + { + m_flDisplayEndTime = gpGlobals->curtime; } } // Some other useable entity @@ -178,6 +187,7 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() } else { + m_flDisplayEndTime = gpGlobals->curtime; // Bot command hint { if (C_NEO_Player* pTargetPlayer = pLocalNeoPlayer->PlayerUseTraceLine(); diff --git a/src/game/server/neo/neo_player.cpp b/src/game/server/neo/neo_player.cpp index 17d7901583..276f6b527a 100644 --- a/src/game/server/neo/neo_player.cpp +++ b/src/game/server/neo/neo_player.cpp @@ -2722,15 +2722,15 @@ 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; } diff --git a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp index 891d169887..13829c183f 100644 --- a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp +++ b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp @@ -1375,25 +1375,27 @@ void CNEOBaseCombatWeapon::SetPickupTouch(void) #ifdef GAME_DLL void CNEOBaseCombatWeapon::Use(CBaseEntity* pActivator, CBaseEntity* pCaller, USE_TYPE useType, float value) { - bool bPickedUp = false; + m_OnPlayerUse.FireOutput( pActivator, pCaller ); + if (m_pfnTouch) { if (CNEO_Player* pNeoPlayer = ToNEOPlayer(pActivator); - pNeoPlayer) + pNeoPlayer && CanBePickedUpByClass(pNeoPlayer->GetClass())) { - pNeoPlayer->Weapon_DropSlot(GetSlot()); + CBaseCombatWeapon* pActiveWeapon = pNeoPlayer->GetActiveWeapon(); + const int activeSlot = pActiveWeapon ? pActiveWeapon->GetSlot() : -1; + pNeoPlayer->Weapon_DropSlot(GetSlot()); // NEO NOTE (Adam) no guarantee we will actually pick up the weapon. CanBePickedUpByClass should catch most problems + // Also we shouldn't do this for throwables since you can have multiple in the same slot, but throwables can't be dropped so not a problem right now (this->*m_pfnTouch)(pActivator); - m_OnPlayerUse.FireOutput( pActivator, pCaller ); - bPickedUp = true; - RemoveEffects(EF_BONEMERGE); + if (GetOwner() == pNeoPlayer && activeSlot == GetSlot()) + { + pNeoPlayer->Weapon_Switch(this); + } } } - if (!bPickedUp) - { - 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 From 69570ebdaa05f06cdcda2f4b03a5f5168f8bf4fa Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Tue, 14 Apr 2026 14:38:18 +0100 Subject: [PATCH 06/19] settings page --- src/game/client/c_baseentity.h | 4 ++ src/game/client/glow_outline_effect.cpp | 13 ++++ src/game/client/glow_outline_effect.h | 20 ++++++ .../client/neo/ui/neo_hud_context_hint.cpp | 68 +++++++++++++------ src/game/client/neo/ui/neo_root_settings.cpp | 16 ++++- src/game/client/neo/ui/neo_root_settings.h | 6 ++ src/game/server/baseentity.h | 4 ++ src/game/server/neo/neo_player.cpp | 11 ++- src/game/shared/baseplayer_shared.cpp | 42 ++++++++++++ src/game/shared/neo/neo_juggernaut.h | 4 ++ .../neo/weapons/weapon_neobasecombatweapon.h | 9 ++- 11 files changed, 172 insertions(+), 25 deletions(-) diff --git a/src/game/client/c_baseentity.h b/src/game/client/c_baseentity.h index 2200f6db1a..0d60cf6f02 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/glow_outline_effect.cpp b/src/game/client/glow_outline_effect.cpp index 5b68d827bb..2f5af417d7 100644 --- a/src/game/client/glow_outline_effect.cpp +++ b/src/game/client/glow_outline_effect.cpp @@ -201,6 +201,9 @@ void CGlowObjectManager::ApplyEntityGlowEffects( const CViewSetup *pSetup, int n int iNumGlowObjects = 0; +#ifdef NEO + const int useElementIndex = m_GlowObjectDefinitions.AddToTail(useItemGlow); +#endif // NEO for ( int i = 0; i < m_GlowObjectDefinitions.Count(); ++ i ) { if ( m_GlowObjectDefinitions[i].IsUnused() || !m_GlowObjectDefinitions[i].ShouldDraw( nSplitScreenSlot ) ) @@ -316,7 +319,14 @@ 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 + { + m_GlowObjectDefinitions.Remove(useElementIndex); + return; + } +#else return; +#endif // NEO //============================================= // Render the glow colors to _rt_FullFrameFB @@ -325,6 +335,9 @@ void CGlowObjectManager::ApplyEntityGlowEffects( const CViewSetup *pSetup, int n PIXEvent pixEvent( pRenderContext, "RenderGlowModels" ); RenderGlowModels( pSetup, nSplitScreenSlot, pRenderContext ); } +#ifdef NEO + 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 625fef8667..e1d6a3e574 100644 --- a/src/game/client/glow_outline_effect.h +++ b/src/game/client/glow_outline_effect.h @@ -121,6 +121,22 @@ class CGlowObjectManager Assert( !m_GlowObjectDefinitions[nGlowObjectHandle].IsUnused() ); m_GlowObjectDefinitions[nGlowObjectHandle].m_bUseTexturedHighlight = useTexturedHighlight; } + + void SetUseItemGlowObject( 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 ) + { + useItemGlow.m_hEntity = pEntity; + useItemGlow.m_vGlowColor = vGlowColor; + useItemGlow.m_flGlowAlpha = flGlowAlpha; + useItemGlow.m_bRenderWhenOccluded = bRenderWhenOccluded; + useItemGlow.m_bRenderWhenUnoccluded = bRenderWhenUnoccluded; + useItemGlow.m_nSplitScreenSlot = nSplitScreenSlot; + useItemGlow.m_nNextFreeSlot = GlowObjectDefinition_t::ENTRY_IN_USE; + } + + void ClearUseItemGlowObject() + { + useItemGlow.m_hEntity = INVALID_EHANDLE; + } #endif // NEO private: @@ -162,6 +178,10 @@ class CGlowObjectManager CUtlVector< GlowObjectDefinition_t > m_GlowObjectDefinitions; int m_nFirstFreeSlot; + +#ifdef NEO + GlowObjectDefinition_t useItemGlow; +#endif // NEO }; extern CGlowObjectManager g_GlowObjectManager; 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 ea4fa8c46f..95d036f5fe 100644 --- a/src/game/client/neo/ui/neo_hud_context_hint.cpp +++ b/src/game/client/neo/ui/neo_hud_context_hint.cpp @@ -11,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" @@ -68,7 +70,10 @@ void CNEOHud_ContextHint::ApplySchemeSettings(vgui::IScheme* pScheme) bool CNEOHud_ContextHint::ShouldDraw() { if (!cl_neo_hud_context_hint_enabled.GetBool()) + { + g_GlowObjectManager.ClearUseItemGlowObject(); return false; + } return true; } @@ -76,8 +81,13 @@ bool CNEOHud_ContextHint::ShouldDraw() 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.ClearUseItemGlowObject(); + C_NEO_Player* pLocalNeoPlayer = C_NEO_Player::GetLocalNEOPlayer(); if (!pLocalNeoPlayer) return; @@ -98,6 +108,9 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() if (pLocalNeoPlayer->IsObserver()) { + if (!cl_neo_hud_context_hint_show_player_takeover_hint.GetBool()) + return; + // Takeover hint { bool showTakeOverHint = false; @@ -130,9 +143,15 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() } else { + constexpr float ITEM_DISCOVERY_SMOKE_THRESHOLD = 0.8f; if (CBaseEntity *pUseEntity = pLocalNeoPlayer->FindUseEntity(); - pUseEntity) + pUseEntity && (g_SmokeFogOverlayThermalOverride || g_SmokeFogOverlayAlpha < ITEM_DISCOVERY_SMOKE_THRESHOLD)) { + if (!cl_neo_hud_context_hint_show_object_interact_hint.GetBool()) + return; + + g_GlowObjectManager.SetUseItemGlowObject(pUseEntity, Vector( 1.0f, 1.0f, 1.0f ), g_SmokeFogOverlayThermalOverride ? 1.0f : Max( 0.0f, 1.0f - g_SmokeFogOverlayAlpha), false, true); + // Weapon pickup hint if (pUseEntity->IsBaseCombatWeapon()) { @@ -152,42 +171,53 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() } else if (pLocalNeoPlayer->m_nButtons & IN_USE) { - V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"Weapon too heavy"); // NEO TODO (Adam) Replace with a more generic message if we have weapons that can't be picked up for other reasons ("weapon too unwieldly", or simply "cannot pick up") + V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"Cannot pickup weapon"); m_flDisplayEndTime = gpGlobals->curtime + 1.f; + // g_GlowObjectManager.ClearUseItemGlowObject(); // NEO TODO (Adam) Clear highlight? or show which weapon cannot be picked up? Change highlight colour to red? + } + else + { + g_GlowObjectManager.ClearUseItemGlowObject(); } - // else don't touch m_flDisplayEndTime to keep weapon too heavy message around for a bit } - // Juggernaut hint - else if (Q_strcmp(pUseEntity->GetClassname(), "neo_juggernaut") == 0) + else { - if (CNEO_Juggernaut* pJuggernaut = static_cast(pUseEntity); - pJuggernaut) + // Juggernaut hint + if (Q_strcmp(pUseEntity->GetClassname(), "class C_NEO_Juggernaut") == 0) { - if (pJuggernaut->m_bLocked) + if (CNEO_Juggernaut* pJuggernaut = static_cast(pUseEntity); + pJuggernaut) { - V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"Juggernaut is locked"); + if (pJuggernaut->m_bLocked) + { + V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"Juggernaut is locked"); + } + else // NEO TODO (Adam) network and check m_hHoldingPlayer, time left until juggernaut taken? + { + V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"Hold [%hs] take the Juggernaut", szUppercaseKeyBinding); + } + m_flDisplayEndTime = gpGlobals->curtime + 1.f; } - else // NEO TODO (Adam) network and check m_hHoldingPlayer, time left until juggernaut taken? + else { - V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"Hold [%hs] take the Juggernaut", szUppercaseKeyBinding); + m_flDisplayEndTime = gpGlobals->curtime; } - m_flDisplayEndTime = gpGlobals->curtime + 1.f; } + // Some other useable entity else { - m_flDisplayEndTime = gpGlobals->curtime; + V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"[%hs] use", szUppercaseKeyBinding); + m_flDisplayEndTime = gpGlobals->curtime + 1.f; } } - // Some other useable entity - else - { - V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"[%hs] use", szUppercaseKeyBinding); - m_flDisplayEndTime = gpGlobals->curtime + 1.f; - } } else { 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(); diff --git a/src/game/client/neo/ui/neo_root_settings.cpp b/src/game/client/neo/ui/neo_root_settings.cpp index ff384a6788..bd7cb4515c 100644 --- a/src/game/client/neo/ui/neo_root_settings.cpp +++ b/src/game/client/neo/ui/neo_root_settings.cpp @@ -685,6 +685,9 @@ 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(); bool bImported = ImportMarker(&pHUD->options[NEOIFFMARKER_OPTION_SQUAD], cvr->cl_neo_squad_marker.GetString()); if (!bImported) @@ -945,6 +948,9 @@ 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); char szSequence[NEO_IFFMARKER_SEQMAX]; ExportMarker(&pHUD->options[NEOIFFMARKER_OPTION_SQUAD], szSequence); @@ -1642,13 +1648,21 @@ 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); + } + #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 577772a910..74913b78e8 100644 --- a/src/game/client/neo/ui/neo_root_settings.h +++ b/src/game/client/neo/ui/neo_root_settings.h @@ -207,6 +207,9 @@ struct NeoSettings bool bEnableRangeFinder; int iExtendedKillfeed; bool bShowHudContextHints; + bool bShowHudContextHintPlayerTakeover; + bool bShowHudContextHintObjectInteract; + bool bShowHudContextHintBotInteract; int iKdinfoToggletype; // IFF Markers @@ -272,6 +275,9 @@ 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_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 4a112c1e56..1fc85890b4 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/neo/neo_player.cpp b/src/game/server/neo/neo_player.cpp index 276f6b527a..08aac2e629 100644 --- a/src/game/server/neo/neo_player.cpp +++ b/src/game/server/neo/neo_player.cpp @@ -3484,8 +3484,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 diff --git a/src/game/shared/baseplayer_shared.cpp b/src/game/shared/baseplayer_shared.cpp index 23e4d887bb..e9fa7b1c53 100644 --- a/src/game/shared/baseplayer_shared.cpp +++ b/src/game/shared/baseplayer_shared.cpp @@ -1158,7 +1158,11 @@ CBaseEntity *CBasePlayer::FindUseEntity() // UNDONE: Might be faster to just fold this range into the sphere query CBaseEntity *pObject = NULL; +#ifdef NEO + float nearestDist = -1; +#else float nearestDist = FLT_MAX; +#endif // NEO // try the hit entity if there is one, or the ground entity if there isn't. CBaseEntity *pNearest = NULL; @@ -1221,7 +1225,11 @@ CBaseEntity *CBasePlayer::FindUseEntity() pNearest = pObject; // if this is directly under the cursor just return it now +#ifdef NEO + if ( i == 0 && pObject && !(pObject->ObjectCaps() & FCAP_USE_IN_RADIUS) && !pObject->IsBaseCombatWeapon() ) // NEO NOTE (Adam) weapon AABBs are usually far removed from where they actually are visually, just use distance to worldspace center for them +#else if ( i == 0 ) +#endif // NEO return pObject; } } @@ -1237,9 +1245,13 @@ CBaseEntity *CBasePlayer::FindUseEntity() if ( pNearest ) { // estimate nearest object by distance from the view vector +#ifdef NEO + nearestDist = DotProduct((pNearest->CollisionProp()->WorldSpaceCenter() - searchCenter).Normalized(), forward); +#else Vector point; pNearest->CollisionProp()->CalcNearestPoint( searchCenter, &point ); nearestDist = CalcDistanceToLine( point, searchCenter, forward ); +#endif // NEO if ( sv_debug_player_use.GetBool() ) { Msg("Trace found %s, dist %.2f\n", pNearest->GetClassname(), nearestDist ); @@ -1258,22 +1270,42 @@ CBaseEntity *CBasePlayer::FindUseEntity() Vector point; pObject->CollisionProp()->CalcNearestPoint( searchCenter, &point ); +#ifdef NEO + float dot = -1; + dot = DotProduct((pObject->CollisionProp()->WorldSpaceCenter() - searchCenter).Normalized(), forward); + + if (sv_debug_player_use.GetBool()) + { + DebugDrawLine(pObject->CollisionProp()->WorldSpaceCenter() - Vector(4, 0, 0), pObject->CollisionProp()->WorldSpaceCenter() + Vector(4, 0, 0), 16, 255, 0, false, 0.2); + DebugDrawLine(pObject->CollisionProp()->WorldSpaceCenter() - Vector(0, 4, 0), pObject->CollisionProp()->WorldSpaceCenter() + Vector(0, 4, 0), 16, 255, 0, false, 0.2); + DebugDrawLine(pObject->CollisionProp()->WorldSpaceCenter() - Vector(0, 0, 4), pObject->CollisionProp()->WorldSpaceCenter() + Vector(0, 0, 4), 16, 255, 0, false, 0.2); + } +#else Vector dir = point - searchCenter; VectorNormalize(dir); float dot = DotProduct( dir, forward ); +#endif // NEO // Need to be looking at the object more or less if ( dot < 0.8 ) continue; +#ifdef NEO + float dist = dot; +#else float dist = CalcDistanceToLine( point, searchCenter, forward ); +#endif // NEO if ( sv_debug_player_use.GetBool() ) { Msg("Radius found %s, dist %.2f\n", pObject->GetClassname(), dist ); } +#ifdef NEO + if ( dist > nearestDist ) +#else if ( dist < nearestDist ) +#endif // NEO { // Since this has purely been a radius search to this point, we now // make sure the object isn't behind glass or a grate. @@ -1285,6 +1317,16 @@ CBaseEntity *CBasePlayer::FindUseEntity() pNearest = pObject; nearestDist = dist; } +#if defined NEO + // NEO NOTE (Adam) usable entities can obstruct other usable entities. As mentioned above, weapon AABBs can be far from where they are visually and annoyingly block pickups anyway. + // Could add each weapon found to a list and redo the trace until we find a non-weapon blocking object or the object we want to pick up + else if (sv_debug_player_use.GetBool()) + { + DebugDrawLine(pObject->CollisionProp()->WorldSpaceCenter() - Vector(3, 3, 0), pObject->CollisionProp()->WorldSpaceCenter() + Vector(3, 3, 0), 255, 16, 0, false, 0.2); + DebugDrawLine(pObject->CollisionProp()->WorldSpaceCenter() - Vector(0, 3, 3), pObject->CollisionProp()->WorldSpaceCenter() + Vector(0, 3, 3), 255, 16, 0, false, 0.2); + DebugDrawLine(pObject->CollisionProp()->WorldSpaceCenter() - Vector(3, 0, 3), pObject->CollisionProp()->WorldSpaceCenter() + Vector(3, 0, 3), 255, 16, 0, false, 0.2); + } +#endif // NEO && DEBUG } } diff --git a/src/game/shared/neo/neo_juggernaut.h b/src/game/shared/neo/neo_juggernaut.h index c1740eba7d..5f297db887 100644 --- a/src/game/shared/neo/neo_juggernaut.h +++ b/src/game/shared/neo/neo_juggernaut.h @@ -40,6 +40,10 @@ class CNEO_Juggernaut : public CBaseAnimating bool m_bPostDeath = false; #endif +#ifdef CLIENT_DLL + virtual int ObjectCaps() override { return BaseClass::ObjectCaps() | FCAP_IMPULSE_USE; } +#endif // CLIENT_DLL + virtual unsigned int PhysicsSolidMaskForEntity() const final override { return MASK_PLAYERSOLID; } virtual void UpdateOnRemove() override; diff --git a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h index f47850e854..0ab7040f9d 100644 --- a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h +++ b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h @@ -140,16 +140,19 @@ 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) + && !HasSpawnFlags(SF_WEAPON_NO_PLAYER_PICKUP) // NEO TODO (Adam) workout how to check this client side #endif // GAME_DLL ) - { - caps |= FCAP_IMPULSE_USE; + { // NEO NOTE (Adam) debris can be usable too, and debris such as ragdolls and gibs prevents us from picking up weapons with use via direct traceline, allow usage in radius too. + caps |= FCAP_IMPULSE_USE|FCAP_USE_IN_RADIUS; } return caps; From c343d97901bf5969a080fed8385aff33b4a1afd2 Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Tue, 14 Apr 2026 21:39:41 +0100 Subject: [PATCH 07/19] use hint for buttons --- src/game/client/CMakeLists.txt | 2 ++ src/game/client/c_buttons.cpp | 15 +++++++++++++++ src/game/client/c_buttons.h | 14 ++++++++++++++ src/game/server/buttons.cpp | 5 +++++ src/game/server/buttons.h | 5 +++++ src/game/shared/neo/neo_juggernaut.h | 6 +----- 6 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 src/game/client/c_buttons.cpp create mode 100644 src/game/client/c_buttons.h diff --git a/src/game/client/CMakeLists.txt b/src/game/client/CMakeLists.txt index 19c2458a4c..85f50ab7f6 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_buttons.cpp b/src/game/client/c_buttons.cpp new file mode 100644 index 0000000000..e16fe01efb --- /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 0000000000..1429579e57 --- /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/server/buttons.cpp b/src/game/server/buttons.cpp index e044dd84ee..b26532fd47 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 c78a8652db..6ba4a96c18 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/shared/neo/neo_juggernaut.h b/src/game/shared/neo/neo_juggernaut.h index 5f297db887..469455af3e 100644 --- a/src/game/shared/neo/neo_juggernaut.h +++ b/src/game/shared/neo/neo_juggernaut.h @@ -30,7 +30,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,10 +38,7 @@ class CNEO_Juggernaut : public CBaseAnimating bool m_bPostDeath = false; #endif - -#ifdef CLIENT_DLL - virtual int ObjectCaps() override { return BaseClass::ObjectCaps() | FCAP_IMPULSE_USE; } -#endif // CLIENT_DLL + virtual int ObjectCaps(void) { return BaseClass::ObjectCaps() | FCAP_ONOFF_USE; } virtual unsigned int PhysicsSolidMaskForEntity() const final override { return MASK_PLAYERSOLID; } virtual void UpdateOnRemove() override; From d5d719c10e3d1134f4d17b6650799fe3d90a2d99 Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Wed, 15 Apr 2026 11:51:22 +0100 Subject: [PATCH 08/19] bot highlighting if selectable, hiding juggernaut use prompt is juggernaut is being used --- src/game/client/glow_outline_effect.cpp | 37 ++++++++++++++-- src/game/client/glow_outline_effect.h | 9 +++- src/game/client/neo/c_neo_player.cpp | 9 +--- .../client/neo/ui/neo_hud_context_hint.cpp | 23 +++++++--- src/game/client/neo/ui/neo_root_settings.cpp | 3 ++ src/game/client/neo/ui/neo_root_settings.h | 2 + src/game/shared/neo/neo_juggernaut.cpp | 43 +++++++++++-------- src/game/shared/neo/neo_juggernaut.h | 5 ++- 8 files changed, 92 insertions(+), 39 deletions(-) diff --git a/src/game/client/glow_outline_effect.cpp b/src/game/client/glow_outline_effect.cpp index 2f5af417d7..140f900994 100644 --- a/src/game/client/glow_outline_effect.cpp +++ b/src/game/client/glow_outline_effect.cpp @@ -35,6 +35,12 @@ 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.ClearUseItemGlowObject(); + 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 +87,7 @@ void CGlowObjectManager::RenderGlowEffects( const CViewSetup *pSetup, int nSplit { if ( g_pMaterialSystemHardwareConfig->SupportsPixelShaders_2_0() ) { - if ( glow_outline_effect_enable.GetBool() ) + if ( glow_outline_effect_enable.GetBool() || cl_neo_hud_context_hint_highlight_object.GetBool() ) { CMatRenderContextPtr pRenderContext( materials ); @@ -156,6 +162,9 @@ void CGlowObjectManager::RenderGlowModels( const CViewSetup *pSetup, int nSplitS continue; #ifdef NEO + if ( i != m_GlowObjectDefinitions.Count() - 1 && useItemGlow.m_hEntity == m_GlowObjectDefinitions[i].m_hEntity && useItemGlow.m_hEntity.IsValid() && cl_neo_hud_context_hint_highlight_object.GetBool()) + continue; + // DrawModel can call ForcedMaterialOverride also g_pStudioRender->ForcedMaterialOverride(pMatGlowColor); #endif @@ -202,13 +211,22 @@ void CGlowObjectManager::ApplyEntityGlowEffects( const CViewSetup *pSetup, int n int iNumGlowObjects = 0; #ifdef NEO - const int useElementIndex = m_GlowObjectDefinitions.AddToTail(useItemGlow); + int useElementIndex = -1; + if (useItemGlow.m_hEntity.IsValid() && cl_neo_hud_context_hint_highlight_object.GetBool()) + { + useElementIndex = m_GlowObjectDefinitions.AddToTail(useItemGlow); + } #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 (useElementIndex != -1 && i != useElementIndex && useItemGlow.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 ) @@ -283,6 +301,11 @@ void CGlowObjectManager::ApplyEntityGlowEffects( const CViewSetup *pSetup, int n { if ( m_GlowObjectDefinitions[i].IsUnused() || !m_GlowObjectDefinitions[i].ShouldDraw( nSplitScreenSlot ) ) continue; + +#ifdef NEO + if (useElementIndex != -1 && i != useElementIndex && useItemGlow.m_hEntity == m_GlowObjectDefinitions[i].m_hEntity) + continue; +#endif // NEO if ( m_GlowObjectDefinitions[i].m_bRenderWhenOccluded && !m_GlowObjectDefinitions[i].m_bRenderWhenUnoccluded ) { @@ -321,7 +344,10 @@ void CGlowObjectManager::ApplyEntityGlowEffects( const CViewSetup *pSetup, int n if ( iNumGlowObjects <= 0 ) #ifdef NEO { - m_GlowObjectDefinitions.Remove(useElementIndex); + if (useElementIndex != -1) + { + m_GlowObjectDefinitions.Remove(useElementIndex); + } return; } #else @@ -336,7 +362,10 @@ void CGlowObjectManager::ApplyEntityGlowEffects( const CViewSetup *pSetup, int n RenderGlowModels( pSetup, nSplitScreenSlot, pRenderContext ); } #ifdef NEO - m_GlowObjectDefinitions.Remove(useElementIndex); + if (useElementIndex != -1) + { + m_GlowObjectDefinitions.Remove(useElementIndex); + } #endif // NEO // Get viewport diff --git a/src/game/client/glow_outline_effect.h b/src/game/client/glow_outline_effect.h index e1d6a3e574..60519d61ca 100644 --- a/src/game/client/glow_outline_effect.h +++ b/src/game/client/glow_outline_effect.h @@ -22,6 +22,10 @@ class CMatRenderContextPtr; static const int GLOW_FOR_ALL_SPLIT_SCREEN_SLOTS = -1; +#ifdef NEO +extern ConVar cl_neo_hud_context_hint_highlight_object; +#endif // NEO + class CGlowObjectManager { public: @@ -123,7 +127,10 @@ class CGlowObjectManager } void SetUseItemGlowObject( 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 (!cl_neo_hud_context_hint_highlight_object.GetBool()) + return; + useItemGlow.m_hEntity = pEntity; useItemGlow.m_vGlowColor = vGlowColor; useItemGlow.m_flGlowAlpha = flGlowAlpha; diff --git a/src/game/client/neo/c_neo_player.cpp b/src/game/client/neo/c_neo_player.cpp index 85dc94a5f0..ead4529b1e 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; 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 95d036f5fe..b5343ee4ae 100644 --- a/src/game/client/neo/ui/neo_hud_context_hint.cpp +++ b/src/game/client/neo/ui/neo_hud_context_hint.cpp @@ -147,10 +147,10 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() if (CBaseEntity *pUseEntity = pLocalNeoPlayer->FindUseEntity(); pUseEntity && (g_SmokeFogOverlayThermalOverride || g_SmokeFogOverlayAlpha < ITEM_DISCOVERY_SMOKE_THRESHOLD)) { + g_GlowObjectManager.SetUseItemGlowObject(pUseEntity, Vector( 1.0f, 1.0f, 1.0f ), g_SmokeFogOverlayThermalOverride ? 1.0f : Max( 0.0f, 1.0f - g_SmokeFogOverlayAlpha), true, false); + if (!cl_neo_hud_context_hint_show_object_interact_hint.GetBool()) return; - - g_GlowObjectManager.SetUseItemGlowObject(pUseEntity, Vector( 1.0f, 1.0f, 1.0f ), g_SmokeFogOverlayThermalOverride ? 1.0f : Max( 0.0f, 1.0f - g_SmokeFogOverlayAlpha), false, true); // Weapon pickup hint if (pUseEntity->IsBaseCombatWeapon()) @@ -188,15 +188,21 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() if (CNEO_Juggernaut* pJuggernaut = static_cast(pUseEntity); pJuggernaut) { + m_flDisplayEndTime = gpGlobals->curtime + 1.f; if (pJuggernaut->m_bLocked) { V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"Juggernaut is locked"); } - else // NEO TODO (Adam) network and check m_hHoldingPlayer, time left until juggernaut taken? + else if (C_NEO_Player* pNeoJuggernautPlayer = pJuggernaut->m_hHoldingPlayer.Get(); + pNeoJuggernautPlayer && pJuggernaut->m_bIsHolding) + { + g_GlowObjectManager.ClearUseItemGlowObject(); + m_flDisplayEndTime = gpGlobals->curtime; + } + else { V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"Hold [%hs] take the Juggernaut", szUppercaseKeyBinding); } - m_flDisplayEndTime = gpGlobals->curtime + 1.f; } else { @@ -214,7 +220,6 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() else { m_flDisplayEndTime = gpGlobals->curtime; - if (!cl_neo_hud_context_hint_show_bot_interact_hint.GetBool()) return; @@ -225,10 +230,15 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() && GameResources()->IsFakePlayer(pTargetPlayer->entindex()) && NEORules()->IsTeamplay() && pTargetPlayer->GetTeamNumber() == pLocalNeoPlayer->GetTeamNumber()) { + // NEO TODO (Adam) if xray is enabled, we're drawing this player's glow effect twice unnecessarily, fix + Vector teamGlowColor { 1.f, 1.f, 1.f }; + NEORules()->GetTeamGlowColor(pTargetPlayer->GetTeamNumber(), teamGlowColor[0], teamGlowColor[1], teamGlowColor[2]); + g_GlowObjectManager.SetUseItemGlowObject(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] command %hs", szUppercaseKeyBinding, pTargetPlayer->GetNeoPlayerName()); + V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"[%hs] %hs %hs", szUppercaseKeyBinding, pTargetPlayer->m_hCommandingPlayer.Get() == pLocalNeoPlayer ? "release" : "command", pTargetPlayer->GetNeoPlayerName()); } else { @@ -238,7 +248,6 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() } } } - } } diff --git a/src/game/client/neo/ui/neo_root_settings.cpp b/src/game/client/neo/ui/neo_root_settings.cpp index bd7cb4515c..131163bce7 100644 --- a/src/game/client/neo/ui/neo_root_settings.cpp +++ b/src/game/client/neo/ui/neo_root_settings.cpp @@ -687,6 +687,7 @@ void NeoSettingsRestore(NeoSettings *ns, const NeoSettings::Keys::Flags flagsKey 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->bShowHudContextHighlightObject = cvr->cl_neo_hud_context_hint_highlight_object.GetBool(); pHUD->bShowHudContextHintBotInteract = cvr->cl_neo_hud_context_hint_show_bot_interact_hint.GetBool(); bool bImported = ImportMarker(&pHUD->options[NEOIFFMARKER_OPTION_SQUAD], cvr->cl_neo_squad_marker.GetString()); @@ -950,6 +951,7 @@ void NeoSettingsSave(const NeoSettings *ns) 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_highlight_object.SetValue(pHUD->bShowHudContextHighlightObject); cvr->cl_neo_hud_context_hint_show_bot_interact_hint.SetValue(pHUD->bShowHudContextHintBotInteract); char szSequence[NEO_IFFMARKER_SEQMAX]; @@ -1660,6 +1662,7 @@ void NeoSettings_HUD(NeoSettings *ns) { NeoUI::RingBoxBool(L"Show player takeover contextual hint", &pHud->bShowHudContextHintPlayerTakeover); NeoUI::RingBoxBool(L"Show object interact contextual hint", &pHud->bShowHudContextHintObjectInteract); + NeoUI::RingBoxBool(L"Highlight interactable object (requires xray enabled)", &pHud->bShowHudContextHighlightObject); NeoUI::RingBoxBool(L"Show bot interact contextual hint", &pHud->bShowHudContextHintBotInteract); } diff --git a/src/game/client/neo/ui/neo_root_settings.h b/src/game/client/neo/ui/neo_root_settings.h index 74913b78e8..92fdcd23a2 100644 --- a/src/game/client/neo/ui/neo_root_settings.h +++ b/src/game/client/neo/ui/neo_root_settings.h @@ -209,6 +209,7 @@ struct NeoSettings bool bShowHudContextHints; bool bShowHudContextHintPlayerTakeover; bool bShowHudContextHintObjectInteract; + bool bShowHudContextHighlightObject; bool bShowHudContextHintBotInteract; int iKdinfoToggletype; @@ -277,6 +278,7 @@ struct NeoSettings 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_highlight_object); CONVARREF_DEF(cl_neo_hud_context_hint_show_bot_interact_hint); CONVARREF_DEF(cl_neo_equip_utility_priority); CONVARREF_DEF(cl_neo_taking_damage_sounds); diff --git a/src/game/shared/neo/neo_juggernaut.cpp b/src/game/shared/neo/neo_juggernaut.cpp index 3e74986639..afc0879c41 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 469455af3e..86a9b41550 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(); @@ -44,6 +45,8 @@ class CNEO_Juggernaut : public CBaseAnimating 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; From dc30686478b7393a662691d16c456fe7e10b5ce5 Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Wed, 15 Apr 2026 11:51:58 +0100 Subject: [PATCH 09/19] no longer the case --- src/game/client/neo/ui/neo_hud_context_hint.cpp | 1 - 1 file changed, 1 deletion(-) 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 b5343ee4ae..a9494ad032 100644 --- a/src/game/client/neo/ui/neo_hud_context_hint.cpp +++ b/src/game/client/neo/ui/neo_hud_context_hint.cpp @@ -230,7 +230,6 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() && GameResources()->IsFakePlayer(pTargetPlayer->entindex()) && NEORules()->IsTeamplay() && pTargetPlayer->GetTeamNumber() == pLocalNeoPlayer->GetTeamNumber()) { - // NEO TODO (Adam) if xray is enabled, we're drawing this player's glow effect twice unnecessarily, fix Vector teamGlowColor { 1.f, 1.f, 1.f }; NEORules()->GetTeamGlowColor(pTargetPlayer->GetTeamNumber(), teamGlowColor[0], teamGlowColor[1], teamGlowColor[2]); g_GlowObjectManager.SetUseItemGlowObject(pTargetPlayer, teamGlowColor, g_SmokeFogOverlayThermalOverride ? 1.0f : Max(0.0f, 1.0f - g_SmokeFogOverlayAlpha), false, true); From 315a493144afd8a2753b990d2597a7601e33486f Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Wed, 15 Apr 2026 13:16:57 +0100 Subject: [PATCH 10/19] separate setting for using players (bots) --- src/game/client/glow_outline_effect.cpp | 14 ++++++++++---- src/game/client/glow_outline_effect.h | 19 +++++++++++++++++-- .../client/neo/ui/neo_hud_context_hint.cpp | 8 ++++---- src/game/client/neo/ui/neo_root_settings.cpp | 9 ++++++--- src/game/client/neo/ui/neo_root_settings.h | 6 ++++-- src/game/shared/baseplayer_shared.cpp | 15 +++++++++++++-- 6 files changed, 54 insertions(+), 17 deletions(-) diff --git a/src/game/client/glow_outline_effect.cpp b/src/game/client/glow_outline_effect.cpp index 140f900994..a40bb833c7 100644 --- a/src/game/client/glow_outline_effect.cpp +++ b/src/game/client/glow_outline_effect.cpp @@ -38,7 +38,13 @@ ConVar glow_outline_effect_textured_center_alpha("glow_outline_effect_textured_c 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.ClearUseItemGlowObject(); + 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 @@ -87,7 +93,7 @@ void CGlowObjectManager::RenderGlowEffects( const CViewSetup *pSetup, int nSplit { if ( g_pMaterialSystemHardwareConfig->SupportsPixelShaders_2_0() ) { - if ( glow_outline_effect_enable.GetBool() || cl_neo_hud_context_hint_highlight_object.GetBool() ) + if ( glow_outline_effect_enable.GetBool() || cl_neo_hud_context_hint_highlight_object.GetBool() || cl_neo_hud_context_hint_highlight_player.GetBool() ) { CMatRenderContextPtr pRenderContext( materials ); @@ -162,7 +168,7 @@ void CGlowObjectManager::RenderGlowModels( const CViewSetup *pSetup, int nSplitS continue; #ifdef NEO - if ( i != m_GlowObjectDefinitions.Count() - 1 && useItemGlow.m_hEntity == m_GlowObjectDefinitions[i].m_hEntity && useItemGlow.m_hEntity.IsValid() && cl_neo_hud_context_hint_highlight_object.GetBool()) + if ( i != m_GlowObjectDefinitions.Count() - 1 && useItemGlow.m_hEntity == m_GlowObjectDefinitions[i].m_hEntity && useItemGlow.m_hEntity.IsValid()) continue; // DrawModel can call ForcedMaterialOverride also @@ -212,7 +218,7 @@ void CGlowObjectManager::ApplyEntityGlowEffects( const CViewSetup *pSetup, int n #ifdef NEO int useElementIndex = -1; - if (useItemGlow.m_hEntity.IsValid() && cl_neo_hud_context_hint_highlight_object.GetBool()) + if (useItemGlow.m_hEntity.IsValid()) { useElementIndex = m_GlowObjectDefinitions.AddToTail(useItemGlow); } diff --git a/src/game/client/glow_outline_effect.h b/src/game/client/glow_outline_effect.h index 60519d61ca..ed3170a30f 100644 --- a/src/game/client/glow_outline_effect.h +++ b/src/game/client/glow_outline_effect.h @@ -24,6 +24,7 @@ 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 @@ -128,7 +129,9 @@ class CGlowObjectManager void SetUseItemGlowObject( 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 (!cl_neo_hud_context_hint_highlight_object.GetBool()) + 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; useItemGlow.m_hEntity = pEntity; @@ -140,10 +143,22 @@ class CGlowObjectManager useItemGlow.m_nNextFreeSlot = GlowObjectDefinition_t::ENTRY_IN_USE; } - void ClearUseItemGlowObject() + void ClearUseItem() { useItemGlow.m_hEntity = INVALID_EHANDLE; } + void ClearUseItemObject() + { + C_BaseEntity* pEntity = useItemGlow.m_hEntity.Get(); + if (pEntity && !pEntity->IsPlayer()) + useItemGlow.m_hEntity = INVALID_EHANDLE; + } + void ClearUseItemPlayer() + { + C_BaseEntity* pEntity = useItemGlow.m_hEntity.Get(); + if (pEntity && pEntity->IsPlayer()) + useItemGlow.m_hEntity = INVALID_EHANDLE; + } #endif // NEO private: 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 a9494ad032..5a582b9c92 100644 --- a/src/game/client/neo/ui/neo_hud_context_hint.cpp +++ b/src/game/client/neo/ui/neo_hud_context_hint.cpp @@ -71,7 +71,7 @@ bool CNEOHud_ContextHint::ShouldDraw() { if (!cl_neo_hud_context_hint_enabled.GetBool()) { - g_GlowObjectManager.ClearUseItemGlowObject(); + g_GlowObjectManager.ClearUseItem(); return false; } @@ -86,7 +86,7 @@ ConVar cl_neo_hud_context_hint_show_object_interact_hint("cl_neo_hud_context_hin 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.ClearUseItemGlowObject(); + g_GlowObjectManager.ClearUseItem(); C_NEO_Player* pLocalNeoPlayer = C_NEO_Player::GetLocalNEOPlayer(); if (!pLocalNeoPlayer) @@ -177,7 +177,7 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() } else { - g_GlowObjectManager.ClearUseItemGlowObject(); + g_GlowObjectManager.ClearUseItemObject(); } } else @@ -196,7 +196,7 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() else if (C_NEO_Player* pNeoJuggernautPlayer = pJuggernaut->m_hHoldingPlayer.Get(); pNeoJuggernautPlayer && pJuggernaut->m_bIsHolding) { - g_GlowObjectManager.ClearUseItemGlowObject(); + g_GlowObjectManager.ClearUseItemPlayer(); m_flDisplayEndTime = gpGlobals->curtime; } else diff --git a/src/game/client/neo/ui/neo_root_settings.cpp b/src/game/client/neo/ui/neo_root_settings.cpp index 131163bce7..8aaf7e040a 100644 --- a/src/game/client/neo/ui/neo_root_settings.cpp +++ b/src/game/client/neo/ui/neo_root_settings.cpp @@ -687,8 +687,9 @@ void NeoSettingsRestore(NeoSettings *ns, const NeoSettings::Keys::Flags flagsKey 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->bShowHudContextHighlightObject = cvr->cl_neo_hud_context_hint_highlight_object.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->bShowHudContextHighlightBot = 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) @@ -951,8 +952,9 @@ void NeoSettingsSave(const NeoSettings *ns) 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_highlight_object.SetValue(pHUD->bShowHudContextHighlightObject); 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->bShowHudContextHighlightBot); char szSequence[NEO_IFFMARKER_SEQMAX]; ExportMarker(&pHUD->options[NEOIFFMARKER_OPTION_SQUAD], szSequence); @@ -1662,8 +1664,9 @@ void NeoSettings_HUD(NeoSettings *ns) { NeoUI::RingBoxBool(L"Show player takeover contextual hint", &pHud->bShowHudContextHintPlayerTakeover); NeoUI::RingBoxBool(L"Show object interact contextual hint", &pHud->bShowHudContextHintObjectInteract); - NeoUI::RingBoxBool(L"Highlight interactable object (requires xray enabled)", &pHud->bShowHudContextHighlightObject); NeoUI::RingBoxBool(L"Show bot interact contextual hint", &pHud->bShowHudContextHintBotInteract); + NeoUI::RingBoxBool(L"Highlight interactable object", &pHud->bShowHudContextHighlightObject); + NeoUI::RingBoxBool(L"Highlight interactable bots", &pHud->bShowHudContextHighlightBot); } #ifdef GLOWS_ENABLE diff --git a/src/game/client/neo/ui/neo_root_settings.h b/src/game/client/neo/ui/neo_root_settings.h index 92fdcd23a2..e3b646b20a 100644 --- a/src/game/client/neo/ui/neo_root_settings.h +++ b/src/game/client/neo/ui/neo_root_settings.h @@ -209,8 +209,9 @@ struct NeoSettings bool bShowHudContextHints; bool bShowHudContextHintPlayerTakeover; bool bShowHudContextHintObjectInteract; - bool bShowHudContextHighlightObject; bool bShowHudContextHintBotInteract; + bool bShowHudContextHighlightObject; + bool bShowHudContextHighlightBot; int iKdinfoToggletype; // IFF Markers @@ -278,8 +279,9 @@ struct NeoSettings 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_highlight_object); 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/shared/baseplayer_shared.cpp b/src/game/shared/baseplayer_shared.cpp index e9fa7b1c53..b99b93fc90 100644 --- a/src/game/shared/baseplayer_shared.cpp +++ b/src/game/shared/baseplayer_shared.cpp @@ -1247,15 +1247,21 @@ CBaseEntity *CBasePlayer::FindUseEntity() // estimate nearest object by distance from the view vector #ifdef NEO nearestDist = 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 ) ); + } #else Vector point; pNearest->CollisionProp()->CalcNearestPoint( searchCenter, &point ); nearestDist = CalcDistanceToLine( point, searchCenter, forward ); -#endif // NEO if ( sv_debug_player_use.GetBool() ) { Msg("Trace found %s, dist %.2f\n", pNearest->GetClassname(), nearestDist ); } +#endif // NEO } for ( CEntitySphereQuery sphere( searchCenter, PLAYER_USE_RADIUS ); ( pObject = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() ) @@ -1292,14 +1298,19 @@ CBaseEntity *CBasePlayer::FindUseEntity() #ifdef NEO float dist = dot; + + if ( sv_debug_player_use.GetBool() ) + { + Msg("Radius found %s, dist %.2f\n", pObject->GetClassname(), CalcDistanceToLine( point, searchCenter, forward ) ); + } #else float dist = CalcDistanceToLine( point, searchCenter, forward ); -#endif // NEO if ( sv_debug_player_use.GetBool() ) { Msg("Radius found %s, dist %.2f\n", pObject->GetClassname(), dist ); } +#endif // NEO #ifdef NEO if ( dist > nearestDist ) From 21c7553920a6dc7fd511e6beca3ff0bc7a5316fc Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Wed, 15 Apr 2026 14:06:58 +0100 Subject: [PATCH 11/19] return early if found a hit entity, comments, simplify glow effect checks --- src/game/client/glow_outline_effect.cpp | 17 +++++----- src/game/client/glow_outline_effect.h | 28 ++++++++-------- .../client/neo/ui/neo_hud_context_hint.cpp | 6 ++-- src/game/shared/baseplayer_shared.cpp | 32 ++++++++----------- 4 files changed, 40 insertions(+), 43 deletions(-) diff --git a/src/game/client/glow_outline_effect.cpp b/src/game/client/glow_outline_effect.cpp index a40bb833c7..c6858e6989 100644 --- a/src/game/client/glow_outline_effect.cpp +++ b/src/game/client/glow_outline_effect.cpp @@ -168,7 +168,7 @@ void CGlowObjectManager::RenderGlowModels( const CViewSetup *pSetup, int nSplitS continue; #ifdef NEO - if ( i != m_GlowObjectDefinitions.Count() - 1 && useItemGlow.m_hEntity == m_GlowObjectDefinitions[i].m_hEntity && useItemGlow.m_hEntity.IsValid()) + if ( i != m_GlowObjectDefinitions.Count() - 1 && useItem.m_hEntity.IsValid() && useItem.m_hEntity == m_GlowObjectDefinitions[i].m_hEntity) continue; // DrawModel can call ForcedMaterialOverride also @@ -217,10 +217,11 @@ void CGlowObjectManager::ApplyEntityGlowEffects( const CViewSetup *pSetup, int n int iNumGlowObjects = 0; #ifdef NEO - int useElementIndex = -1; - if (useItemGlow.m_hEntity.IsValid()) + constexpr int INVALID_USEELEMENT_INDEX = -1; + int useElementIndex = INVALID_USEELEMENT_INDEX; + if (useItem.m_hEntity.IsValid()) { - useElementIndex = m_GlowObjectDefinitions.AddToTail(useItemGlow); + useElementIndex = m_GlowObjectDefinitions.AddToTail(useItem); } #endif // NEO for ( int i = 0; i < m_GlowObjectDefinitions.Count(); ++ i ) @@ -229,7 +230,7 @@ void CGlowObjectManager::ApplyEntityGlowEffects( const CViewSetup *pSetup, int n continue; #ifdef NEO - if (useElementIndex != -1 && i != useElementIndex && useItemGlow.m_hEntity == m_GlowObjectDefinitions[i].m_hEntity) + if (i != useElementIndex && useItem.m_hEntity == m_GlowObjectDefinitions[i].m_hEntity) continue; #endif // NEO @@ -309,7 +310,7 @@ void CGlowObjectManager::ApplyEntityGlowEffects( const CViewSetup *pSetup, int n continue; #ifdef NEO - if (useElementIndex != -1 && i != useElementIndex && useItemGlow.m_hEntity == m_GlowObjectDefinitions[i].m_hEntity) + if (i != useElementIndex && useItem.m_hEntity == m_GlowObjectDefinitions[i].m_hEntity) continue; #endif // NEO @@ -350,7 +351,7 @@ void CGlowObjectManager::ApplyEntityGlowEffects( const CViewSetup *pSetup, int n if ( iNumGlowObjects <= 0 ) #ifdef NEO { - if (useElementIndex != -1) + if (useElementIndex != INVALID_USEELEMENT_INDEX) { m_GlowObjectDefinitions.Remove(useElementIndex); } @@ -368,7 +369,7 @@ void CGlowObjectManager::ApplyEntityGlowEffects( const CViewSetup *pSetup, int n RenderGlowModels( pSetup, nSplitScreenSlot, pRenderContext ); } #ifdef NEO - if (useElementIndex != -1) + if (useElementIndex != INVALID_USEELEMENT_INDEX) { m_GlowObjectDefinitions.Remove(useElementIndex); } diff --git a/src/game/client/glow_outline_effect.h b/src/game/client/glow_outline_effect.h index ed3170a30f..955cb902c9 100644 --- a/src/game/client/glow_outline_effect.h +++ b/src/game/client/glow_outline_effect.h @@ -127,37 +127,37 @@ class CGlowObjectManager m_GlowObjectDefinitions[nGlowObjectHandle].m_bUseTexturedHighlight = useTexturedHighlight; } - void SetUseItemGlowObject( 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 ) + 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; - useItemGlow.m_hEntity = pEntity; - useItemGlow.m_vGlowColor = vGlowColor; - useItemGlow.m_flGlowAlpha = flGlowAlpha; - useItemGlow.m_bRenderWhenOccluded = bRenderWhenOccluded; - useItemGlow.m_bRenderWhenUnoccluded = bRenderWhenUnoccluded; - useItemGlow.m_nSplitScreenSlot = nSplitScreenSlot; - useItemGlow.m_nNextFreeSlot = GlowObjectDefinition_t::ENTRY_IN_USE; + 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() { - useItemGlow.m_hEntity = INVALID_EHANDLE; + useItem.m_hEntity = INVALID_EHANDLE; } void ClearUseItemObject() { - C_BaseEntity* pEntity = useItemGlow.m_hEntity.Get(); + C_BaseEntity* pEntity = useItem.m_hEntity.Get(); if (pEntity && !pEntity->IsPlayer()) - useItemGlow.m_hEntity = INVALID_EHANDLE; + useItem.m_hEntity = INVALID_EHANDLE; } void ClearUseItemPlayer() { - C_BaseEntity* pEntity = useItemGlow.m_hEntity.Get(); + C_BaseEntity* pEntity = useItem.m_hEntity.Get(); if (pEntity && pEntity->IsPlayer()) - useItemGlow.m_hEntity = INVALID_EHANDLE; + useItem.m_hEntity = INVALID_EHANDLE; } #endif // NEO private: @@ -202,7 +202,7 @@ class CGlowObjectManager int m_nFirstFreeSlot; #ifdef NEO - GlowObjectDefinition_t useItemGlow; + GlowObjectDefinition_t useItem; #endif // NEO }; 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 5a582b9c92..91da606410 100644 --- a/src/game/client/neo/ui/neo_hud_context_hint.cpp +++ b/src/game/client/neo/ui/neo_hud_context_hint.cpp @@ -147,7 +147,7 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() if (CBaseEntity *pUseEntity = pLocalNeoPlayer->FindUseEntity(); pUseEntity && (g_SmokeFogOverlayThermalOverride || g_SmokeFogOverlayAlpha < ITEM_DISCOVERY_SMOKE_THRESHOLD)) { - g_GlowObjectManager.SetUseItemGlowObject(pUseEntity, Vector( 1.0f, 1.0f, 1.0f ), g_SmokeFogOverlayThermalOverride ? 1.0f : Max( 0.0f, 1.0f - g_SmokeFogOverlayAlpha), true, false); + g_GlowObjectManager.SetUseItem(pUseEntity, Vector( 1.0f, 1.0f, 1.0f ), g_SmokeFogOverlayThermalOverride ? 1.0f : Max( 0.0f, 1.0f - g_SmokeFogOverlayAlpha), true, false); if (!cl_neo_hud_context_hint_show_object_interact_hint.GetBool()) return; @@ -173,7 +173,7 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() { V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"Cannot pickup weapon"); m_flDisplayEndTime = gpGlobals->curtime + 1.f; - // g_GlowObjectManager.ClearUseItemGlowObject(); // NEO TODO (Adam) Clear highlight? or show which weapon cannot be picked up? Change highlight colour to red? + // g_GlowObjectManager.ClearuseItemObject(); // NEO TODO (Adam) Clear highlight? or show which weapon cannot be picked up? Change highlight colour to red? } else { @@ -232,7 +232,7 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() { Vector teamGlowColor { 1.f, 1.f, 1.f }; NEORules()->GetTeamGlowColor(pTargetPlayer->GetTeamNumber(), teamGlowColor[0], teamGlowColor[1], teamGlowColor[2]); - g_GlowObjectManager.SetUseItemGlowObject(pTargetPlayer, teamGlowColor, g_SmokeFogOverlayThermalOverride ? 1.0f : Max(0.0f, 1.0f - g_SmokeFogOverlayAlpha), false, true); + 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()) diff --git a/src/game/shared/baseplayer_shared.cpp b/src/game/shared/baseplayer_shared.cpp index b99b93fc90..006aad6127 100644 --- a/src/game/shared/baseplayer_shared.cpp +++ b/src/game/shared/baseplayer_shared.cpp @@ -1225,10 +1225,11 @@ CBaseEntity *CBasePlayer::FindUseEntity() pNearest = pObject; // if this is directly under the cursor just return it now -#ifdef NEO - if ( i == 0 && pObject && !(pObject->ObjectCaps() & FCAP_USE_IN_RADIUS) && !pObject->IsBaseCombatWeapon() ) // NEO NOTE (Adam) weapon AABBs are usually far removed from where they actually are visually, just use distance to worldspace center for them -#else if ( i == 0 ) +#ifdef NEO // 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()) + break; + else #endif // NEO return pObject; } @@ -1281,10 +1282,11 @@ CBaseEntity *CBasePlayer::FindUseEntity() dot = DotProduct((pObject->CollisionProp()->WorldSpaceCenter() - searchCenter).Normalized(), forward); if (sv_debug_player_use.GetBool()) - { - DebugDrawLine(pObject->CollisionProp()->WorldSpaceCenter() - Vector(4, 0, 0), pObject->CollisionProp()->WorldSpaceCenter() + Vector(4, 0, 0), 16, 255, 0, false, 0.2); - DebugDrawLine(pObject->CollisionProp()->WorldSpaceCenter() - Vector(0, 4, 0), pObject->CollisionProp()->WorldSpaceCenter() + Vector(0, 4, 0), 16, 255, 0, false, 0.2); - DebugDrawLine(pObject->CollisionProp()->WorldSpaceCenter() - Vector(0, 0, 4), pObject->CollisionProp()->WorldSpaceCenter() + Vector(0, 0, 4), 16, 255, 0, false, 0.2); + { // NEO TODO (Adam) Some games actually draw where the interaction point of an object is, in our case this world space center. A mode for STALKER GAMMA comes to mind that uses a small white dot. + // Could make it easier to pick out a weapon if there are a whole bunch close together. + DebugDrawLine(pObject->CollisionProp()->WorldSpaceCenter() - Vector(4, 0, 0), pObject->CollisionProp()->WorldSpaceCenter() + Vector(4, 0, 0), 16, 255, 0, true, 0.2); + DebugDrawLine(pObject->CollisionProp()->WorldSpaceCenter() - Vector(0, 4, 0), pObject->CollisionProp()->WorldSpaceCenter() + Vector(0, 4, 0), 16, 255, 0, true, 0.2); + DebugDrawLine(pObject->CollisionProp()->WorldSpaceCenter() - Vector(0, 0, 4), pObject->CollisionProp()->WorldSpaceCenter() + Vector(0, 0, 4), 16, 255, 0, true, 0.2); } #else Vector dir = point - searchCenter; @@ -1300,7 +1302,7 @@ CBaseEntity *CBasePlayer::FindUseEntity() float dist = dot; 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 ) ); } #else @@ -1321,23 +1323,17 @@ CBaseEntity *CBasePlayer::FindUseEntity() // 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; +#ifdef NEO // NEO NOTE (Adam) Weapons are debris, their axis aligned collision bounds can block other weapons from being picked up but are seldom aligned with the weapon itself, just stop debris from blocking player use + UTIL_TraceLine( searchCenter, point, useableContents & !CONTENTS_DEBRIS, this, COLLISION_GROUP_NONE, &trCheckOccluded ); +#else UTIL_TraceLine( searchCenter, point, useableContents, this, COLLISION_GROUP_NONE, &trCheckOccluded ); +#endif // NEO if ( trCheckOccluded.fraction == 1.0 || trCheckOccluded.m_pEnt == pObject ) { pNearest = pObject; nearestDist = dist; } -#if defined NEO - // NEO NOTE (Adam) usable entities can obstruct other usable entities. As mentioned above, weapon AABBs can be far from where they are visually and annoyingly block pickups anyway. - // Could add each weapon found to a list and redo the trace until we find a non-weapon blocking object or the object we want to pick up - else if (sv_debug_player_use.GetBool()) - { - DebugDrawLine(pObject->CollisionProp()->WorldSpaceCenter() - Vector(3, 3, 0), pObject->CollisionProp()->WorldSpaceCenter() + Vector(3, 3, 0), 255, 16, 0, false, 0.2); - DebugDrawLine(pObject->CollisionProp()->WorldSpaceCenter() - Vector(0, 3, 3), pObject->CollisionProp()->WorldSpaceCenter() + Vector(0, 3, 3), 255, 16, 0, false, 0.2); - DebugDrawLine(pObject->CollisionProp()->WorldSpaceCenter() - Vector(3, 0, 3), pObject->CollisionProp()->WorldSpaceCenter() + Vector(3, 0, 3), 255, 16, 0, false, 0.2); - } -#endif // NEO && DEBUG } } From eefc941baa3207f8503d35967d542fee93855ac8 Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Wed, 15 Apr 2026 20:21:40 +0100 Subject: [PATCH 12/19] rearrange some things, wording --- .../client/neo/ui/neo_hud_context_hint.cpp | 37 +++++++------------ src/game/client/neo/ui/neo_root_settings.cpp | 8 ++-- src/game/client/neo/ui/neo_root_settings.h | 2 +- 3 files changed, 19 insertions(+), 28 deletions(-) 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 91da606410..9583374e0e 100644 --- a/src/game/client/neo/ui/neo_hud_context_hint.cpp +++ b/src/game/client/neo/ui/neo_hud_context_hint.cpp @@ -123,11 +123,9 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() // update hint duration if (pObserverTargetPlayer != m_hLastSpecTarget.Get()) { - m_flDisplayEndTime = gpGlobals->curtime + cl_neo_spec_takeover_player_hint_time_sec.GetFloat(); m_hLastSpecTarget = pObserverTargetPlayer; - - // NEO NOTE (Adam) currently this is the only hint we can show when observing. If the hint text ever changes while observer target remains the same, will need to update this more often V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"[%hs] Control Bot", szUppercaseKeyBinding); + m_flDisplayEndTime = gpGlobals->curtime + cl_neo_spec_takeover_player_hint_time_sec.GetFloat(); } showTakeOverHint = true; @@ -136,8 +134,8 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() if (!showTakeOverHint) { - m_flDisplayEndTime = gpGlobals->curtime; m_hLastSpecTarget = INVALID_EHANDLE; + m_flDisplayEndTime = gpGlobals->curtime; } } } @@ -183,31 +181,24 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() else { // Juggernaut hint - if (Q_strcmp(pUseEntity->GetClassname(), "class C_NEO_Juggernaut") == 0) + if (CNEO_Juggernaut* pJuggernaut = dynamic_cast(pUseEntity); + pJuggernaut) { - if (CNEO_Juggernaut* pJuggernaut = static_cast(pUseEntity); - pJuggernaut) + m_flDisplayEndTime = gpGlobals->curtime + 1.f; + if (pJuggernaut->m_bLocked) { - m_flDisplayEndTime = gpGlobals->curtime + 1.f; - if (pJuggernaut->m_bLocked) - { - V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"Juggernaut 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] take the Juggernaut", szUppercaseKeyBinding); - } + V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"JGR56 is locked"); } - else + 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 diff --git a/src/game/client/neo/ui/neo_root_settings.cpp b/src/game/client/neo/ui/neo_root_settings.cpp index 8aaf7e040a..a2623a02f0 100644 --- a/src/game/client/neo/ui/neo_root_settings.cpp +++ b/src/game/client/neo/ui/neo_root_settings.cpp @@ -689,7 +689,7 @@ void NeoSettingsRestore(NeoSettings *ns, const NeoSettings::Keys::Flags flagsKey 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->bShowHudContextHighlightBot = cvr->cl_neo_hud_context_hint_highlight_player.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) @@ -954,7 +954,7 @@ void NeoSettingsSave(const NeoSettings *ns) 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->bShowHudContextHighlightBot); + cvr->cl_neo_hud_context_hint_highlight_player.SetValue(pHUD->bShowHudContextHighlightPlayer); char szSequence[NEO_IFFMARKER_SEQMAX]; ExportMarker(&pHUD->options[NEOIFFMARKER_OPTION_SQUAD], szSequence); @@ -1665,9 +1665,9 @@ void NeoSettings_HUD(NeoSettings *ns) 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 object", &pHud->bShowHudContextHighlightObject); - NeoUI::RingBoxBool(L"Highlight interactable bots", &pHud->bShowHudContextHighlightBot); } + NeoUI::RingBoxBool(L"Highlight interactable objects", &pHud->bShowHudContextHighlightObject); + NeoUI::RingBoxBool(L"Highlight interactable players", &pHud->bShowHudContextHighlightPlayer); #ifdef GLOWS_ENABLE NeoUI::Divider(L"XRAY"); diff --git a/src/game/client/neo/ui/neo_root_settings.h b/src/game/client/neo/ui/neo_root_settings.h index e3b646b20a..410933abd8 100644 --- a/src/game/client/neo/ui/neo_root_settings.h +++ b/src/game/client/neo/ui/neo_root_settings.h @@ -211,7 +211,7 @@ struct NeoSettings bool bShowHudContextHintObjectInteract; bool bShowHudContextHintBotInteract; bool bShowHudContextHighlightObject; - bool bShowHudContextHighlightBot; + bool bShowHudContextHighlightPlayer; int iKdinfoToggletype; // IFF Markers From 527b68cb35b18d5ce5aabe4d0f239cc33b7b5853 Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Sat, 18 Apr 2026 18:02:16 +0100 Subject: [PATCH 13/19] showing adjacent usable items --- src/game/client/c_baseplayer.h | 4 + src/game/client/glow_outline_effect.cpp | 4 + src/game/client/neo/c_neo_player.h | 1 + .../client/neo/ui/neo_hud_context_hint.cpp | 75 +++++- src/game/client/neo/ui/neo_hud_context_hint.h | 4 + src/game/server/hl2/hl2_player.cpp | 3 + src/game/server/neo/neo_player.cpp | 80 ++++-- src/game/server/neo/neo_player.h | 6 +- src/game/server/player_resource.cpp | 2 +- src/game/shared/basecombatweapon_shared.cpp | 4 + src/game/shared/baseplayer_shared.cpp | 49 ---- src/game/shared/baseplayer_shared.h | 4 + src/game/shared/neo/neo_player_shared.cpp | 233 +++++++++++++++++- src/game/shared/neo/neo_player_shared.h | 4 + .../weapons/weapon_neobasecombatweapon.cpp | 5 +- .../neo/weapons/weapon_neobasecombatweapon.h | 9 +- 16 files changed, 402 insertions(+), 85 deletions(-) diff --git a/src/game/client/c_baseplayer.h b/src/game/client/c_baseplayer.h index be8c78f157..332e921a2b 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/glow_outline_effect.cpp b/src/game/client/glow_outline_effect.cpp index c6858e6989..5e3c89d1bb 100644 --- a/src/game/client/glow_outline_effect.cpp +++ b/src/game/client/glow_outline_effect.cpp @@ -93,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 ); diff --git a/src/game/client/neo/c_neo_player.h b/src/game/client/neo/c_neo_player.h index a95e97a3c8..2768a700df 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; 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 9583374e0e..b6043c1c28 100644 --- a/src/game/client/neo/ui/neo_hud_context_hint.cpp +++ b/src/game/client/neo/ui/neo_hud_context_hint.cpp @@ -87,6 +87,7 @@ ConVar cl_neo_hud_context_hint_show_bot_interact_hint("cl_neo_hud_context_hint_s void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() { g_GlowObjectManager.ClearUseItem(); + m_hUseEntity = nullptr; C_NEO_Player* pLocalNeoPlayer = C_NEO_Player::GetLocalNEOPlayer(); if (!pLocalNeoPlayer) @@ -124,7 +125,7 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() if (pObserverTargetPlayer != m_hLastSpecTarget.Get()) { m_hLastSpecTarget = pObserverTargetPlayer; - V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"[%hs] Control Bot", szUppercaseKeyBinding); + 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(); } @@ -142,10 +143,12 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() else { constexpr float ITEM_DISCOVERY_SMOKE_THRESHOLD = 0.8f; + SetAddUseEntitysToUseEntityList(true); if (CBaseEntity *pUseEntity = pLocalNeoPlayer->FindUseEntity(); - pUseEntity && (g_SmokeFogOverlayThermalOverride || g_SmokeFogOverlayAlpha < ITEM_DISCOVERY_SMOKE_THRESHOLD)) + SetAddUseEntitysToUseEntityList(false) && pUseEntity && (g_SmokeFogOverlayThermalOverride || g_SmokeFogOverlayAlpha < ITEM_DISCOVERY_SMOKE_THRESHOLD)) { 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; @@ -171,11 +174,12 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() { V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"Cannot pickup weapon"); m_flDisplayEndTime = gpGlobals->curtime + 1.f; - // g_GlowObjectManager.ClearuseItemObject(); // NEO TODO (Adam) Clear highlight? or show which weapon cannot be picked up? Change highlight colour to red? + m_hUseEntity = INVALID_EHANDLE; } else { g_GlowObjectManager.ClearUseItemObject(); + m_hUseEntity = INVALID_EHANDLE; } } else @@ -234,19 +238,37 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() { 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? } - // else if NEO TODO (Adam) check if bots are frozen because of no navigation mesh or nb_player_stop, show appropriate message here? } } } } } -void CNEOHud_ContextHint::DrawNeoHudElement() +struct UseEntity { - if (m_wszHintText[0] == L'\0') + 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; +}; - if (m_flDisplayEndTime <= gpGlobals->curtime) +void ClearUseEntityListEntry() +{ + useEntityListLastIndex = -1; +}; + +void CNEOHud_ContextHint::DrawNeoHudElement() +{ + if (m_wszHintText[0] == L'\0') return; int iScrWide, iScrTall; @@ -260,16 +282,49 @@ 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))); - - // NEO TODO (Adam) different text colour for keybind, instruction and target name? } void CNEOHud_ContextHint::Paint() 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 08cee2cee3..944677b7f9 100644 --- a/src/game/client/neo/ui/neo_hud_context_hint.h +++ b/src/game/client/neo/ui/neo_hud_context_hint.h @@ -38,6 +38,7 @@ class CNEOHud_ContextHint : public CNEOHud_ChildElement, public CHudElement, pub 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"); @@ -47,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/server/hl2/hl2_player.cpp b/src/game/server/hl2/hl2_player.cpp index 8656ca90ac..88f10a56f4 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 08aac2e629..c0cfdbfa3b 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" @@ -763,16 +764,6 @@ void CNEO_Player::Spawn(void) m_iBotDetectableBleedingInjuryEvents = 0; } -void CNEO_Player::PlayerRunCommand(CUserCmd* ucmd, IMoveHelper* moveHelper) -{ - if (ucmd->forwardmove || ucmd->sidemove || ucmd->upmove || ucmd->buttons) - { - m_flLastInput = gpGlobals->curtime; - } - - BaseClass::PlayerRunCommand(ucmd, moveHelper); -} - extern ConVar neo_lean_angle; ConVar neo_lean_thirdperson_roll_lerp_scale("neo_lean_thirdperson_roll_lerp_scale", "5", @@ -2441,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); } @@ -2618,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)) @@ -2701,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; } @@ -2734,7 +2778,7 @@ bool CNEO_Player::BumpWeapon( CBaseCombatWeapon *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++) @@ -2743,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()); } } @@ -3705,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) diff --git a/src/game/server/neo/neo_player.h b/src/game/server/neo/neo_player.h index 6ad9aa259d..18f7ea1c87 100644 --- a/src/game/server/neo/neo_player.h +++ b/src/game/server/neo/neo_player.h @@ -63,7 +63,6 @@ class CNEO_Player : public CHL2MP_Player virtual void Precache(void) OVERRIDE; virtual void Spawn(void) OVERRIDE; - virtual void PlayerRunCommand(CUserCmd* ucmd, IMoveHelper* moveHelper) override; virtual void PostThink(void) OVERRIDE; virtual void CalculateSpeed(void); virtual void PreThink(void) 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; @@ -326,7 +326,7 @@ class CNEO_Player : public CHL2MP_Player void ResetBotCommandState(); void ToggleBotFollowCommander( CNEO_Player *pCommander ); static const Vector VECTOR_INVALID_WAYPOINT; - float m_flLastInput = gpGlobals->curtime; + float m_flLastInputTime = gpGlobals->curtime; private: bool m_bFirstDeathTick; diff --git a/src/game/server/player_resource.cpp b/src/game/server/player_resource.cpp index c9b4e7e484..02ef368939 100644 --- a/src/game/server/player_resource.cpp +++ b/src/game/server/player_resource.cpp @@ -179,7 +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_flLastInput > sv_neo_spec_replace_player_afk_time_sec.GetInt()); + 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/shared/basecombatweapon_shared.cpp b/src/game/shared/basecombatweapon_shared.cpp index e72e024be8..17296e9ca1 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.cpp b/src/game/shared/baseplayer_shared.cpp index 006aad6127..23e4d887bb 100644 --- a/src/game/shared/baseplayer_shared.cpp +++ b/src/game/shared/baseplayer_shared.cpp @@ -1158,11 +1158,7 @@ CBaseEntity *CBasePlayer::FindUseEntity() // UNDONE: Might be faster to just fold this range into the sphere query CBaseEntity *pObject = NULL; -#ifdef NEO - float nearestDist = -1; -#else float nearestDist = FLT_MAX; -#endif // NEO // try the hit entity if there is one, or the ground entity if there isn't. CBaseEntity *pNearest = NULL; @@ -1226,11 +1222,6 @@ CBaseEntity *CBasePlayer::FindUseEntity() // if this is directly under the cursor just return it now if ( i == 0 ) -#ifdef NEO // 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()) - break; - else -#endif // NEO return pObject; } } @@ -1246,15 +1237,6 @@ CBaseEntity *CBasePlayer::FindUseEntity() if ( pNearest ) { // estimate nearest object by distance from the view vector -#ifdef NEO - nearestDist = 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 ) ); - } -#else Vector point; pNearest->CollisionProp()->CalcNearestPoint( searchCenter, &point ); nearestDist = CalcDistanceToLine( point, searchCenter, forward ); @@ -1262,7 +1244,6 @@ CBaseEntity *CBasePlayer::FindUseEntity() { Msg("Trace found %s, dist %.2f\n", pNearest->GetClassname(), nearestDist ); } -#endif // NEO } for ( CEntitySphereQuery sphere( searchCenter, PLAYER_USE_RADIUS ); ( pObject = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() ) @@ -1277,57 +1258,27 @@ CBaseEntity *CBasePlayer::FindUseEntity() Vector point; pObject->CollisionProp()->CalcNearestPoint( searchCenter, &point ); -#ifdef NEO - float dot = -1; - dot = DotProduct((pObject->CollisionProp()->WorldSpaceCenter() - searchCenter).Normalized(), forward); - - if (sv_debug_player_use.GetBool()) - { // NEO TODO (Adam) Some games actually draw where the interaction point of an object is, in our case this world space center. A mode for STALKER GAMMA comes to mind that uses a small white dot. - // Could make it easier to pick out a weapon if there are a whole bunch close together. - DebugDrawLine(pObject->CollisionProp()->WorldSpaceCenter() - Vector(4, 0, 0), pObject->CollisionProp()->WorldSpaceCenter() + Vector(4, 0, 0), 16, 255, 0, true, 0.2); - DebugDrawLine(pObject->CollisionProp()->WorldSpaceCenter() - Vector(0, 4, 0), pObject->CollisionProp()->WorldSpaceCenter() + Vector(0, 4, 0), 16, 255, 0, true, 0.2); - DebugDrawLine(pObject->CollisionProp()->WorldSpaceCenter() - Vector(0, 0, 4), pObject->CollisionProp()->WorldSpaceCenter() + Vector(0, 0, 4), 16, 255, 0, true, 0.2); - } -#else Vector dir = point - searchCenter; VectorNormalize(dir); float dot = DotProduct( dir, forward ); -#endif // NEO // Need to be looking at the object more or less if ( dot < 0.8 ) continue; -#ifdef NEO - float dist = dot; - - 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 ) ); - } -#else float dist = CalcDistanceToLine( point, searchCenter, forward ); if ( sv_debug_player_use.GetBool() ) { Msg("Radius found %s, dist %.2f\n", pObject->GetClassname(), dist ); } -#endif // NEO -#ifdef NEO - if ( dist > nearestDist ) -#else if ( dist < nearestDist ) -#endif // NEO { // 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; -#ifdef NEO // NEO NOTE (Adam) Weapons are debris, their axis aligned collision bounds can block other weapons from being picked up but are seldom aligned with the weapon itself, just stop debris from blocking player use - UTIL_TraceLine( searchCenter, point, useableContents & !CONTENTS_DEBRIS, this, COLLISION_GROUP_NONE, &trCheckOccluded ); -#else UTIL_TraceLine( searchCenter, point, useableContents, this, COLLISION_GROUP_NONE, &trCheckOccluded ); -#endif // NEO if ( trCheckOccluded.fraction == 1.0 || trCheckOccluded.m_pEnt == pObject ) { diff --git a/src/game/shared/baseplayer_shared.h b/src/game/shared/baseplayer_shared.h index 0e3400183a..791122754b 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_player_shared.cpp b/src/game/shared/neo/neo_player_shared.cpp index 6230db84cf..9593072c68 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" @@ -28,6 +29,11 @@ #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" @@ -408,7 +414,7 @@ void CNEO_Player::CheckAimButtons() bool CNEO_Player::IsAFK() const { #ifdef GAME_DLL - return gpGlobals->curtime - m_flLastInput > sv_neo_spec_replace_player_afk_time_sec.GetInt(); + return gpGlobals->curtime - m_flLastInputTime > sv_neo_spec_replace_player_afk_time_sec.GetInt(); #else return GameResources()->IsAfk(entindex()); #endif // GAME_DLL @@ -432,4 +438,227 @@ bool CNEO_Player::ValidTakeoverTargetFor(CNEO_Player *pPlayerTakingOver) && InSameTeam(pPlayerTakingOver) && NEORules()->IsTeamplay() && (sv_neo_spec_replace_player_bot_enable.GetBool() && IsFakePlayer() || sv_neo_spec_replace_player_afk_enable.GetBool() && IsAFK()); -} \ No newline at end of file +} + +#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())) + 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 b450ccad02..9c268839c9 100644 --- a/src/game/shared/neo/neo_player_shared.h +++ b/src/game/shared/neo/neo_player_shared.h @@ -413,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_neobasecombatweapon.cpp b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp index 13829c183f..d1ea3abde4 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() @@ -1384,8 +1386,7 @@ void CNEOBaseCombatWeapon::Use(CBaseEntity* pActivator, CBaseEntity* pCaller, US { CBaseCombatWeapon* pActiveWeapon = pNeoPlayer->GetActiveWeapon(); const int activeSlot = pActiveWeapon ? pActiveWeapon->GetSlot() : -1; - pNeoPlayer->Weapon_DropSlot(GetSlot()); // NEO NOTE (Adam) no guarantee we will actually pick up the weapon. CanBePickedUpByClass should catch most problems - // Also we shouldn't do this for throwables since you can have multiple in the same slot, but throwables can't be dropped so not a problem right now + pNeoPlayer->Weapon_DropSlot(GetSlot()); (this->*m_pfnTouch)(pActivator); diff --git a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h index 0ab7040f9d..089d216528 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. @@ -148,10 +149,12 @@ class CNEOBaseCombatWeapon : public CBaseHL2MPCombatWeapon int caps = BaseClass::ObjectCaps(); if (!IsFollowingEntity() #ifdef GAME_DLL - && !HasSpawnFlags(SF_WEAPON_NO_PLAYER_PICKUP) // NEO TODO (Adam) workout how to check this client side + && !HasSpawnFlags(SF_WEAPON_NO_PLAYER_PICKUP) +#else + && !(m_spawnflags & SF_WEAPON_NO_PLAYER_PICKUP) #endif // GAME_DLL ) - { // NEO NOTE (Adam) debris can be usable too, and debris such as ragdolls and gibs prevents us from picking up weapons with use via direct traceline, allow usage in radius too. + { caps |= FCAP_IMPULSE_USE|FCAP_USE_IN_RADIUS; } @@ -177,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; @@ -252,6 +256,7 @@ class CNEOBaseCombatWeapon : public CBaseHL2MPCombatWeapon float GetPenetration() const; #ifdef CLIENT_DLL float m_flTemperature; + int m_spawnflags; #endif // CLIENT_DLL protected: From bd2080bcd254babd097aa9f9b26ca0d7401babde Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Tue, 28 Apr 2026 13:57:31 +0100 Subject: [PATCH 14/19] add nearby to sphere --- src/game/shared/neo/neo_player_shared.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/game/shared/neo/neo_player_shared.cpp b/src/game/shared/neo/neo_player_shared.cpp index 9593072c68..d0b73b9d8e 100644 --- a/src/game/shared/neo/neo_player_shared.cpp +++ b/src/game/shared/neo/neo_player_shared.cpp @@ -523,7 +523,15 @@ CBaseEntity *CNEO_Player::FindUseEntity() // 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; + } } } } From 28baa21c0a6f47d8a2ea53e2f052b7284d31503a Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Sun, 3 May 2026 11:54:31 +0100 Subject: [PATCH 15/19] spec changes --- src/game/client/neo/ui/neo_hud_context_hint.cpp | 17 ++++++++++------- src/game/client/neo/ui/neo_root_settings.cpp | 10 ++++++++-- src/game/client/neo/ui/neo_root_settings.h | 2 ++ src/game/shared/neo/neo_player_shared.cpp | 15 +++++---------- src/game/shared/neo/neo_player_shared.h | 4 ---- 5 files changed, 25 insertions(+), 23 deletions(-) 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 b6043c1c28..c58c2eabe3 100644 --- a/src/game/client/neo/ui/neo_hud_context_hint.cpp +++ b/src/game/client/neo/ui/neo_hud_context_hint.cpp @@ -72,6 +72,7 @@ bool CNEOHud_ContextHint::ShouldDraw() if (!cl_neo_hud_context_hint_enabled.GetBool()) { g_GlowObjectManager.ClearUseItem(); + ClearUseEntityListEntry(); return false; } @@ -82,12 +83,12 @@ 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_object_interact_hint("cl_neo_hud_context_hint_show_object_interact_hint", "1", FCVAR_ARCHIVE, "Show object interact 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; + ClearUseEntityListEntry(); C_NEO_Player* pLocalNeoPlayer = C_NEO_Player::GetLocalNEOPlayer(); if (!pLocalNeoPlayer) @@ -110,7 +111,10 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() if (pLocalNeoPlayer->IsObserver()) { if (!cl_neo_hud_context_hint_show_player_takeover_hint.GetBool()) + { + m_flDisplayEndTime = gpGlobals->curtime; return; + } // Takeover hint { @@ -143,9 +147,8 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() else { 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)) + pUseEntity && (g_SmokeFogOverlayThermalOverride || g_SmokeFogOverlayAlpha < ITEM_DISCOVERY_SMOKE_THRESHOLD)) { 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; @@ -266,6 +269,7 @@ void ClearUseEntityListEntry() useEntityListLastIndex = -1; }; +ConVar cl_neo_hud_context_hint_show_object_interact_hint_in_world("cl_neo_hud_context_hint_show_object_interact_hint_in_world", "1", FCVAR_ARCHIVE, "Draw the object interact hint next to the interactable object", true, 0.f, true, 1.f); void CNEOHud_ContextHint::DrawNeoHudElement() { if (m_wszHintText[0] == L'\0') @@ -305,10 +309,9 @@ void CNEOHud_ContextHint::DrawNeoHudElement() if (m_flDisplayEndTime <= gpGlobals->curtime) return; - if (m_hUseEntity.IsValid()) + if (cl_neo_hud_context_hint_show_object_interact_hint_in_world.GetBool() && m_hUseEntity.IsValid()) { - C_BaseEntity* useEntity = m_hUseEntity.Get(); - if (useEntity) + if (C_BaseEntity* useEntity = m_hUseEntity.Get()) { GetVectorInScreenSpace(useEntity->CollisionProp()->WorldSpaceCenter(), textX, textY); diff --git a/src/game/client/neo/ui/neo_root_settings.cpp b/src/game/client/neo/ui/neo_root_settings.cpp index a2623a02f0..6f4cec827c 100644 --- a/src/game/client/neo/ui/neo_root_settings.cpp +++ b/src/game/client/neo/ui/neo_root_settings.cpp @@ -687,6 +687,7 @@ void NeoSettingsRestore(NeoSettings *ns, const NeoSettings::Keys::Flags flagsKey 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->bShowHudContextAdjacentObjects = cvr->cl_neo_hud_context_hint_show_adjacent_interactable_objects.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(); @@ -952,6 +953,7 @@ void NeoSettingsSave(const NeoSettings *ns) 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_adjacent_interactable_objects.SetValue(pHUD->bShowHudContextAdjacentObjects && 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); @@ -1664,10 +1666,14 @@ void NeoSettings_HUD(NeoSettings *ns) { NeoUI::RingBoxBool(L"Show player takeover contextual hint", &pHud->bShowHudContextHintPlayerTakeover); NeoUI::RingBoxBool(L"Show object interact contextual hint", &pHud->bShowHudContextHintObjectInteract); + if (pHud->bShowHudContextHintObjectInteract) + { + NeoUI::RingBoxBool(L"Show adjacent interactable objects", &pHud->bShowHudContextAdjacentObjects); + } 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); } - NeoUI::RingBoxBool(L"Highlight interactable objects", &pHud->bShowHudContextHighlightObject); - NeoUI::RingBoxBool(L"Highlight interactable players", &pHud->bShowHudContextHighlightPlayer); #ifdef GLOWS_ENABLE NeoUI::Divider(L"XRAY"); diff --git a/src/game/client/neo/ui/neo_root_settings.h b/src/game/client/neo/ui/neo_root_settings.h index 410933abd8..78dbebc8fc 100644 --- a/src/game/client/neo/ui/neo_root_settings.h +++ b/src/game/client/neo/ui/neo_root_settings.h @@ -209,6 +209,7 @@ struct NeoSettings bool bShowHudContextHints; bool bShowHudContextHintPlayerTakeover; bool bShowHudContextHintObjectInteract; + bool bShowHudContextAdjacentObjects; bool bShowHudContextHintBotInteract; bool bShowHudContextHighlightObject; bool bShowHudContextHighlightPlayer; @@ -279,6 +280,7 @@ struct NeoSettings 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_adjacent_interactable_objects); 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); diff --git a/src/game/shared/neo/neo_player_shared.cpp b/src/game/shared/neo/neo_player_shared.cpp index d0b73b9d8e..d769d60c3e 100644 --- a/src/game/shared/neo/neo_player_shared.cpp +++ b/src/game/shared/neo/neo_player_shared.cpp @@ -442,15 +442,7 @@ bool CNEO_Player::ValidTakeoverTargetFor(CNEO_Player *pPlayerTakingOver) #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() { @@ -477,6 +469,9 @@ CBaseEntity *CNEO_Player::FindUseEntity() // try the hit entity if there is one, or the ground entity if there isn't. CBaseEntity *pNearest = nullptr; +#ifdef CLIENT_DLL + bool addUseItemsToUseItemsList = cl_neo_hud_context_hint_show_adjacent_interactable_objects.GetBool(); +#endif // CLIENT_DLL { UTIL_TraceLine( searchCenter, searchCenter + forward * 1024, useableContents, this, COLLISION_GROUP_NONE, &tr ); pObject = tr.m_pEnt; @@ -526,7 +521,7 @@ CBaseEntity *CNEO_Player::FindUseEntity() { #ifdef CLIENT_DLL // Client side do the sphere query anyway to show adjacent items - if (gAddUseItemsToUseItemsList) + if (addUseItemsToUseItemsList) nearestDot = 0.f; else #endif // CLIENT_DLL @@ -570,7 +565,7 @@ CBaseEntity *CNEO_Player::FindUseEntity() float dot = -1; dot = DotProduct((pObject->CollisionProp()->WorldSpaceCenter() - searchCenter).Normalized(), forward); - if (gAddUseItemsToUseItemsList && IsUseableEntity(pObject, 0) && dot >= 0.8) + if (addUseItemsToUseItemsList && IsUseableEntity(pObject, 0) && dot >= 0.8) { pObject->CollisionProp()->CalcNearestPoint( searchCenter, &point ); trace_t trCheckOccluded; diff --git a/src/game/shared/neo/neo_player_shared.h b/src/game/shared/neo/neo_player_shared.h index 9c268839c9..b450ccad02 100644 --- a/src/game/shared/neo/neo_player_shared.h +++ b/src/game/shared/neo/neo_player_shared.h @@ -413,10 +413,6 @@ 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 From d42580bfa2183936796dc361406b455eec807c1c Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Sun, 3 May 2026 16:40:30 +0100 Subject: [PATCH 16/19] fixup ghost --- src/game/client/neo/c_neo_player.cpp | 7 +- .../client/neo/ui/neo_hud_context_hint.cpp | 1 + .../neo/ui/neo_hud_ghost_uplink_state.cpp | 13 ++- src/game/shared/neo/weapons/weapon_ghost.cpp | 108 ++++++------------ src/game/shared/neo/weapons/weapon_ghost.h | 15 +-- 5 files changed, 53 insertions(+), 91 deletions(-) diff --git a/src/game/client/neo/c_neo_player.cpp b/src/game/client/neo/c_neo_player.cpp index ead4529b1e..13c4ec5f09 100644 --- a/src/game/client/neo/c_neo_player.cpp +++ b/src/game/client/neo/c_neo_player.cpp @@ -1673,12 +1673,7 @@ void C_NEO_Player::Weapon_Drop(C_NEOBaseCombatWeapon *pWeapon) { m_bIneligibleForLoadoutPick = true; - if (pWeapon->IsGhost()) - { - pWeapon->Holster(NULL); - assert_cast(pWeapon)->HandleGhostUnequip(); - } - else if (pWeapon->GetNeoWepBits() & NEO_WEP_SUPA7) + if (pWeapon->GetNeoWepBits() & NEO_WEP_SUPA7) { assert_cast(pWeapon)->ClearDelayedInputs(); } 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 c58c2eabe3..b88b08bdc6 100644 --- a/src/game/client/neo/ui/neo_hud_context_hint.cpp +++ b/src/game/client/neo/ui/neo_hud_context_hint.cpp @@ -88,6 +88,7 @@ ConVar cl_neo_hud_context_hint_show_bot_interact_hint("cl_neo_hud_context_hint_s void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() { g_GlowObjectManager.ClearUseItem(); + m_hUseEntity = INVALID_EHANDLE; ClearUseEntityListEntry(); C_NEO_Player* pLocalNeoPlayer = C_NEO_Player::GetLocalNEOPlayer(); diff --git a/src/game/client/neo/ui/neo_hud_ghost_uplink_state.cpp b/src/game/client/neo/ui/neo_hud_ghost_uplink_state.cpp index d4dec3a34f..2ceca0ef42 100644 --- a/src/game/client/neo/ui/neo_hud_ghost_uplink_state.cpp +++ b/src/game/client/neo/ui/neo_hud_ghost_uplink_state.cpp @@ -78,16 +78,21 @@ void CNEOHud_GhostUplinkState::UpdateStateForNeoHudElementDraw() } } +extern ConVar sv_neo_ctg_ghost_beacons_when_inactive; void CNEOHud_GhostUplinkState::DrawNeoHudElement() { if (!ShouldDraw()) - { return; - } + + if (NEORules()->IsRoundOver()) + return; + + C_NEO_Player* localPlayer = C_NEO_Player::GetLocalNEOPlayer(); + if (!localPlayer) + return; - auto localPlayer = C_NEO_Player::GetLocalNEOPlayer(); const auto wep = static_cast(localPlayer->GetActiveWeapon()); - if (wep && wep->IsGhost()) + if ((wep && wep->IsGhost()) || (sv_neo_ctg_ghost_beacons_when_inactive.GetBool() && (NEORules()->GetGhosterPlayer() == localPlayer->entindex() || localPlayer->IsCarryingGhost()))) { if (m_flTimeGhostEquip == 0.f) { diff --git a/src/game/shared/neo/weapons/weapon_ghost.cpp b/src/game/shared/neo/weapons/weapon_ghost.cpp index 7f99233e33..550aae00d2 100644 --- a/src/game/shared/neo/weapons/weapon_ghost.cpp +++ b/src/game/shared/neo/weapons/weapon_ghost.cpp @@ -57,8 +57,6 @@ PRECACHE_WEAPON_REGISTER(weapon_ghost); CWeaponGhost::CWeaponGhost(void) { #ifdef CLIENT_DLL - m_bHavePlayedGhostEquipSound = false; - m_bHaveHolsteredTheGhost = false; m_flLastGhostBeepTime = 0; #endif m_flDeployTime = 0; @@ -75,16 +73,6 @@ CWeaponGhost::~CWeaponGhost() } #endif -void CWeaponGhost::ItemPreFrame(void) -{ -#ifdef CLIENT_DLL - if (!sv_neo_ctg_ghost_beacons_when_inactive.GetBool()) - { - HandleGhostEquip(); - } -#endif -} - #ifdef GAME_DLL int CWeaponGhost::UpdateTransmitState() { @@ -102,64 +90,61 @@ int CWeaponGhost::ShouldTransmit(const CCheckTransmitInfo *pInfo) } #endif -// Consider calling HandleGhostEquip instead. -void CWeaponGhost::PlayGhostSound(float volume) -{ #ifdef CLIENT_DLL - auto owner = static_cast(GetOwner()); -#else - auto owner = static_cast(GetOwner()); -#endif // CLIENT_DLL - if (!owner) +void CWeaponGhost::OnDataChanged(DataUpdateType_t updateType) +{ + if (updateType == DATA_UPDATE_DATATABLE_CHANGED && m_iOldState != m_iState) { - return; + if (!IsCarriedByLocalPlayer()) + { + StopGhostSound(); + } + else + { + if (sv_neo_ctg_ghost_beacons_when_inactive.GetBool()) + { + if (m_iState == WEAPON_NOT_CARRIED) + StopGhostSound(); + else if (m_iOldState == WEAPON_NOT_CARRIED) + PlayGhostSound(); + } + else + { + if (m_iState == WEAPON_IS_ACTIVE) + PlayGhostSound(); + else + StopGhostSound(); + } + } + // NEO TODO (Adam) if round is over, stop the ghost sound too? } -#ifdef CLIENT_DLL - C_RecipientFilter filter; -#else - CRecipientFilter filter; -#endif // CLIENT_DLL - filter.AddRecipient(owner); - - EmitSound_t emitSoundType; - emitSoundType.m_flVolume = volume; - emitSoundType.m_pSoundName = "HUD.GhostEquip"; - emitSoundType.m_nFlags |= SND_SHOULDPAUSE | SND_DO_NOT_OVERWRITE_EXISTING_ON_CHANNEL; - - EmitSound(filter, entindex(), emitSoundType); + BaseClass::OnDataChanged(updateType); } -#ifdef CLIENT_DLL -void CWeaponGhost::HandleGhostEquip(void) +void CWeaponGhost::PlayGhostSound() { - if (!m_bHavePlayedGhostEquipSound) + C_BasePlayer* owner = static_cast(GetOwner()); + if (!owner) { - PlayGhostSound(); - m_bHavePlayedGhostEquipSound = true; - m_bHaveHolsteredTheGhost = false; + return; } -} -void CWeaponGhost::HandleGhostUnequip(void) -{ - if (!m_bHaveHolsteredTheGhost) - { - StopGhostSound(); - m_bHaveHolsteredTheGhost = true; - m_bHavePlayedGhostEquipSound = false; - } + CLocalPlayerFilter filter; + EmitSound_t soundParams; + soundParams.m_flVolume = 1.f; + soundParams.m_pSoundName = "HUD.GhostEquip"; + soundParams.m_nFlags |= SND_SHOULDPAUSE | SND_DO_NOT_OVERWRITE_EXISTING_ON_CHANNEL; + + EmitSound(filter, entindex(), soundParams); } -// Consider calling HandleGhostUnequip instead. void CWeaponGhost::StopGhostSound(void) { StopSound(this->entindex(), "HUD.GhostEquip"); } -#endif // Emit a ghost ping at a refire interval based on distance. -#ifdef CLIENT_DLL void CWeaponGhost::TryGhostPing() { const auto* owner = GetOwner(); @@ -192,19 +177,7 @@ void CWeaponGhost::TryGhostPing() m_flLastGhostBeepTime = gpGlobals->curtime; } } -#endif - -void CWeaponGhost::ItemHolsterFrame(void) -{ - BaseClass::ItemHolsterFrame(); - -#ifdef CLIENT_DLL - if (!sv_neo_ctg_ghost_beacons_when_inactive.GetBool()) - { - HandleGhostUnequip(); // is there some callback, so we don't have to keep calling this? - } -#endif -} +#endif // CLIENT_DLL enum { NEO_GHOST_ONLY_ENEMIES = 0, @@ -253,11 +226,6 @@ void CWeaponGhost::Equip(CBaseCombatCharacter *pNewOwner) soundFilter.RemoveRecipientByPlayerIndex(ownerIndex); soundFilter.MakeReliable(); EmitSound(soundFilter, ownerIndex, soundParams); - - if (sv_neo_ctg_ghost_beacons_when_inactive.GetBool()) - { - PlayGhostSound(); - } #endif } diff --git a/src/game/shared/neo/weapons/weapon_ghost.h b/src/game/shared/neo/weapons/weapon_ghost.h index db576612c4..b84a0dc408 100644 --- a/src/game/shared/neo/weapons/weapon_ghost.h +++ b/src/game/shared/neo/weapons/weapon_ghost.h @@ -38,6 +38,8 @@ class CWeaponGhost : public CNEOBaseCombatWeapon UpdateNearestGhostBeaconDist(); TryGhostPing(); } + + virtual void OnDataChanged(DataUpdateType_t updateType) override final; #endif // When the player "activates" the ghost, there is a slight time delay until they're allowed @@ -55,15 +57,12 @@ class CWeaponGhost : public CNEOBaseCombatWeapon // If the enemy is not in range, the distance will not be written into. [[nodiscard]] bool BeaconRange(CBaseEntity* enemy, float& outDistance) const; - virtual void ItemPreFrame(void) OVERRIDE; virtual void PrimaryAttack(void) OVERRIDE { } virtual void SecondaryAttack(void) OVERRIDE { } virtual bool Deploy() override; virtual void Drop(const Vector &vecVelocity) override; - virtual void ItemHolsterFrame(void); virtual void Equip(CBaseCombatCharacter *pNewOwner) override; - void HandleGhostUnequip(void); bool CanBePickedUpByClass(int classId) OVERRIDE; virtual bool CanAim() final { return false; } @@ -80,11 +79,6 @@ class CWeaponGhost : public CNEOBaseCombatWeapon int ShouldTransmit(const CCheckTransmitInfo *pInfo) override; #endif -#ifdef CLIENT_DLL - void StopGhostSound(void); - void HandleGhostEquip(void); -#endif - virtual void UpdateOnRemove() override { if (GetOwner()) @@ -99,12 +93,11 @@ class CWeaponGhost : public CNEOBaseCombatWeapon }; private: - void PlayGhostSound(float volume = 1.0f); #ifdef CLIENT_DLL + void PlayGhostSound(); + void StopGhostSound(); void TryGhostPing(); - bool m_bHavePlayedGhostEquipSound; - bool m_bHaveHolsteredTheGhost; float m_flLastGhostBeepTime; #endif From b4d133d33b1af19ecc7649874156642ffe09f2f6 Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Sun, 3 May 2026 17:52:17 +0100 Subject: [PATCH 17/19] brackets --- src/game/client/neo/ui/neo_hud_ghost_uplink_state.cpp | 11 ++++++----- src/game/shared/neo/neo_player_shared.cpp | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/game/client/neo/ui/neo_hud_ghost_uplink_state.cpp b/src/game/client/neo/ui/neo_hud_ghost_uplink_state.cpp index 2ceca0ef42..f2493fa026 100644 --- a/src/game/client/neo/ui/neo_hud_ghost_uplink_state.cpp +++ b/src/game/client/neo/ui/neo_hud_ghost_uplink_state.cpp @@ -83,17 +83,18 @@ void CNEOHud_GhostUplinkState::DrawNeoHudElement() { if (!ShouldDraw()) return; - - if (NEORules()->IsRoundOver()) - return; C_NEO_Player* localPlayer = C_NEO_Player::GetLocalNEOPlayer(); if (!localPlayer) return; - const auto wep = static_cast(localPlayer->GetActiveWeapon()); - if ((wep && wep->IsGhost()) || (sv_neo_ctg_ghost_beacons_when_inactive.GetBool() && (NEORules()->GetGhosterPlayer() == localPlayer->entindex() || localPlayer->IsCarryingGhost()))) + C_NEOBaseCombatWeapon* pActiveWeapon = static_cast(localPlayer->GetActiveWeapon()); + if ((pActiveWeapon && pActiveWeapon->IsGhost()) || + (sv_neo_ctg_ghost_beacons_when_inactive.GetBool() && (NEORules()->GetGhosterPlayer() == localPlayer->entindex() || localPlayer->IsCarryingGhost()))) { + if (NEORules()->IsRoundOver()) + return; + if (m_flTimeGhostEquip == 0.f) { m_flTimeGhostEquip = gpGlobals->curtime; diff --git a/src/game/shared/neo/neo_player_shared.cpp b/src/game/shared/neo/neo_player_shared.cpp index 53dbe40583..fe96d73046 100644 --- a/src/game/shared/neo/neo_player_shared.cpp +++ b/src/game/shared/neo/neo_player_shared.cpp @@ -476,8 +476,8 @@ bool CNEO_Player::ValidTakeoverTargetFor(CNEO_Player *pPlayerTakingOver) && 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()); + && ((sv_neo_spec_replace_player_bot_enable.GetBool() && IsFakePlayer()) || + (sv_neo_spec_replace_player_afk_enable.GetBool() && IsAFK())); } #ifdef CLIENT_DLL From 8b541cbd23ba72cb3ee3adb161be7c72f4c37110 Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Mon, 4 May 2026 12:55:31 +0100 Subject: [PATCH 18/19] cleanup --- src/game/client/c_baseplayer.cpp | 13 ----- src/game/client/neo/c_neo_player.cpp | 25 ++++++++- src/game/client/neo/c_neo_player.h | 4 +- .../client/neo/ui/neo_hud_context_hint.cpp | 53 +++++++++---------- .../neo/ui/neo_hud_ghost_uplink_state.cpp | 10 ++-- src/game/server/neo/neo_player.cpp | 14 ++--- src/game/shared/basecombatweapon_shared.cpp | 4 -- src/game/shared/neo/weapons/weapon_ghost.cpp | 39 +++++++------- .../neo/weapons/weapon_neobasecombatweapon.h | 1 + 9 files changed, 84 insertions(+), 79 deletions(-) diff --git a/src/game/client/c_baseplayer.cpp b/src/game/client/c_baseplayer.cpp index 8d5dfc880e..21e5f5170f 100644 --- a/src/game/client/c_baseplayer.cpp +++ b/src/game/client/c_baseplayer.cpp @@ -2666,19 +2666,6 @@ 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/neo/c_neo_player.cpp b/src/game/client/neo/c_neo_player.cpp index 13c4ec5f09..ac4de71286 100644 --- a/src/game/client/neo/c_neo_player.cpp +++ b/src/game/client/neo/c_neo_player.cpp @@ -2113,6 +2113,28 @@ C_NEO_Player* C_NEO_Player::PlayerUseTraceLine() return nullptr; } + +//----------------------------------------------------------------------------- +// Purpose: Return true if this object can be +used by the player +//----------------------------------------------------------------------------- +bool C_NEO_Player::IsUseableEntity( CBaseEntity *pEntity, unsigned int requiredCaps ) +{ +#ifdef NEO + if (!pEntity) + return false; + + if (int caps = pEntity->ObjectCaps(); + caps & (FCAP_IMPULSE_USE|FCAP_CONTINUOUS_USE|FCAP_ONOFF_USE|FCAP_DIRECTIONAL_USE) && + (caps & requiredCaps) == requiredCaps) + { + return true; + } + + return false; +#endif // NEO +} + + void C_NEO_Player::PlayerUse() { BaseClass::PlayerUse(); @@ -2123,8 +2145,7 @@ void C_NEO_Player::PlayerUse() if ( (m_afButtonPressed & IN_USE) && prediction->IsFirstTimePredicted() && !GetUseEntity()) { - if (C_NEO_Player* pTargetPlayer = PlayerUseTraceLine(); - pTargetPlayer ) + if (C_NEO_Player* pTargetPlayer = PlayerUseTraceLine()) { m_Local.m_nOldButtons |= IN_USE; m_afButtonPressed &= ~IN_USE; diff --git a/src/game/client/neo/c_neo_player.h b/src/game/client/neo/c_neo_player.h index 5f236b1ebe..8b52a63a92 100644 --- a/src/game/client/neo/c_neo_player.h +++ b/src/game/client/neo/c_neo_player.h @@ -76,7 +76,9 @@ 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 bool IsUseableEntity( CBaseEntity *pEntity, unsigned int requiredCaps ) override final; + virtual CBaseEntity* FindUseEntity() override final; virtual void ModifyFireBulletsDamage(CTakeDamageInfo* dmgInfo) OVERRIDE; 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 b88b08bdc6..7f6e6745b8 100644 --- a/src/game/client/neo/ui/neo_hud_context_hint.cpp +++ b/src/game/client/neo/ui/neo_hud_context_hint.cpp @@ -147,6 +147,7 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() } else { + // Usable entity constexpr float ITEM_DISCOVERY_SMOKE_THRESHOLD = 0.8f; if (CBaseEntity *pUseEntity = pLocalNeoPlayer->FindUseEntity(); pUseEntity && (g_SmokeFogOverlayThermalOverride || g_SmokeFogOverlayAlpha < ITEM_DISCOVERY_SMOKE_THRESHOLD)) @@ -186,35 +187,31 @@ void CNEOHud_ContextHint::UpdateStateForNeoHudElementDraw() m_hUseEntity = INVALID_EHANDLE; } } - else + // Juggernaut hint + else if (CNEO_Juggernaut* pJuggernaut = dynamic_cast(pUseEntity)) { - // Juggernaut hint - if (CNEO_Juggernaut* pJuggernaut = dynamic_cast(pUseEntity); - pJuggernaut) + m_flDisplayEndTime = gpGlobals->curtime + 1.f; + if (pJuggernaut->m_bLocked) { - 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); - } + 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; } - // Some other useable entity else { - V_snwprintf(m_wszHintText, ARRAYSIZE(m_wszHintText), L"[%hs] use", szUppercaseKeyBinding); - m_flDisplayEndTime = gpGlobals->curtime + 1.f; + 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; + } } else { @@ -261,6 +258,7 @@ void SetUseEntityListEntry(int index, CBaseEntity* entity) { if (index < 0 || index >= ARRAYSIZE(useEntityList)) return; + useEntityList[index].entity = entity; useEntityListLastIndex = index; }; @@ -297,20 +295,17 @@ void CNEOHud_ContextHint::DrawNeoHudElement() 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()) { - if (C_BaseEntity* entity = useEntityList[i].entity.Get()) - { - GetVectorInScreenSpace(entity->CollisionProp()->WorldSpaceCenter(), x, y); - vgui::surface()->DrawOutlinedCircle(x, y, size, 12); - } + GetVectorInScreenSpace(entity->CollisionProp()->WorldSpaceCenter(), x, y); + vgui::surface()->DrawOutlinedCircle(x, y, size, 12); } } if (m_flDisplayEndTime <= gpGlobals->curtime) return; - if (cl_neo_hud_context_hint_show_object_interact_hint_in_world.GetBool() && m_hUseEntity.IsValid()) + if (cl_neo_hud_context_hint_show_object_interact_hint_in_world.GetBool()) { if (C_BaseEntity* useEntity = m_hUseEntity.Get()) { diff --git a/src/game/client/neo/ui/neo_hud_ghost_uplink_state.cpp b/src/game/client/neo/ui/neo_hud_ghost_uplink_state.cpp index f2493fa026..5d57c17571 100644 --- a/src/game/client/neo/ui/neo_hud_ghost_uplink_state.cpp +++ b/src/game/client/neo/ui/neo_hud_ghost_uplink_state.cpp @@ -84,13 +84,13 @@ void CNEOHud_GhostUplinkState::DrawNeoHudElement() if (!ShouldDraw()) return; - C_NEO_Player* localPlayer = C_NEO_Player::GetLocalNEOPlayer(); - if (!localPlayer) + C_NEO_Player* pLocalPlayer = C_NEO_Player::GetLocalNEOPlayer(); + if (!pLocalPlayer) return; - C_NEOBaseCombatWeapon* pActiveWeapon = static_cast(localPlayer->GetActiveWeapon()); + C_NEOBaseCombatWeapon* pActiveWeapon = static_cast(pLocalPlayer->GetActiveWeapon()); if ((pActiveWeapon && pActiveWeapon->IsGhost()) || - (sv_neo_ctg_ghost_beacons_when_inactive.GetBool() && (NEORules()->GetGhosterPlayer() == localPlayer->entindex() || localPlayer->IsCarryingGhost()))) + (sv_neo_ctg_ghost_beacons_when_inactive.GetBool() && (NEORules()->GetGhosterPlayer() == pLocalPlayer->entindex() || pLocalPlayer->IsCarryingGhost()))) { if (NEORules()->IsRoundOver()) return; @@ -105,7 +105,7 @@ void CNEOHud_GhostUplinkState::DrawNeoHudElement() surface()->DrawSetTexture(m_pUplinkTextures[textureOrder[m_iCurrentTextureIndex]]); surface()->DrawTexturedRect(0, 0, m_iUplinkTextureWidth, m_iUplinkTextureHeight); } - else if (localPlayer->IsAlive() && localPlayer->GetClass() == NEO_CLASS_JUGGERNAUT) + else if (pLocalPlayer->IsAlive() && pLocalPlayer->GetClass() == NEO_CLASS_JUGGERNAUT) { surface()->DrawSetColor(COLOR_RED); surface()->DrawSetTexture(m_iJGRTexture); diff --git a/src/game/server/neo/neo_player.cpp b/src/game/server/neo/neo_player.cpp index c0cfdbfa3b..01be2b04cd 100644 --- a/src/game/server/neo/neo_player.cpp +++ b/src/game/server/neo/neo_player.cpp @@ -513,23 +513,23 @@ CON_COMMAND_F(useplayer, "+use on a player", FCVAR_USERINFO) if (args.ArgC() < 2) return; - auto player = static_cast(UTIL_GetCommandClient()); - if (!player) + auto pPlayer = static_cast(UTIL_GetCommandClient()); + if (!pPlayer) return; - CNEO_Player* pTargetPlayer = ToNEOPlayer(UTIL_PlayerByIndex(atoi(args[1]))); - if ( pTargetPlayer && pTargetPlayer->IsBot()) + if (CNEO_Player* pTargetPlayer = ToNEOPlayer(UTIL_PlayerByIndex(atoi(args[1]))); + pTargetPlayer && pTargetPlayer->IsBot()) { if (sv_neo_bot_cmdr_enable.GetBool()) { - pTargetPlayer->ToggleBotFollowCommander( player ); + pTargetPlayer->ToggleBotFollowCommander( pPlayer ); // TODO: Do we want to allow using players for some kind of communication? } - else if (NEORules()->IsTeamplay() && pTargetPlayer->GetTeamNumber() == player->GetTeamNumber()) + else if (NEORules()->IsTeamplay() && pTargetPlayer->GetTeamNumber() == pPlayer->GetTeamNumber()) { // Alt: Triggers throwing primary weapon to user // see neo_bot_scenario_monitor for behavior transition - pTargetPlayer->m_hCommandingPlayer = player; + pTargetPlayer->m_hCommandingPlayer = pPlayer; } } } diff --git a/src/game/shared/basecombatweapon_shared.cpp b/src/game/shared/basecombatweapon_shared.cpp index 17296e9ca1..e72e024be8 100644 --- a/src/game/shared/basecombatweapon_shared.cpp +++ b/src/game/shared/basecombatweapon_shared.cpp @@ -228,11 +228,7 @@ 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/neo/weapons/weapon_ghost.cpp b/src/game/shared/neo/weapons/weapon_ghost.cpp index 550aae00d2..ffe007fbaa 100644 --- a/src/game/shared/neo/weapons/weapon_ghost.cpp +++ b/src/game/shared/neo/weapons/weapon_ghost.cpp @@ -95,28 +95,33 @@ void CWeaponGhost::OnDataChanged(DataUpdateType_t updateType) { if (updateType == DATA_UPDATE_DATATABLE_CHANGED && m_iOldState != m_iState) { - if (!IsCarriedByLocalPlayer()) + // Ghost startup sound { - StopGhostSound(); - } - else - { - if (sv_neo_ctg_ghost_beacons_when_inactive.GetBool()) + // Could be more than one ghost on the map, check whether localplayer is carrying the ghost too + C_NEO_Player* pLocalPlayer = C_NEO_Player::GetLocalNEOPlayer(); + if (!IsCarriedByLocalPlayer() && !pLocalPlayer->IsCarryingGhost()) { - if (m_iState == WEAPON_NOT_CARRIED) - StopGhostSound(); - else if (m_iOldState == WEAPON_NOT_CARRIED) - PlayGhostSound(); + StopGhostSound(); } else { - if (m_iState == WEAPON_IS_ACTIVE) - PlayGhostSound(); + if (sv_neo_ctg_ghost_beacons_when_inactive.GetBool()) + { + if (m_iState == WEAPON_NOT_CARRIED) + StopGhostSound(); + else if (m_iOldState == WEAPON_NOT_CARRIED) + PlayGhostSound(); + } else - StopGhostSound(); + { + if (m_iState == WEAPON_IS_ACTIVE) + PlayGhostSound(); + else + StopGhostSound(); + } } + // NEO TODO (Adam) if round is over, stop the ghost sound too? } - // NEO TODO (Adam) if round is over, stop the ghost sound too? } BaseClass::OnDataChanged(updateType); @@ -124,11 +129,9 @@ void CWeaponGhost::OnDataChanged(DataUpdateType_t updateType) void CWeaponGhost::PlayGhostSound() { - C_BasePlayer* owner = static_cast(GetOwner()); - if (!owner) - { + C_BasePlayer* pOwner = static_cast(GetOwner()); + if (!pOwner) return; - } CLocalPlayerFilter filter; EmitSound_t soundParams; diff --git a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h index 089d216528..be1732aace 100644 --- a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h +++ b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h @@ -142,6 +142,7 @@ class CNEOBaseCombatWeapon : public CBaseHL2MPCombatWeapon virtual bool CanBeSelected(void) override; virtual bool IsFollowingEntity() override { + // NEO NOTE (Adam) stops non-active weapons attached to players from showing a pickup hint, also should fix position of attached weapons where they were previously interpolated separate to the owner instead of always being attached to the owner return (GetMoveType() == MOVETYPE_NONE) && GetMoveParent(); }; virtual int ObjectCaps(void) override From 0c741100047bb36b886a6347829f6a5eff34f6a2 Mon Sep 17 00:00:00 2001 From: AdamTadeusz Date: Mon, 4 May 2026 14:01:00 +0100 Subject: [PATCH 19/19] return weapon trigger bounds, force use in sphere for weapons with the flag --- src/game/shared/neo/neo_player_shared.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/game/shared/neo/neo_player_shared.cpp b/src/game/shared/neo/neo_player_shared.cpp index fe96d73046..b30e0eba2b 100644 --- a/src/game/shared/neo/neo_player_shared.cpp +++ b/src/game/shared/neo/neo_player_shared.cpp @@ -526,13 +526,13 @@ CBaseEntity *CNEO_Player::FindUseEntity() bUsable = IsUseableEntity(pObject, 0); } - if ( bUsable ) + if ( bUsable && !pObject->IsBaseCombatWeapon() ) { 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 ) + float dist = delta.LengthSqr(); + if ( dist < (PLAYER_USE_RADIUS * PLAYER_USE_RADIUS) ) { #ifndef CLIENT_DLL if ( sv_debug_player_use.GetBool() ) @@ -556,8 +556,7 @@ CBaseEntity *CNEO_Player::FindUseEntity() 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())) + if (pObject) { #ifdef CLIENT_DLL // Client side do the sphere query anyway to show adjacent items @@ -599,6 +598,10 @@ CBaseEntity *CNEO_Player::FindUseEntity() if ( !pObject ) continue; + // Weapons have very inflated trigger bounds which, if any part of is in the search sphere, get added to the query. This prevents weapons from being picked up from very far away + if (pObject->IsBaseCombatWeapon() && pObject->CollisionProp()->CalcDistanceFromPoint(searchCenter) > PLAYER_USE_RADIUS) + continue; + #ifdef CLIENT_DLL Vector point; pObject->CollisionProp()->CalcNearestPoint( searchCenter, &point ); @@ -639,7 +642,6 @@ CBaseEntity *CNEO_Player::FindUseEntity() 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 ) ); }