diff --git a/src/game/client/components/ghost.cpp b/src/game/client/components/ghost.cpp index 03b839740..8894c21c0 100644 --- a/src/game/client/components/ghost.cpp +++ b/src/game/client/components/ghost.cpp @@ -344,6 +344,7 @@ void CGhost::OnRender() Player.m_AttackTick += Client()->GameTick(g_Config.m_ClDummy) - GhostTick; m_pClient->m_Players.RenderHook(&Prev, &Player, &Ghost.m_RenderInfo, -2, IntraTick); + m_pClient->m_Players.RenderHookCollLine(&Prev, &Player, -2, IntraTick); m_pClient->m_Players.RenderPlayer(&Prev, &Player, &Ghost.m_RenderInfo, -2, IntraTick); } } diff --git a/src/game/client/components/nameplates.cpp b/src/game/client/components/nameplates.cpp index 02c9b357d..29a3489ab 100644 --- a/src/game/client/components/nameplates.cpp +++ b/src/game/client/components/nameplates.cpp @@ -305,6 +305,18 @@ void CNamePlates::OnRender() if(!g_Config.m_ClNameplates && ShowDirection == 0) return; + // get screen edges to avoid rendering offscreen + float ScreenX0, ScreenY0, ScreenX1, ScreenY1; + Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); + // expand the edges to prevent popping in/out onscreen + // + // it is assumed that the nameplate and all its components fit into a 800x800 box placed directly above the tee + // this may need to be changed or calculated differently in the future + ScreenX0 -= 400; + ScreenX1 += 400; + //ScreenY0 -= 0; + ScreenY1 += 800; + for(int i = 0; i < MAX_CLIENTS; i++) { const CNetObj_PlayerInfo *pInfo = m_pClient->m_Snap.m_paPlayerInfos[i]; @@ -313,6 +325,13 @@ void CNamePlates::OnRender() continue; } + // don't render offscreen + vec2 *pRenderPos = &m_pClient->m_aClients[i].m_RenderPos; + if(pRenderPos->x < ScreenX0 || pRenderPos->x > ScreenX1 || pRenderPos->y < ScreenY0 || pRenderPos->y > ScreenY1) + { + continue; + } + if(m_pClient->m_aClients[i].m_SpecCharPresent) { RenderNameplatePos(m_pClient->m_aClients[i].m_SpecChar, pInfo, 0.4f, true); diff --git a/src/game/client/components/players.cpp b/src/game/client/components/players.cpp index 0ff879940..0562b86c3 100644 --- a/src/game/client/components/players.cpp +++ b/src/game/client/components/players.cpp @@ -76,7 +76,156 @@ inline float AngularApproach(float Src, float Dst, float Amount) return Dst; return n; } +void CPlayers::RenderHookCollLine( + const CNetObj_Character *pPrevChar, + const CNetObj_Character *pPlayerChar, + int ClientID, + float Intra) +{ + CNetObj_Character Prev; + CNetObj_Character Player; + Prev = *pPrevChar; + Player = *pPlayerChar; + bool Local = m_pClient->m_Snap.m_LocalClientID == ClientID; + bool OtherTeam = m_pClient->IsOtherTeam(ClientID); + float Alpha = (OtherTeam || ClientID < 0) ? g_Config.m_ClShowOthersAlpha / 100.0f : 1.0f; + + float IntraTick = Intra; + if(ClientID >= 0) + IntraTick = m_pClient->m_aClients[ClientID].m_IsPredicted ? Client()->PredIntraGameTick(g_Config.m_ClDummy) : Client()->IntraGameTick(g_Config.m_ClDummy); + + float Angle; + if(Local && Client()->State() != IClient::STATE_DEMOPLAYBACK) + { + // just use the direct input if it's the local player we are rendering + Angle = angle(m_pClient->m_Controls.m_MousePos[g_Config.m_ClDummy]); + } + else + { + float AngleIntraTick = IntraTick; + // using unpredicted angle when rendering other players in-game + if(ClientID >= 0) + AngleIntraTick = Client()->IntraGameTick(g_Config.m_ClDummy); + // If the player moves their weapon through top, then change + // the end angle by 2*Pi, so that the mix function will use the + // short path and not the long one. + if(Player.m_Angle > (256.0f * pi) && Prev.m_Angle < 0) + Player.m_Angle -= 256.0f * 2 * pi; + else if(Player.m_Angle < 0 && Prev.m_Angle > (256.0f * pi)) + Player.m_Angle += 256.0f * 2 * pi; + + Angle = mix((float)Prev.m_Angle, (float)Player.m_Angle, AngleIntraTick) / 256.0f; + } + + vec2 Direction = direction(Angle); + vec2 Position; + if(in_range(ClientID, MAX_CLIENTS - 1)) + Position = m_pClient->m_aClients[ClientID].m_RenderPos; + else + Position = mix(vec2(Prev.m_X, Prev.m_Y), vec2(Player.m_X, Player.m_Y), IntraTick); + // draw hook collision line + { + bool AlwaysRenderHookColl = GameClient()->m_GameInfo.m_AllowHookColl && (Local ? g_Config.m_ClShowHookCollOwn : g_Config.m_ClShowHookCollOther) == 2; + bool RenderHookCollPlayer = ClientID >= 0 && Player.m_PlayerFlags & PLAYERFLAG_AIM && (Local ? g_Config.m_ClShowHookCollOwn : g_Config.m_ClShowHookCollOther) > 0; + bool RenderHookCollVideo = true; +#if defined(CONF_VIDEORECORDER) + RenderHookCollVideo = !IVideo::Current() || g_Config.m_ClVideoShowHookCollOther || Local; +#endif + if((AlwaysRenderHookColl || RenderHookCollPlayer) && RenderHookCollVideo) + { + vec2 ExDirection = Direction; + + if(Local && Client()->State() != IClient::STATE_DEMOPLAYBACK) + ExDirection = normalize(vec2((int)m_pClient->m_Controls.m_MousePos[g_Config.m_ClDummy].x, (int)m_pClient->m_Controls.m_MousePos[g_Config.m_ClDummy].y)); + + Graphics()->TextureClear(); + vec2 InitPos = Position; + vec2 FinishPos = InitPos + ExDirection * (m_pClient->m_Tuning[g_Config.m_ClDummy].m_HookLength - 42.0f); + + if(g_Config.m_ClHookCollSize > 0) + Graphics()->QuadsBegin(); + else + Graphics()->LinesBegin(); + + ColorRGBA HookCollColor = color_cast(ColorHSLA(g_Config.m_ClHookCollColorNoColl)); + + float PhysSize = 28.0f; + + vec2 OldPos = InitPos + ExDirection * PhysSize * 1.5f; + vec2 NewPos = OldPos; + + bool DoBreak = false; + int Hit = 0; + + do + { + OldPos = NewPos; + NewPos = OldPos + ExDirection * m_pClient->m_Tuning[g_Config.m_ClDummy].m_HookFireSpeed; + + if(distance(InitPos, NewPos) > m_pClient->m_Tuning[g_Config.m_ClDummy].m_HookLength) + { + NewPos = InitPos + normalize(NewPos - InitPos) * m_pClient->m_Tuning[g_Config.m_ClDummy].m_HookLength; + DoBreak = true; + } + + int TeleNr = 0; + Hit = Collision()->IntersectLineTeleHook(OldPos, NewPos, &FinishPos, 0x0, &TeleNr); + + if(!DoBreak && Hit) + { + if(Hit != TILE_NOHOOK) + { + HookCollColor = color_cast(ColorHSLA(g_Config.m_ClHookCollColorHookableColl)); + } + } + + if(m_pClient->IntersectCharacter(OldPos, FinishPos, FinishPos, ClientID) != -1) + { + HookCollColor = color_cast(ColorHSLA(g_Config.m_ClHookCollColorTeeColl)); + break; + } + + if(Hit) + break; + + NewPos.x = round_to_int(NewPos.x); + NewPos.y = round_to_int(NewPos.y); + + if(OldPos == NewPos) + break; + + ExDirection.x = round_to_int(ExDirection.x * 256.0f) / 256.0f; + ExDirection.y = round_to_int(ExDirection.y * 256.0f) / 256.0f; + } while(!DoBreak); + + if(AlwaysRenderHookColl && RenderHookCollPlayer) + { + // invert the hook coll colors when using cl_show_hook_coll_always and +showhookcoll is pressed + HookCollColor = color_invert(HookCollColor); + } + Graphics()->SetColor(HookCollColor.WithAlpha(Alpha)); + if(g_Config.m_ClHookCollSize > 0) + { + float LineWidth = 0.5f + (float)(g_Config.m_ClHookCollSize - 1) * 0.25f; + vec2 PerpToAngle = normalize(vec2(ExDirection.y, -ExDirection.x)) * GameClient()->m_Camera.m_Zoom; + vec2 Pos0 = FinishPos + PerpToAngle * -LineWidth; + vec2 Pos1 = FinishPos + PerpToAngle * LineWidth; + vec2 Pos2 = InitPos + PerpToAngle * -LineWidth; + vec2 Pos3 = InitPos + PerpToAngle * LineWidth; + IGraphics::CFreeformItem FreeformItem(Pos0.x, Pos0.y, Pos1.x, Pos1.y, Pos2.x, Pos2.y, Pos3.x, Pos3.y); + Graphics()->QuadsDrawFreeform(&FreeformItem, 1); + Graphics()->QuadsEnd(); + } + else + { + IGraphics::CLineItem LineItem(InitPos.x, InitPos.y, FinishPos.x, FinishPos.y); + Graphics()->LinesDraw(&LineItem, 1); + Graphics()->LinesEnd(); + } + } + } +} void CPlayers::RenderHook( const CNetObj_Character *pPrevChar, const CNetObj_Character *pPlayerChar, @@ -231,7 +380,6 @@ void CPlayers::RenderPlayer( Position = m_pClient->m_aClients[ClientID].m_RenderPos; else Position = mix(vec2(Prev.m_X, Prev.m_Y), vec2(Player.m_X, Player.m_Y), IntraTick); - vec2 Vel = mix(vec2(Prev.m_VelX / 256.0f, Prev.m_VelY / 256.0f), vec2(Player.m_VelX / 256.0f, Player.m_VelY / 256.0f), IntraTick); m_pClient->m_Flow.Add(Position, Vel * 100.0f, 10.0f); @@ -277,105 +425,6 @@ void CPlayers::RenderPlayer( // draw gun { - bool AlwaysRenderHookColl = GameClient()->m_GameInfo.m_AllowHookColl && (Local ? g_Config.m_ClShowHookCollOwn : g_Config.m_ClShowHookCollOther) == 2; - bool RenderHookCollPlayer = ClientID >= 0 && Player.m_PlayerFlags & PLAYERFLAG_AIM && (Local ? g_Config.m_ClShowHookCollOwn : g_Config.m_ClShowHookCollOther) > 0; - bool RenderHookCollVideo = true; -#if defined(CONF_VIDEORECORDER) - RenderHookCollVideo = !IVideo::Current() || g_Config.m_ClVideoShowHookCollOther || Local; -#endif - if((AlwaysRenderHookColl || RenderHookCollPlayer) && RenderHookCollVideo) - { - vec2 ExDirection = Direction; - - if(Local && Client()->State() != IClient::STATE_DEMOPLAYBACK) - ExDirection = normalize(vec2((int)m_pClient->m_Controls.m_MousePos[g_Config.m_ClDummy].x, (int)m_pClient->m_Controls.m_MousePos[g_Config.m_ClDummy].y)); - - Graphics()->TextureClear(); - vec2 InitPos = Position; - vec2 FinishPos = InitPos + ExDirection * (m_pClient->m_Tuning[g_Config.m_ClDummy].m_HookLength - 42.0f); - - if(g_Config.m_ClHookCollSize > 0) - Graphics()->QuadsBegin(); - else - Graphics()->LinesBegin(); - - ColorRGBA HookCollColor = color_cast(ColorHSLA(g_Config.m_ClHookCollColorNoColl)); - - float PhysSize = 28.0f; - - vec2 OldPos = InitPos + ExDirection * PhysSize * 1.5f; - vec2 NewPos = OldPos; - - bool DoBreak = false; - int Hit = 0; - - do - { - OldPos = NewPos; - NewPos = OldPos + ExDirection * m_pClient->m_Tuning[g_Config.m_ClDummy].m_HookFireSpeed; - - if(distance(InitPos, NewPos) > m_pClient->m_Tuning[g_Config.m_ClDummy].m_HookLength) - { - NewPos = InitPos + normalize(NewPos - InitPos) * m_pClient->m_Tuning[g_Config.m_ClDummy].m_HookLength; - DoBreak = true; - } - - int TeleNr = 0; - Hit = Collision()->IntersectLineTeleHook(OldPos, NewPos, &FinishPos, 0x0, &TeleNr); - - if(!DoBreak && Hit) - { - if(Hit != TILE_NOHOOK) - { - HookCollColor = color_cast(ColorHSLA(g_Config.m_ClHookCollColorHookableColl)); - } - } - - if(m_pClient->IntersectCharacter(OldPos, FinishPos, FinishPos, ClientID) != -1) - { - HookCollColor = color_cast(ColorHSLA(g_Config.m_ClHookCollColorTeeColl)); - break; - } - - if(Hit) - break; - - NewPos.x = round_to_int(NewPos.x); - NewPos.y = round_to_int(NewPos.y); - - if(OldPos == NewPos) - break; - - ExDirection.x = round_to_int(ExDirection.x * 256.0f) / 256.0f; - ExDirection.y = round_to_int(ExDirection.y * 256.0f) / 256.0f; - } while(!DoBreak); - - if(AlwaysRenderHookColl && RenderHookCollPlayer) - { - // invert the hook coll colors when using cl_show_hook_coll_always and +showhookcoll is pressed - HookCollColor = color_invert(HookCollColor); - } - Graphics()->SetColor(HookCollColor.WithAlpha(Alpha)); - if(g_Config.m_ClHookCollSize > 0) - { - float LineWidth = 0.5f + (float)(g_Config.m_ClHookCollSize - 1) * 0.25f; - vec2 PerpToAngle = normalize(vec2(ExDirection.y, -ExDirection.x)) * GameClient()->m_Camera.m_Zoom; - vec2 Pos0 = FinishPos + PerpToAngle * -LineWidth; - vec2 Pos1 = FinishPos + PerpToAngle * LineWidth; - vec2 Pos2 = InitPos + PerpToAngle * -LineWidth; - vec2 Pos3 = InitPos + PerpToAngle * LineWidth; - IGraphics::CFreeformItem FreeformItem(Pos0.x, Pos0.y, Pos1.x, Pos1.y, Pos2.x, Pos2.y, Pos3.x, Pos3.y); - Graphics()->QuadsDrawFreeform(&FreeformItem, 1); - Graphics()->QuadsEnd(); - } - else - { - IGraphics::CLineItem LineItem(InitPos.x, InitPos.y, FinishPos.x, FinishPos.y); - Graphics()->LinesDraw(&LineItem, 1); - Graphics()->LinesEnd(); - } - } - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); Graphics()->QuadsSetRotation(State.GetAttach()->m_Angle * pi * 2 + Angle); @@ -681,6 +730,19 @@ void CPlayers::OnRender() m_RenderInfoSpec.m_Size = 64.0f; int LocalClientID = m_pClient->m_Snap.m_LocalClientID; + // get screen edges to avoid rendering offscreen + float ScreenX0, ScreenY0, ScreenX1, ScreenY1; + Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); + // expand the edges to prevent popping in/out onscreen + // + // it is assumed that the tee, all its weapons, and emotes fit into a 200x200 box centered on the tee + // this may need to be changed or calculated differently in the future + float BorderBuffer = 100; + ScreenX0 -= BorderBuffer; + ScreenX1 += BorderBuffer; + ScreenY0 -= BorderBuffer; + ScreenY1 += BorderBuffer; + // render everyone else's hook, then our own for(int ClientID = 0; ClientID < MAX_CLIENTS; ClientID++) { @@ -713,11 +775,21 @@ void CPlayers::OnRender() { continue; } + + RenderHookCollLine(&m_pClient->m_aClients[ClientID].m_RenderPrev, &m_pClient->m_aClients[ClientID].m_RenderCur, ClientID); + + //don't render offscreen + vec2 *pRenderPos = &m_pClient->m_aClients[ClientID].m_RenderPos; + if(pRenderPos->x < ScreenX0 || pRenderPos->x > ScreenX1 || pRenderPos->y < ScreenY0 || pRenderPos->y > ScreenY1) + { + continue; + } RenderPlayer(&m_pClient->m_aClients[ClientID].m_RenderPrev, &m_pClient->m_aClients[ClientID].m_RenderCur, &m_aRenderInfo[ClientID], ClientID); } if(LocalClientID != -1 && m_pClient->m_Snap.m_aCharacters[LocalClientID].m_Active && IsPlayerInfoAvailable(LocalClientID)) { const CGameClient::CClientData *pLocalClientData = &m_pClient->m_aClients[LocalClientID]; + RenderHookCollLine(&pLocalClientData->m_RenderPrev, &pLocalClientData->m_RenderCur, LocalClientID); RenderPlayer(&pLocalClientData->m_RenderPrev, &pLocalClientData->m_RenderCur, &m_aRenderInfo[LocalClientID], LocalClientID); } } diff --git a/src/game/client/components/players.h b/src/game/client/components/players.h index b2a5cb5b6..e8ce8491f 100644 --- a/src/game/client/components/players.h +++ b/src/game/client/components/players.h @@ -26,6 +26,11 @@ class CPlayers : public CComponent const CTeeRenderInfo *pRenderInfo, int ClientID, float Intra = 0.f); + void RenderHookCollLine( + const CNetObj_Character *pPrevChar, + const CNetObj_Character *pPlayerChar, + int ClientID, + float Intra = 0.f); bool IsPlayerInfoAvailable(int ClientID) const; int m_WeaponEmoteQuadContainerIndex;