From 7a2c6e787b720a0fa1b5efdeb5be0868f4d80a2f Mon Sep 17 00:00:00 2001 From: Rain Date: Fri, 11 Sep 2026 01:47:22 +0300 Subject: [PATCH 1/7] Refactor old superjump spaghetti --- src/game/client/neo/c_neo_player.cpp | 43 +------------------ src/game/client/neo/c_neo_player.h | 5 --- src/game/server/neo/neo_player.cpp | 55 +------------------------ src/game/server/neo/neo_player.h | 4 -- src/game/shared/neo/neo_player_shared.h | 4 -- 5 files changed, 4 insertions(+), 107 deletions(-) diff --git a/src/game/client/neo/c_neo_player.cpp b/src/game/client/neo/c_neo_player.cpp index 5bfc8bd7c..d46cedec1 100644 --- a/src/game/client/neo/c_neo_player.cpp +++ b/src/game/client/neo/c_neo_player.cpp @@ -88,7 +88,6 @@ IMPLEMENT_CLIENTCLASS_DT(C_NEO_Player, DT_NEO_Player, CNEO_Player) RecvPropBool(RECVINFO(m_bInThermOpticCamo)), RecvPropBool(RECVINFO(m_bLastTickInThermOpticCamo)), RecvPropBool(RECVINFO(m_bInVision)), - RecvPropBool(RECVINFO(m_bHasBeenAirborneForTooLongToSuperJump)), RecvPropBool(RECVINFO(m_bInAim)), RecvPropBool(RECVINFO(m_bIneligibleForLoadoutPick)), RecvPropBool(RECVINFO(m_bCarryingGhost)), @@ -121,7 +120,6 @@ BEGIN_PREDICTION_DATA(C_NEO_Player) DEFINE_PRED_FIELD(m_bInAim, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE), DEFINE_PRED_FIELD(m_bInLean, FIELD_INTEGER, FTYPEDESC_INSENDTABLE), DEFINE_PRED_FIELD(m_bInVision, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE), - DEFINE_PRED_FIELD(m_bHasBeenAirborneForTooLongToSuperJump, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE), DEFINE_PRED_FIELD_TOL(m_flVisionLastTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE, TD_MSECTOLERANCE), DEFINE_PRED_FIELD_TOL(m_flJumpLastTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE, TD_MSECTOLERANCE), @@ -451,7 +449,6 @@ C_NEO_Player::C_NEO_Player() m_iXP.GetForModify() = 0; m_bInThermOpticCamo = m_bInVision = false; - m_bHasBeenAirborneForTooLongToSuperJump = false; m_bInAim = false; m_bCarryingGhost = false; m_bIneligibleForLoadoutPick = false; @@ -459,8 +456,6 @@ C_NEO_Player::C_NEO_Player() m_flCamoAuxLastTime = 0; m_flVisionLastTime = 0; - m_flLastAirborneJumpOkTime = 0; - m_flLastSuperJumpTime = 0; m_bFirstAliveTick = true; m_bFirstDeathTick = true; @@ -1170,27 +1165,6 @@ void C_NEO_Player::PreThink( void ) Lean(); } - // Eek. See rationale for this thing in CNEO_Player::PreThink - if (IsAirborne()) - { - m_flLastAirborneJumpOkTime = gpGlobals->curtime; - const float deltaTime = gpGlobals->curtime - m_flLastAirborneJumpOkTime; - const float leeway = 0.5f; - if (deltaTime > leeway) - { - m_bHasBeenAirborneForTooLongToSuperJump = false; - m_flLastAirborneJumpOkTime = gpGlobals->curtime; - } - else - { - m_bHasBeenAirborneForTooLongToSuperJump = true; - } - } - else - { - m_bHasBeenAirborneForTooLongToSuperJump = false; - } - if (m_iNeoClass == NEO_CLASS_RECON && (m_afButtonPressed & IN_JUMP) && (m_nButtons & IN_SPEED) && IsAllowedToSuperJump()) @@ -1477,11 +1451,10 @@ bool C_NEO_Player::IsAllowedToSuperJump(void) if (GetMoveParent()) return false; - if (IsPlayerUnderwater()) + if (GetWaterLevel() >= WL_Waist) return false; - // Can't superjump whilst airborne (although it is kind of cool) - if (m_bHasBeenAirborneForTooLongToSuperJump) + if (IsAirborne()) return false; // Only superjump if we have a reasonable jump direction in mind @@ -1495,16 +1468,6 @@ bool C_NEO_Player::IsAllowedToSuperJump(void) if (m_HL2Local.m_flSuitPower < SUPER_JMP_COST) return false; - if (SUPER_JMP_DELAY_BETWEEN_JUMPS > 0) - { - m_flLastSuperJumpTime = gpGlobals->curtime; - const float deltaTime = gpGlobals->curtime - m_flLastSuperJumpTime; - if (deltaTime > SUPER_JMP_DELAY_BETWEEN_JUMPS) - return false; - - m_flLastSuperJumpTime = gpGlobals->curtime; - } - return true; } @@ -1981,8 +1944,6 @@ void C_NEO_Player::CSpectatorTakeoverPlayerUpdate(C_NEO_Player* pPlayerTakeoverT m_flCamoAuxLastTime = pPlayerTakeoverTarget->m_flCamoAuxLastTime; m_flVisionLastTime = pPlayerTakeoverTarget->m_flVisionLastTime; - m_flLastAirborneJumpOkTime = pPlayerTakeoverTarget->m_flLastAirborneJumpOkTime; - m_flLastSuperJumpTime = pPlayerTakeoverTarget->m_flLastSuperJumpTime; m_bPreviouslyReloading = pPlayerTakeoverTarget->m_bPreviouslyReloading; m_bLastTickInThermOpticCamo = pPlayerTakeoverTarget->m_bLastTickInThermOpticCamo; m_flTocFactor = pPlayerTakeoverTarget->m_flTocFactor; diff --git a/src/game/client/neo/c_neo_player.h b/src/game/client/neo/c_neo_player.h index c5d7015b8..72d003d18 100644 --- a/src/game/client/neo/c_neo_player.h +++ b/src/game/client/neo/c_neo_player.h @@ -222,8 +222,6 @@ class C_NEO_Player : public C_HL2MP_Player CNetworkVar(int, m_iXP); CNetworkVar(int, m_iLoadoutWepChoice); CNetworkVar(int, m_iNextSpawnClassChoice); - - CNetworkVar(bool, m_bHasBeenAirborneForTooLongToSuperJump); CNetworkVar(float, m_flCamoAuxLastTime); CNetworkVar(float, m_flVisionLastTime); @@ -264,9 +262,6 @@ class C_NEO_Player : public C_HL2MP_Player bool m_bPreviouslyReloading; bool m_bSpecRefreshedStates; - float m_flLastAirborneJumpOkTime; - float m_flLastSuperJumpTime; - float m_flTocFactor; // Cloak strength, controls tint, refraction amount. Lower values make player more difficult to spot // Non-network version of m_szNeoName with dupe checker index diff --git a/src/game/server/neo/neo_player.cpp b/src/game/server/neo/neo_player.cpp index 17766eeda..19ca313a0 100644 --- a/src/game/server/neo/neo_player.cpp +++ b/src/game/server/neo/neo_player.cpp @@ -63,7 +63,6 @@ SendPropEHandle(SENDINFO(m_hCommandingPlayer)), SendPropBool(SENDINFO(m_bInThermOpticCamo)), SendPropBool(SENDINFO(m_bLastTickInThermOpticCamo)), SendPropBool(SENDINFO(m_bInVision)), -SendPropBool(SENDINFO(m_bHasBeenAirborneForTooLongToSuperJump)), SendPropBool(SENDINFO(m_bShowTestMessage)), SendPropBool(SENDINFO(m_bInAim)), SendPropBool(SENDINFO(m_bIneligibleForLoadoutPick)), @@ -105,7 +104,6 @@ DEFINE_FIELD(m_bInLean, FIELD_INTEGER), DEFINE_FIELD(m_bInThermOpticCamo, FIELD_BOOLEAN), DEFINE_FIELD(m_bLastTickInThermOpticCamo, FIELD_BOOLEAN), DEFINE_FIELD(m_bInVision, FIELD_BOOLEAN), -DEFINE_FIELD(m_bHasBeenAirborneForTooLongToSuperJump, FIELD_BOOLEAN), DEFINE_FIELD(m_bShowTestMessage, FIELD_BOOLEAN), DEFINE_FIELD(m_bInAim, FIELD_BOOLEAN), @@ -584,7 +582,6 @@ CNEO_Player::CNEO_Player() V_memset(m_szNeoCrosshair.GetForModify(), 0, sizeof(m_szNeoCrosshair)); m_bInThermOpticCamo = m_bInVision = false; - m_bHasBeenAirborneForTooLongToSuperJump = false; m_bInAim = false; m_bCarryingGhost = false; m_bInLean = NEO_LEAN_NONE; @@ -597,11 +594,8 @@ CNEO_Player::CNEO_Player() m_flCamoAuxLastTime = 0; m_flVisionLastTime = 0; - m_flLastAirborneJumpOkTime = 0; - m_flLastSuperJumpTime = 0; m_botThermOpticCamoDisruptedTimer.Invalidate(); - m_bFirstDeathTick = true; m_bCorpseSet = false; m_bPreviouslyReloading = false; @@ -1149,37 +1143,6 @@ void CNEO_Player::PreThink(void) Lean(); } - // NEO HACK (Rain): Just bodging together a check for if we're allowed - // to superjump, or if we've been airborne too long for that. - // Ideally this should get cleaned up and moved to wherever - // the regular engine jump does a similar check. - bool newNetAirborneVal; - if (IsAirborne()) - { - m_flLastAirborneJumpOkTime = gpGlobals->curtime; - const float deltaTime = gpGlobals->curtime - m_flLastAirborneJumpOkTime; - const float leeway = 0.5f; - if (deltaTime > leeway) - { - newNetAirborneVal = false; - m_flLastAirborneJumpOkTime = gpGlobals->curtime; - } - else - { - newNetAirborneVal = true; - } - } - else - { - newNetAirborneVal = false; - } - // Only send the network update if we actually changed state - if (m_bHasBeenAirborneForTooLongToSuperJump != newNetAirborneVal) - { - m_bHasBeenAirborneForTooLongToSuperJump = newNetAirborneVal; - NetworkStateChanged(); - } - if (m_iNeoClass == NEO_CLASS_RECON && (m_afButtonPressed & IN_JUMP) && (m_nButtons & IN_SPEED) && IsAllowedToSuperJump()) @@ -1677,11 +1640,10 @@ bool CNEO_Player::IsAllowedToSuperJump(void) if (GetMoveParent()) return false; - if (IsPlayerUnderwater()) + if (GetWaterLevel() >= WL_Waist) return false; - // Can't superjump whilst airborne (although it is kind of cool) - if (m_bHasBeenAirborneForTooLongToSuperJump) + if (IsAirborne()) return false; // Only superjump if we have a reasonable jump direction in mind @@ -1694,16 +1656,6 @@ bool CNEO_Player::IsAllowedToSuperJump(void) if (SuitPower_GetCurrentPercentage() < SUPER_JMP_COST) return false; - if (SUPER_JMP_DELAY_BETWEEN_JUMPS > 0) - { - m_flLastSuperJumpTime = gpGlobals->curtime; - const float deltaTime = gpGlobals->curtime - m_flLastSuperJumpTime; - if (deltaTime > SUPER_JMP_DELAY_BETWEEN_JUMPS) - return false; - - m_flLastSuperJumpTime = gpGlobals->curtime; - } - return true; } @@ -4294,14 +4246,11 @@ void CNEO_Player::SpectatorTakeoverPlayerPreThink() m_HL2Local.m_flSuitPower = pPlayerTakeoverTarget->m_HL2Local.m_flSuitPower; m_bInThermOpticCamo = pPlayerTakeoverTarget->m_bInThermOpticCamo; - m_bHasBeenAirborneForTooLongToSuperJump = pPlayerTakeoverTarget->m_bHasBeenAirborneForTooLongToSuperJump; m_bInAim = pPlayerTakeoverTarget->m_bInAim; Weapon_SetZoom(pPlayerTakeoverTarget->m_bInAim); m_bCarryingGhost = pPlayerTakeoverTarget->m_bCarryingGhost; m_bInLean = pPlayerTakeoverTarget->m_bInLean; m_flCamoAuxLastTime = pPlayerTakeoverTarget->m_flCamoAuxLastTime; - m_flLastAirborneJumpOkTime = pPlayerTakeoverTarget->m_flLastAirborneJumpOkTime; - m_flLastSuperJumpTime = pPlayerTakeoverTarget->m_flLastSuperJumpTime; m_botThermOpticCamoDisruptedTimer.Invalidate(); // taken over by player m_bFirstDeathTick = pPlayerTakeoverTarget->m_bFirstDeathTick; m_bCorpseSet = pPlayerTakeoverTarget->m_bCorpseSet; diff --git a/src/game/server/neo/neo_player.h b/src/game/server/neo/neo_player.h index dd603fda4..ed25f921c 100644 --- a/src/game/server/neo/neo_player.h +++ b/src/game/server/neo/neo_player.h @@ -292,7 +292,6 @@ class CNEO_Player : public CHL2MP_Player CNetworkVar(bool, m_bInThermOpticCamo); CNetworkVar(bool, m_bLastTickInThermOpticCamo); CNetworkVar(bool, m_bInVision); - CNetworkVar(bool, m_bHasBeenAirborneForTooLongToSuperJump); CNetworkVar(bool, m_bInAim); CNetworkVar(int, m_bInLean); CNetworkVar(bool, m_bCarryingGhost); @@ -349,9 +348,6 @@ class CNEO_Player : public CHL2MP_Player bool m_bPreviouslyReloading; bool m_bNeoNameHasSet; - float m_flLastAirborneJumpOkTime; - float m_flLastSuperJumpTime; - // Non-network version of m_szNeoName with dupe checker index mutable char m_szNeoNameWDupeIdx[MAX_PLAYER_NAME_LENGTH + 10]; mutable bool m_szNeoNameWDupeIdxNeedUpdate; diff --git a/src/game/shared/neo/neo_player_shared.h b/src/game/shared/neo/neo_player_shared.h index 9e621e937..95e27e087 100644 --- a/src/game/shared/neo/neo_player_shared.h +++ b/src/game/shared/neo/neo_player_shared.h @@ -177,10 +177,6 @@ COMPILE_TIME_ASSERT(NEO_ASSAULT_CROUCH_SPEED == NEO_VIP_CROUCH_SPEED); #define MIN_CLOAK_AUX 0.1f #define SPRINT_START_MIN (2.0f) -// Original NT allows chaining superjumps up ramps, -// so leaving this zeroed for enabling movement tricks. -#define SUPER_JMP_DELAY_BETWEEN_JUMPS 0 - // NEO Activities #define ACT_NEO_ATTACK ACT_RANGE_ATTACK1 #define ACT_NEO_RELOAD ACT_RELOAD From 785a2bb3046f72cf5f4e3059bfa71a62aaf72622 Mon Sep 17 00:00:00 2001 From: Rain Date: Fri, 11 Sep 2026 01:54:23 +0300 Subject: [PATCH 2/7] Refactor IsAllowedToSuperJump Move to neo_player_shared --- src/game/client/neo/c_neo_player.cpp | 31 --------------------- src/game/server/neo/neo_player.cpp | 34 ----------------------- src/game/shared/neo/neo_player_shared.cpp | 34 +++++++++++++++++++++++ 3 files changed, 34 insertions(+), 65 deletions(-) diff --git a/src/game/client/neo/c_neo_player.cpp b/src/game/client/neo/c_neo_player.cpp index d46cedec1..c9ac8fa0d 100644 --- a/src/game/client/neo/c_neo_player.cpp +++ b/src/game/client/neo/c_neo_player.cpp @@ -1440,37 +1440,6 @@ void C_NEO_Player::UpdateGlowEffects(int iNewTeam) } #endif // GLOWS_ENABLE -bool C_NEO_Player::IsAllowedToSuperJump(void) -{ - if (!IsSprinting()) - return false; - - if (IsCarryingGhost()) - return false; - - if (GetMoveParent()) - return false; - - if (GetWaterLevel() >= WL_Waist) - return false; - - if (IsAirborne()) - return false; - - // Only superjump if we have a reasonable jump direction in mind - // NEO TODO (Rain): should we support sideways superjumping? - if ((m_nButtons & (IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT)) == 0) - { - return false; - } - - // The suit check is for prediction only, actual power drain happens serverside - if (m_HL2Local.m_flSuitPower < SUPER_JMP_COST) - return false; - - return true; -} - // This is applied for prediction purposes. It should match CNEO_Player's method. void C_NEO_Player::SuperJump(void) { diff --git a/src/game/server/neo/neo_player.cpp b/src/game/server/neo/neo_player.cpp index 19ca313a0..edb06af51 100644 --- a/src/game/server/neo/neo_player.cpp +++ b/src/game/server/neo/neo_player.cpp @@ -1625,40 +1625,6 @@ void CNEO_Player::SuperJump(void) ApplyAbsVelocityImpulse(forward * boostIntensity); } -bool CNEO_Player::IsAllowedToSuperJump(void) -{ - // NEOJANK: Bots are exempt from certain checks due to their their erratic input control - if (!IsBot()) - { - if (!IsSprinting()) - return false; - } - - if (IsCarryingGhost()) - return false; - - if (GetMoveParent()) - return false; - - if (GetWaterLevel() >= WL_Waist) - return false; - - if (IsAirborne()) - return false; - - // Only superjump if we have a reasonable jump direction in mind - // NEO TODO (Rain): should we support sideways superjumping? - if ((m_nButtons & (IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT)) == 0) - { - return false; - } - - if (SuitPower_GetCurrentPercentage() < SUPER_JMP_COST) - return false; - - return true; -} - void CNEO_Player::PostThink(void) { BaseClass::PostThink(); diff --git a/src/game/shared/neo/neo_player_shared.cpp b/src/game/shared/neo/neo_player_shared.cpp index 2f1b1a5f7..a953a35dc 100644 --- a/src/game/shared/neo/neo_player_shared.cpp +++ b/src/game/shared/neo/neo_player_shared.cpp @@ -884,3 +884,37 @@ bool CNEO_Player::TestHitboxes(const Ray_t& ray, unsigned int fContentsMask, tra return true; } + +bool CNEO_Player::IsAllowedToSuperJump() +{ + // NEOJANK: Bots are exempt from certain checks due to their their erratic input control + if (!IsBot()) + { + if (!IsSprinting()) + return false; + } + + if (IsCarryingGhost()) + return false; + + if (GetMoveParent()) + return false; + + if (GetWaterLevel() >= WL_Waist) + return false; + + if (IsAirborne()) + return false; + + // Only superjump if we have a reasonable jump direction in mind + // NEO TODO (Rain): should we support sideways superjumping? + if ((m_nButtons & (IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT)) == 0) + { + return false; + } + + if (SuitPower_GetCurrentPercentage() < SUPER_JMP_COST) + return false; + + return true; +} From 9c0b04f52b6774dd00e52bd84fe7b9bcec2ae1a0 Mon Sep 17 00:00:00 2001 From: Rain Date: Fri, 11 Sep 2026 02:23:53 +0300 Subject: [PATCH 3/7] Hacky fix for the water jump case Manually verify ground entity presence for the water jump case. This fixes the issue with waterjumping, but doesn't address the underlaying problem, which seems to be that we spawn with stale FL_ONGROUND value, which causes IsAllowedToSuperJump to incorrectly pass the IsAirborne check. This also means that one can superjump upon spawning for non-freezetimed rounds (like warmup), if they are fast enough to do the superjump before landing. Will address the actual problem in next commit. --- src/game/shared/neo/neo_player_shared.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/game/shared/neo/neo_player_shared.cpp b/src/game/shared/neo/neo_player_shared.cpp index a953a35dc..f03f58e67 100644 --- a/src/game/shared/neo/neo_player_shared.cpp +++ b/src/game/shared/neo/neo_player_shared.cpp @@ -900,12 +900,25 @@ bool CNEO_Player::IsAllowedToSuperJump() if (GetMoveParent()) return false; - if (GetWaterLevel() >= WL_Waist) - return false; - if (IsAirborne()) return false; + if (GetFlags() & FL_INWATER) + { + if (GetWaterLevel() >= WL_Waist) // the cheap case + return false; + + const Vector& start = GetAbsOrigin(); + Vector end(start.x, start.y, start.z - 64); + Ray_t ray; + ray.Init(start, end, GetPlayerMins(), GetPlayerMaxs()); + trace_t trace; + UTIL_TraceRay(ray, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace); + const bool foundGround = trace.DidHit(); + if (!foundGround) + return false; + } + // Only superjump if we have a reasonable jump direction in mind // NEO TODO (Rain): should we support sideways superjumping? if ((m_nButtons & (IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT)) == 0) From 1757243fb4692f40c3d9d9c9b5de1980f47fcf47 Mon Sep 17 00:00:00 2001 From: Rain Date: Fri, 11 Sep 2026 02:38:23 +0300 Subject: [PATCH 4/7] Fix FL_ONGROUND set incorrectly on Spawn BaseClass of server CNEO_Player class will set FL_ONGROUND unconditionally at Spawn. This is not necessarily true, and will fail most notably for the case where the player spawns in and above a pool of water. Having the FL_ONGROUND be incorrect will then subsequently break checks for Airborne-ness for the recon superjump, effectively allowing the recon to superjump from above the surface of the water, until they first land on a true ground surface to sync their ONGROUND flag with reality. Another bugged case would be spawning high in the air, during warmup or mid-round where there might be no freezetime, whereby the recon could superjump mid-air if they were quick enough to do it before touching the floor. Client side seems to not set the FL_ONGROUND flag in this way, so the fix is only needed server side. Also reverting the previous 9c0b04f52 hotfix because this commit obsoletes it. --- src/game/client/neo/c_neo_player.cpp | 1 + src/game/client/neo/c_neo_player.h | 1 + src/game/server/neo/neo_player.cpp | 1 + src/game/server/neo/neo_player.h | 1 + src/game/shared/neo/neo_player_shared.cpp | 40 ++++++++++++++--------- 5 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/game/client/neo/c_neo_player.cpp b/src/game/client/neo/c_neo_player.cpp index c9ac8fa0d..0b4096cbb 100644 --- a/src/game/client/neo/c_neo_player.cpp +++ b/src/game/client/neo/c_neo_player.cpp @@ -1483,6 +1483,7 @@ float C_NEO_Player::CloakPower_CurrentVisualPercentage(void) const void C_NEO_Player::Spawn( void ) { BaseClass::Spawn(); + FixupOnGroundFlag(); m_bLastTickInThermOpticCamo = m_bInThermOpticCamo = false; m_flCamoAuxLastTime = 0; diff --git a/src/game/client/neo/c_neo_player.h b/src/game/client/neo/c_neo_player.h index 72d003d18..60464ea78 100644 --- a/src/game/client/neo/c_neo_player.h +++ b/src/game/client/neo/c_neo_player.h @@ -205,6 +205,7 @@ class C_NEO_Player : public C_HL2MP_Player void CheckLeanButtons(); void PlayCloakSound(); void SetCloakState(bool state); + void FixupOnGroundFlag(); bool IsAllowedToSuperJump(void); diff --git a/src/game/server/neo/neo_player.cpp b/src/game/server/neo/neo_player.cpp index edb06af51..8de413763 100644 --- a/src/game/server/neo/neo_player.cpp +++ b/src/game/server/neo/neo_player.cpp @@ -716,6 +716,7 @@ void CNEO_Player::Spawn(void) } BaseClass::Spawn(); + FixupOnGroundFlag(); SetMaxHealth(MAX_HEALTH_FOR_CLASS[m_iNeoClass]); SetHealth(GetMaxHealth()); diff --git a/src/game/server/neo/neo_player.h b/src/game/server/neo/neo_player.h index ed25f921c..c146e9a7d 100644 --- a/src/game/server/neo/neo_player.h +++ b/src/game/server/neo/neo_player.h @@ -266,6 +266,7 @@ class CNEO_Player : public CHL2MP_Player void CheckLeanButtons(); void PlayCloakSound(bool removeLocalPlayer = true); void SetCloakState(bool state); + void FixupOnGroundFlag(); public: CNetworkVar(int, m_iNeoClass); diff --git a/src/game/shared/neo/neo_player_shared.cpp b/src/game/shared/neo/neo_player_shared.cpp index f03f58e67..626ff0232 100644 --- a/src/game/shared/neo/neo_player_shared.cpp +++ b/src/game/shared/neo/neo_player_shared.cpp @@ -903,21 +903,9 @@ bool CNEO_Player::IsAllowedToSuperJump() if (IsAirborne()) return false; - if (GetFlags() & FL_INWATER) - { - if (GetWaterLevel() >= WL_Waist) // the cheap case - return false; - - const Vector& start = GetAbsOrigin(); - Vector end(start.x, start.y, start.z - 64); - Ray_t ray; - ray.Init(start, end, GetPlayerMins(), GetPlayerMaxs()); - trace_t trace; - UTIL_TraceRay(ray, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace); - const bool foundGround = trace.DidHit(); - if (!foundGround) - return false; - } + constexpr auto parityHighestAllowedWaterLevel = WL_Feet; + if (GetWaterLevel() > parityHighestAllowedWaterLevel) + return false; // Only superjump if we have a reasonable jump direction in mind // NEO TODO (Rain): should we support sideways superjumping? @@ -931,3 +919,25 @@ bool CNEO_Player::IsAllowedToSuperJump() return true; } + +// BaseClass::Spawn sets FL_ONGROUND for us, which is convenient but not guaranteed correct, +// for example if spawning in/above a body of water. This matters especially for the recon +// superjump validity check. So let's check if we've actually got a ground or not. +// Another case that goes out of whack without this fixup is being able to superjump mid-air +// when spawning into a warmup/non-freezetime'd match, before hitting the floor for the first time. +void CNEO_Player::FixupOnGroundFlag() +{ + // Assert that the client side baseclass doesn't do this (and therefore we need do nothing for it) + Assert((GetFlags() & FL_ONGROUND) == IsServer()); +#ifdef GAME_DLL + const Vector& start = GetAbsOrigin(); + Vector end(start.x, start.y, start.z - 64); + Ray_t ray; + ray.Init(start, end, GetPlayerMins(), GetPlayerMaxs()); + trace_t trace; + UTIL_TraceRay(ray, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace); + const bool foundGround = trace.DidHit(); + if (!foundGround) + RemoveFlag(FL_ONGROUND); // not actually on ground, remove it! +#endif +} From 15344392ec975843dfe58cb9c5e518908f27e7d5 Mon Sep 17 00:00:00 2001 From: Rain Date: Fri, 11 Sep 2026 03:07:32 +0300 Subject: [PATCH 5/7] Fix hardcoded player height Height can differ by class (supports) --- src/game/shared/neo/neo_player_shared.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/game/shared/neo/neo_player_shared.cpp b/src/game/shared/neo/neo_player_shared.cpp index 626ff0232..57b97a74f 100644 --- a/src/game/shared/neo/neo_player_shared.cpp +++ b/src/game/shared/neo/neo_player_shared.cpp @@ -931,9 +931,12 @@ void CNEO_Player::FixupOnGroundFlag() Assert((GetFlags() & FL_ONGROUND) == IsServer()); #ifdef GAME_DLL const Vector& start = GetAbsOrigin(); - Vector end(start.x, start.y, start.z - 64); + const Vector& maxs = GetPlayerMaxs(); + const Vector& mins = GetPlayerMins(); + float playerHeight = maxs.z - mins.z; + Vector end(start.x, start.y, start.z - playerHeight); Ray_t ray; - ray.Init(start, end, GetPlayerMins(), GetPlayerMaxs()); + ray.Init(start, end, mins, maxs); trace_t trace; UTIL_TraceRay(ray, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace); const bool foundGround = trace.DidHit(); From 887ec8d0ab6f35744d23a25447ff3f03902ca2cf Mon Sep 17 00:00:00 2001 From: Rain Date: Fri, 11 Sep 2026 03:42:39 +0300 Subject: [PATCH 6/7] optimize: skip spectators' fixup because this only matters for players --- src/game/shared/neo/neo_player_shared.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/game/shared/neo/neo_player_shared.cpp b/src/game/shared/neo/neo_player_shared.cpp index 57b97a74f..0ad4206b7 100644 --- a/src/game/shared/neo/neo_player_shared.cpp +++ b/src/game/shared/neo/neo_player_shared.cpp @@ -930,6 +930,8 @@ void CNEO_Player::FixupOnGroundFlag() // Assert that the client side baseclass doesn't do this (and therefore we need do nothing for it) Assert((GetFlags() & FL_ONGROUND) == IsServer()); #ifdef GAME_DLL + if (GetTeamNumber() < FIRST_GAME_TEAM) // if it's not a player, don't bother + return; const Vector& start = GetAbsOrigin(); const Vector& maxs = GetPlayerMaxs(); const Vector& mins = GetPlayerMins(); From 98d41a54e0b6c14e017527a5ad1ac049fa7241af Mon Sep 17 00:00:00 2001 From: Rain Date: Sat, 12 Sep 2026 23:59:39 +0300 Subject: [PATCH 7/7] refactor --- src/game/shared/neo/neo_player_shared.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/game/shared/neo/neo_player_shared.cpp b/src/game/shared/neo/neo_player_shared.cpp index 0ad4206b7..4ee62dc18 100644 --- a/src/game/shared/neo/neo_player_shared.cpp +++ b/src/game/shared/neo/neo_player_shared.cpp @@ -903,8 +903,7 @@ bool CNEO_Player::IsAllowedToSuperJump() if (IsAirborne()) return false; - constexpr auto parityHighestAllowedWaterLevel = WL_Feet; - if (GetWaterLevel() > parityHighestAllowedWaterLevel) + if (GetWaterLevel() > WL_Feet) return false; // Only superjump if we have a reasonable jump direction in mind