From 6382bfc13e2f9ebb5b9652cd4f67fa4e9193b945 Mon Sep 17 00:00:00 2001 From: Tater Date: Mon, 9 Dec 2024 21:45:36 -0600 Subject: [PATCH] Improved Antiping Smoothing --- src/engine/shared/tater_variables.h | 2 + src/game/client/components/debughud.cpp | 56 ++++- src/game/client/components/debughud.h | 1 + src/game/client/components/players.cpp | 3 +- .../components/tclient/menus_tclient.cpp | 2 +- src/game/client/gameclient.cpp | 213 +++++++++++++++++- src/game/client/gameclient.h | 10 + .../client/prediction/entities/character.h | 2 + 8 files changed, 282 insertions(+), 7 deletions(-) diff --git a/src/engine/shared/tater_variables.h b/src/engine/shared/tater_variables.h index e2a2668ea47..1462b282fc9 100644 --- a/src/engine/shared/tater_variables.h +++ b/src/engine/shared/tater_variables.h @@ -57,6 +57,8 @@ MACRO_CONFIG_INT(ClShowSkinName, tc_skin_name, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG MACRO_CONFIG_INT(ClFastInput, tc_fast_input, 0, 0, 5, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Uses input for prediction up to 20ms faster") MACRO_CONFIG_INT(ClFastInputOthers, tc_fast_input_others, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Do an extra 1 tick (20ms) for other tees with your fast inputs. (increases visual latency, makes dragging easier)") +MACRO_CONFIG_INT(ClAntiPingImproved, tc_antiping_improved, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Different antiping smoothing algorithm, not compatible with cl_antiping_smooth") + MACRO_CONFIG_INT(ClColorFreeze, tc_color_freeze, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Use skin colors for frozen tees") MACRO_CONFIG_INT(ClColorFreezeDarken, tc_color_freeze_darken, 90, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Makes color of tees darker when in freeze (0-100)") MACRO_CONFIG_INT(ClColorFreezeFeet, tc_color_freeze_feet, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Also use color for frozen tee feet") diff --git a/src/game/client/components/debughud.cpp b/src/game/client/components/debughud.cpp index 3b0afb07954..f25903331c8 100644 --- a/src/game/client/components/debughud.cpp +++ b/src/game/client/components/debughud.cpp @@ -259,7 +259,60 @@ void CDebugHud::RenderHint() TextRender()->Text(Spacing, Height - FontSize - Spacing, FontSize, Localize("Debug mode enabled. Press Ctrl+Shift+D to disable debug mode.")); } -void CDebugHud::OnRender() +void CDebugHud::RenderTaterDebug() +{ + if(!g_Config.m_Debug) + return; + Graphics()->TextureClear(); + GameClient()->RenderTools()->MapScreenToGroup(GameClient()->m_Camera.m_Center.x, GameClient()->m_Camera.m_Center.y, GameClient()->Layers()->GameGroup(), GameClient()->m_Camera.m_Zoom); + for(int ClientId = 0; ClientId < MAX_CLIENTS; ClientId++) + { + //if(!m_pClient->m_Snap.m_aCharacters[ClientId].m_Active) + //{ + // continue; + //} + //vec2 ServerPos = mix(vec2(GameClient()->m_Snap.m_aCharacters[ClientId].m_Prev.m_X, GameClient()->m_Snap.m_aCharacters[ClientId].m_Prev.m_Y), + // vec2(GameClient()->m_Snap.m_aCharacters[ClientId].m_Cur.m_X, GameClient()->m_Snap.m_aCharacters[ClientId].m_Cur.m_Y), + // Client()->IntraGameTick(g_Config.m_ClDummy)); + + //vec2 RenderPos = m_pClient->m_aClients[ClientId].m_RenderPos; + //vec2 Vector = m_pClient->m_aClients[ClientId].m_DebugVector; + //vec2 StartPos = ServerPos; + //vec2 EndPos = ServerPos + (Vector); + //Graphics()->LinesBegin(); + //Graphics()->SetColor(ColorRGBA(0.0f, 1.0, 0.0f, 1.0f)); + + //IGraphics::CLineItem LineItem(StartPos.x, StartPos.y, EndPos.x, EndPos.y); + //Graphics()->LinesDraw(&LineItem, 1); + + //EndPos = ServerPos + m_pClient->m_aClients[ClientId].m_DebugVector2; + //LineItem = IGraphics::CLineItem(StartPos.x, StartPos.y, EndPos.x, EndPos.y); + //Graphics()->SetColor(ColorRGBA(0.5f, 0.5, 1.0f, 1.0f)); + //Graphics()->LinesDraw(&LineItem, 1); + + //EndPos = ServerPos + m_pClient->m_aClients[ClientId].m_DebugVector3; + //LineItem = IGraphics::CLineItem(StartPos.x, StartPos.y, EndPos.x, EndPos.y); + //Graphics()->SetColor(ColorRGBA(1.0f, 0.0, 0.0f, 1.0f)); + //Graphics()->LinesDraw(&LineItem, 1); + //Graphics()->LinesEnd(); + + //Graphics()->LinesBegin(); + //Graphics()->SetColor(ColorRGBA(0.0f, 1.0, 0.0f, 1.0f)); + //for(int i = 0; i < 20; i++) + //{ + // break; + // int GameTick = Client()->GameTick(g_Config.m_ClDummy) - i; + // vec2 EndPos = m_pClient->m_aClients[ClientId].m_aPredPos[(GameTick) % 200]; + // vec2 StartPos = m_pClient->m_aClients[ClientId].m_aPredPos[(GameTick - 1) % 200]; + + // LineItem = IGraphics::CLineItem(StartPos.x, StartPos.y, EndPos.x, EndPos.y); + // Graphics()->LinesDraw(&LineItem, 1); + //} + //Graphics()->LinesEnd(); + } +} + + void CDebugHud::OnRender() { if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK) return; @@ -267,4 +320,5 @@ void CDebugHud::OnRender() RenderTuning(); RenderNetCorrections(); RenderHint(); + RenderTaterDebug(); } diff --git a/src/game/client/components/debughud.h b/src/game/client/components/debughud.h index 3dc326d73ec..6b6d7622b4c 100644 --- a/src/game/client/components/debughud.h +++ b/src/game/client/components/debughud.h @@ -11,6 +11,7 @@ class CDebugHud : public CComponent void RenderNetCorrections(); void RenderTuning(); void RenderHint(); + void RenderTaterDebug(); CGraph m_RampGraph; CGraph m_ZoomedInGraph; diff --git a/src/game/client/components/players.cpp b/src/game/client/components/players.cpp index ec0adb2bd49..a639fd69723 100644 --- a/src/game/client/components/players.cpp +++ b/src/game/client/components/players.cpp @@ -518,6 +518,8 @@ void CPlayers::RenderPlayer( bool Stationary = Player.m_VelX <= 1 && Player.m_VelX >= -1; bool InAir = !Collision()->CheckPoint(Player.m_X, Player.m_Y + 16); + if(g_Config.m_ClAntiPingImproved && !Local) + InAir = !Collision()->CheckPoint(Position.x, Position.y + 16); bool Running = Player.m_VelX >= 5000 || Player.m_VelX <= -5000; bool WantOtherDir = (Player.m_Direction == -1 && Vel.x > 0) || (Player.m_Direction == 1 && Vel.x < 0); bool Inactive = ClientId >= 0 && (m_pClient->m_aClients[ClientId].m_Afk || m_pClient->m_aClients[ClientId].m_Paused); @@ -779,7 +781,6 @@ void CPlayers::RenderPlayer( CTeeRenderInfo Shadow = RenderInfo; RenderTools()->RenderTee(&State, &Shadow, Player.m_Emote, Direction, ShadowPosition, 0.5f); // render ghost } - RenderTools()->RenderTee(&State, &RenderInfo, Player.m_Emote, Direction, Position, Alpha); float TeeAnimScale, TeeBaseSize; diff --git a/src/game/client/components/tclient/menus_tclient.cpp b/src/game/client/components/tclient/menus_tclient.cpp index 5f4aedbb748..e089d7972db 100644 --- a/src/game/client/components/tclient/menus_tclient.cpp +++ b/src/game/client/components/tclient/menus_tclient.cpp @@ -269,7 +269,7 @@ void CMenus::RenderSettingsTClient(CUIRect MainView) Column.HSplitTop(MarginSmall, nullptr, &Column); Column.HSplitTop(LineSize, &Button, &Column); - Ui()->DoScrollbarOption(&g_Config.m_ClPredictionMargin, &g_Config.m_ClPredictionMargin, &Button, Localize("Prediction Margin"), 10, 25, &CUi::ms_LinearScrollbarScale, 0, "ms"); + Ui()->DoScrollbarOption(&g_Config.m_ClPredictionMargin, &g_Config.m_ClPredictionMargin, &Button, Localize("Prediction Margin"), 10, 25, &CUi::ms_LinearScrollbarScale, CUi::SCROLLBAR_OPTION_NOCLAMPVALUE, "ms"); DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClRemoveAnti, Localize("Remove prediction & antiping in freeze"), &g_Config.m_ClRemoveAnti, &Column, LineSize); if(g_Config.m_ClRemoveAnti) { diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index e2f43c02ec1..80809aa34fd 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -2293,6 +2293,7 @@ void CGameClient::OnPredict() if(PredictDummy()) pDummyChar = m_PredictedWorld.GetCharacterById(m_PredictedDummyId); + bool RealPredTick = false; // predict // prediction actually happens here @@ -2368,6 +2369,7 @@ void CGameClient::OnPredict() { m_aLastNewPredictedTick[Dummy] = Tick; m_NewPredictedTick = true; + RealPredTick = true; vec2 Pos = pLocalChar->Core()->m_Pos; int Events = pLocalChar->Core()->m_TriggeredEvents; @@ -2441,7 +2443,11 @@ void CGameClient::OnPredict() } // detect mispredictions of other players and make corrections smoother when possible - if(g_Config.m_ClAntiPingSmooth && Predict() && AntiPingPlayers() && m_NewTick && m_PredictedTick >= MIN_TICK && absolute(m_PredictedTick - Client()->PredGameTick(g_Config.m_ClDummy)) <= 1 && absolute(Client()->GameTick(g_Config.m_ClDummy) - Client()->PrevGameTick(g_Config.m_ClDummy)) <= 2) + if(g_Config.m_ClAntiPingSmooth && + Predict() && AntiPingPlayers() && + m_NewTick && m_PredictedTick >= MIN_TICK && + absolute(m_PredictedTick - Client()->PredGameTick(g_Config.m_ClDummy)) <= 1 && + absolute(Client()->GameTick(g_Config.m_ClDummy) - Client()->PrevGameTick(g_Config.m_ClDummy)) <= 2) { int PredTime = clamp(Client()->GetPredictionTime(), 0, 800); float SmoothPace = 4 - 1.5f * PredTime / 800.f; // smoothing pace (a lower value will make the smoothing quicker) @@ -2497,12 +2503,208 @@ void CGameClient::OnPredict() } } + // TClient + // New antiping smoothing + CCharacter *pSmoothLocalChar = m_PredSmoothingWorld.GetCharacterById(m_Snap.m_LocalClientId); + if(!pSmoothLocalChar) + { + m_PredSmoothingWorld.CopyWorld(&m_PredictedWorld); + pSmoothLocalChar = m_PredSmoothingWorld.GetCharacterById(m_Snap.m_LocalClientId); + } + + if(g_Config.m_ClAntiPingImproved && + Predict() && AntiPingPlayers() && + pSmoothLocalChar && + RealPredTick && m_PredictedTick >= MIN_TICK) + { + int PredTime = clamp(Client()->GetPredictionTime(), 0, 8000); //Milliseconds for some reason?? TODO: Use more precision + + // Nightmare: in order to get 100% accurate comparison to detect mispredictions we must + // tick the PREVIOUS predicted world with our CURRENT predicted inputs + CCharacter *pSmoothDummyChar = 0; + CCharacter *pPredDummyChar = 0; + if(PredictDummy()) + { + pSmoothDummyChar = m_PredSmoothingWorld.GetCharacterById(m_PredictedDummyId); + pPredDummyChar = m_PredictedWorld.GetCharacterById(m_PredictedDummyId); + } + CNetObj_PlayerInput *pInputData = &m_PredictedWorld.GetCharacterById(m_Snap.m_LocalClientId)->LatestInput(); + CNetObj_PlayerInput *pDummyInputData = !pPredDummyChar ? 0 : &m_PredictedWorld.GetCharacterById(m_PredictedDummyId)->LatestInput(); + bool DummyFirst = pInputData && pDummyInputData && pSmoothDummyChar->GetCid() < pSmoothLocalChar->GetCid(); + if(DummyFirst) + pSmoothDummyChar->OnDirectInput(pDummyInputData); + if(pInputData) + pSmoothLocalChar->OnDirectInput(pInputData); + if(pDummyInputData && !DummyFirst) + pSmoothDummyChar->OnDirectInput(pDummyInputData); + m_PredSmoothingWorld.m_GameTick++; + if(pInputData) + pSmoothLocalChar->OnPredictedInput(pInputData); + if(pDummyInputData) + pSmoothDummyChar->OnPredictedInput(pDummyInputData); + m_PredSmoothingWorld.Tick(); + + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(!m_Snap.m_aCharacters[i].m_Active || i == m_Snap.m_LocalClientId || !m_aLastActive[i]) + continue; + + CCharacter *pChar = m_PredSmoothingWorld.GetCharacterById(i); + if(!pChar) + continue; + + vec2 PredPos = m_aClients[i].m_Predicted.m_Pos; + if(g_Config.m_ClFastInputOthers && g_Config.m_ClFastInput) + PredPos = m_aClients[i].m_PrevPredicted.m_Pos; + + vec2 PrevPredPos = pChar->GetCore().m_Pos; + + // Cursed hack to get the game tick consistently + int GameTick = Client()->GameTick(g_Config.m_ClDummy) + (int)Client()->IntraGameTick(g_Config.m_ClDummy); + static int PrevGameTick = 0; + if(PrevGameTick == GameTick) + GameTick++; + PrevGameTick = Client()->GameTick(g_Config.m_ClDummy) + (int)Client()->IntraGameTick(g_Config.m_ClDummy); + + vec2 ServerPos = m_aClients[i].m_aPredPos[GameTick % 200]; + vec2 PrevServerPos = m_aClients[i].m_aPredPos[(GameTick - 1) % 200]; + + vec2 IntraServerPos = mix(PrevServerPos, ServerPos, Client()->IntraGameTick(g_Config.m_ClDummy)); + vec2 PredDir = normalize(PredPos - ServerPos); + vec2 LastDir = normalize(PrevPredPos - ServerPos); + + vec2 MaxPos = vec2(0, 0); + vec2 MinPos = vec2(0, 0); + // Get a bounding box for our final prediction position to minimize going through walls + for(int Tick = GameTick - 1; Tick <= FinalTickOthers; Tick++) + { + if(m_aClients[i].m_aPredTick[Tick % 200] == 0) + continue; + vec2 Pos = m_aClients[i].m_aPredPos[Tick % 200]; + if(Tick == GameTick - 1) + { + MaxPos = Pos; + MinPos = Pos; + } + else + { + MaxPos.x = std::max(Pos.x, MaxPos.x); + MaxPos.y = std::max(Pos.y, MaxPos.y); + MinPos.x = std::min(Pos.x, MinPos.x); + MinPos.y = std::min(Pos.y, MinPos.y); + } + } + int PredStartTick = GameTick; + int HistoryStartTick = PredStartTick - (FinalTickOthers - PredStartTick); + HistoryStartTick = std::max(0, HistoryStartTick); + vec2 HistoryVector = vec2(0, 0); + float HistoryDistance = 0.0f; + // Find the average history vector + for(int Tick = HistoryStartTick; Tick <= PredStartTick; Tick++) + { + if(m_aClients[i].m_aPredTick[Tick % 200] == 0 || m_aClients[i].m_aPredTick[(Tick - 1) % 200] == 0) + continue; + vec2 DirVector = m_aClients[i].m_aPredPos[Tick % 200] - m_aClients[i].m_aPredPos[(Tick - 1) % 200]; + HistoryVector += DirVector; + HistoryDistance += length(DirVector); + } + + int HistoryCount = (PredStartTick - HistoryStartTick + 1); + HistoryVector = HistoryVector / HistoryCount; + float HistoryLength = length(HistoryVector); + HistoryVector = normalize(HistoryVector); + float Variance = 0.0f; + // Find the variance over the history window + if(length(HistoryVector) > 0.0f) + { + for(int Tick = HistoryStartTick; Tick <= PredStartTick; Tick++) + { + if(m_aClients[i].m_aPredTick[Tick % 200] == 0 || m_aClients[i].m_aPredTick[(Tick - 1) % 200] == 0) + continue; + vec2 DirVector = m_aClients[i].m_aPredPos[Tick % 200] - m_aClients[i].m_aPredPos[(Tick - 1) % 200]; + vec2 Diff = normalize(DirVector) - HistoryVector; + Variance += dot(Diff, Diff); + } + Variance /= HistoryCount; + } + else + { + Variance = 0.0f; + } + float Sigma = 1.5f; // Can be adjusted + float SigmaScale = length(PredPos - ServerPos) / HistoryDistance; + if(SigmaScale > 0) + Sigma /= SigmaScale; + float TrustFactor = std::max(0.0f, 1.0f - (std::sqrt(Variance) / Sigma)); + vec2 TrustedVector = HistoryVector; + + // Detect mispredictions + float Confidence = 1.0f; + if(PredDir == vec2(0, 0)) + Confidence = 1.0f; + else + { + Confidence = std::max(0.0f, dot(LastDir, PredDir)); + Confidence = std::pow(Confidence, 4.0f); // Can be adjusted + } + float Uncertainty = 1.0f - Confidence; + float TickDuration = (float)1000 / (float)Client()->GameTickSpeed(); + + // Manage uncertainty value + float TickSize = TickDuration / ((float)PredTime * 1.5f); // 20ms / PredTime + float PrevConfidence = 1.0f - m_aClients[i].m_Uncertainty; + float NewConfidence = PrevConfidence - Uncertainty + TickSize; + NewConfidence = std::clamp(NewConfidence, -1.25f, 1.0f); // A certain about of "negative buffer" is allowed + m_aClients[i].m_Uncertainty = 1.0f - NewConfidence; + NewConfidence = std::max(0.0f, NewConfidence); + + // Decompose prediction vector into 2 components based on the trusted vector + vec2 PredVector = PredPos - ServerPos; + vec2 Forward = normalize(TrustedVector); + float dotPF = std::max(0.0f, dot(normalize(PredVector), Forward)); + vec2 ConfidenceParallel = Forward * dotPF * length(PredVector); + if(dotPF == 0.0f) + ConfidenceParallel = vec2(0, 0); + vec2 ConfidencePerp = PredVector - ConfidenceParallel; + vec2 ConfidenceVector = ConfidenceParallel * std::max(TrustFactor, NewConfidence) + ConfidencePerp * NewConfidence; + + // Minor safe gaurd against insane predictions + if (length(ConfidenceVector) > HistoryDistance) + ConfidenceVector = mix(normalize(ConfidenceVector) * HistoryDistance, ConfidenceVector, NewConfidence); + + vec2 ConfidencePos = ServerPos + ConfidenceVector; + + // Clamp final position to bounding box + ConfidencePos.x = std::clamp(ConfidencePos.x, MinPos.x, MaxPos.x); + ConfidencePos.y = std::clamp(ConfidencePos.y, MinPos.y, MaxPos.y); + + //m_aClients[i].m_DebugVector = ConfidenceParallel; + //m_aClients[i].m_DebugVector2 = ConfidencePerp; + //m_aClients[i].m_DebugVector3 = HistoryVector * 25.0f; + + m_aClients[i].m_PrevImprovedPredPos = m_aClients[i].m_ImprovedPredPos; + m_aClients[i].m_ImprovedPredPos = ConfidencePos; + + + //char aBuf[256]; + //str_format(aBuf, sizeof(aBuf), "Trust: %.2f, Confidence: %.2f", TrustFactor, NewConfidence); + //if(NewConfidence != 1.0f || TrustFactor != 1.0f) + //Echo(aBuf); + } + } + // Copy the current pred world so on the next tick we have the "previous" pred world to advance and test against + if(m_NewPredictedTick) + m_PredSmoothingWorld.CopyWorld(&m_PredictedWorld); + for(int i = 0; i < MAX_CLIENTS; i++) { if(m_Snap.m_aCharacters[i].m_Active) { - m_aLastPos[i] = m_aClients[i].m_Predicted.m_Pos; - m_aLastActive[i] = true; + if(m_NewPredictedTick) + { + m_aLastPos[i] = m_aClients[i].m_Predicted.m_Pos; + m_aLastActive[i] = true; + } } else m_aLastActive[i] = false; @@ -3190,7 +3392,6 @@ void CGameClient::UpdateRenderedCharacters() vec2(m_Snap.m_aCharacters[i].m_Cur.m_X, m_Snap.m_aCharacters[i].m_Cur.m_Y), Client()->IntraGameTick(g_Config.m_ClDummy)); vec2 Pos = UnpredPos; - CCharacter *pChar = m_PredictedWorld.GetCharacterById(i); // TODO: @Tater remove this garbage if(i == m_Snap.m_LocalClientId) @@ -3217,6 +3418,7 @@ void CGameClient::UpdateRenderedCharacters() vec2(m_aClients[i].m_RenderCur.m_X, m_aClients[i].m_RenderCur.m_Y), m_aClients[i].m_IsPredicted ? Client()->PredIntraGameTick(g_Config.m_ClDummy) : Client()->IntraGameTick(g_Config.m_ClDummy)); + if(g_Config.m_ClRemoveAnti) Pos = GetFreezePos(i); @@ -3239,6 +3441,9 @@ void CGameClient::UpdateRenderedCharacters() if(g_Config.m_ClAntiPingSmooth) Pos = GetSmoothPos(i); + if(g_Config.m_ClAntiPingImproved) + Pos = mix(m_aClients[i].m_PrevImprovedPredPos, m_aClients[i].m_ImprovedPredPos, Client()->PredIntraGameTick(g_Config.m_ClDummy)); + if(g_Config.m_ClRemoveAnti && g_Config.m_ClAmIFrozen) Pos = GetFreezePos(i); diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h index 3c78ee501ba..240e540951b 100644 --- a/src/game/client/gameclient.h +++ b/src/game/client/gameclient.h @@ -424,6 +424,14 @@ class CGameClient : public IGameClient CCharacterCore m_Predicted; CCharacterCore m_PrevPredicted; + //TClient + vec2 m_ImprovedPredPos = vec2(0, 0); + vec2 m_PrevImprovedPredPos = vec2(0, 0); + //vec2 m_DebugVector = vec2(0, 0); + //vec2 m_DebugVector2 = vec2(0, 0); + //vec2 m_DebugVector3 = vec2(0, 0); + float m_Uncertainty = 0.0f; + CTeeRenderInfo m_SkinInfo; // this is what the server reports CTeeRenderInfo m_RenderInfo; // this is what we use @@ -617,7 +625,9 @@ class CGameClient : public IGameClient CGameWorld m_GameWorld; CGameWorld m_PredictedWorld; CGameWorld m_PrevPredictedWorld; + //TClient CGameWorld m_ExtraPredictedWorld; + CGameWorld m_PredSmoothingWorld; std::vector &Switchers() { return m_GameWorld.m_Core.m_vSwitchers; } std::vector &PredSwitchers() { return m_PredictedWorld.m_Core.m_vSwitchers; } diff --git a/src/game/client/prediction/entities/character.h b/src/game/client/prediction/entities/character.h index 9d51def3ea3..52c3b609817 100644 --- a/src/game/client/prediction/entities/character.h +++ b/src/game/client/prediction/entities/character.h @@ -142,6 +142,8 @@ class CCharacter : public CEntity bool IsSuper() { return m_Core.m_Super; } int m_FreezeAccumulation; int m_AliveAccumulation; + //TClient + CNetObj_PlayerInput LatestInput() { return m_LatestInput; }; private: // weapon info