diff --git a/src/game/client/components/camera.cpp b/src/game/client/components/camera.cpp index 199e134ec..80af33cbb 100644 --- a/src/game/client/components/camera.cpp +++ b/src/game/client/components/camera.cpp @@ -31,7 +31,7 @@ float CCamera::ZoomProgress(float CurrentTime) const void CCamera::ScaleZoom(float Factor) { float CurrentTarget = m_Zooming ? m_ZoomSmoothingTarget : m_Zoom; - ChangeZoom(CurrentTarget * Factor); + ChangeZoom(CurrentTarget * Factor, g_Config.m_ClSmoothZoomTime); } float CCamera::MaxZoomLevel() @@ -44,7 +44,7 @@ float CCamera::MinZoomLevel() return 0.01f; } -void CCamera::ChangeZoom(float Target) +void CCamera::ChangeZoom(float Target, int Smoothness) { if(Target > MaxZoomLevel() || Target < MinZoomLevel()) { @@ -64,7 +64,7 @@ void CCamera::ChangeZoom(float Target) m_ZoomSmoothingTarget = Target; m_ZoomSmoothing = CCubicBezier::With(Current, Derivative, 0, m_ZoomSmoothingTarget); m_ZoomSmoothingStart = Now; - m_ZoomSmoothingEnd = Now + (float)g_Config.m_ClSmoothZoomTime / 1000; + m_ZoomSmoothingEnd = Now + (float)Smoothness / 1000; m_Zooming = true; } @@ -198,6 +198,9 @@ void CCamera::ConZoomPlus(IConsole::IResult *pResult, void *pUserData) if(pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active || pSelf->GameClient()->m_GameInfo.m_AllowZoom || pSelf->Client()->State() == IClient::STATE_DEMOPLAYBACK) { pSelf->ScaleZoom(ZoomStep); + + if(pSelf->GameClient()->m_MultiViewActivated) + pSelf->GameClient()->m_MultiViewPersonalZoom++; } } void CCamera::ConZoomMinus(IConsole::IResult *pResult, void *pUserData) @@ -206,12 +209,19 @@ void CCamera::ConZoomMinus(IConsole::IResult *pResult, void *pUserData) if(pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active || pSelf->GameClient()->m_GameInfo.m_AllowZoom || pSelf->Client()->State() == IClient::STATE_DEMOPLAYBACK) { pSelf->ScaleZoom(1 / ZoomStep); + + if(pSelf->GameClient()->m_MultiViewActivated) + pSelf->GameClient()->m_MultiViewPersonalZoom--; } } void CCamera::ConZoom(IConsole::IResult *pResult, void *pUserData) { + CCamera *pSelf = (CCamera *)pUserData; float TargetLevel = pResult->NumArguments() ? pResult->GetFloat(0) : g_Config.m_ClDefaultZoom; - ((CCamera *)pUserData)->ChangeZoom(std::pow(ZoomStep, TargetLevel - 10)); + pSelf->ChangeZoom(std::pow(ZoomStep, TargetLevel - 10), g_Config.m_ClSmoothZoomTime); + + if(pSelf->GameClient()->m_MultiViewActivated) + pSelf->GameClient()->m_MultiViewPersonalZoom = 0; } void CCamera::ConSetView(IConsole::IResult *pResult, void *pUserData) { @@ -221,3 +231,8 @@ void CCamera::ConSetView(IConsole::IResult *pResult, void *pUserData) clamp(pResult->GetInteger(0) * 32.0f, 200.0f, pSelf->Collision()->GetWidth() * 32 - 200.0f), clamp(pResult->GetInteger(1) * 32.0f, 200.0f, pSelf->Collision()->GetWidth() * 32 - 200.0f)); } + +void CCamera::SetZoom(float Target, int Smoothness) +{ + ChangeZoom(Target, Smoothness); +} diff --git a/src/game/client/components/camera.h b/src/game/client/components/camera.h index 257a9cec6..c52e9a492 100644 --- a/src/game/client/components/camera.h +++ b/src/game/client/components/camera.h @@ -30,7 +30,7 @@ class CCamera : public CComponent float m_ZoomSmoothingEnd; void ScaleZoom(float Factor); - void ChangeZoom(float Target); + void ChangeZoom(float Target, int Smoothness); float ZoomProgress(float CurrentTime) const; float MinZoomLevel(); @@ -52,6 +52,8 @@ public: virtual void OnConsoleInit() override; virtual void OnReset() override; + void SetZoom(float Target, int Smoothness); + private: static void ConZoomPlus(IConsole::IResult *pResult, void *pUserData); static void ConZoomMinus(IConsole::IResult *pResult, void *pUserData); diff --git a/src/game/client/components/hud.cpp b/src/game/client/components/hud.cpp index 1f5848f4d..d6d14527e 100644 --- a/src/game/client/components/hud.cpp +++ b/src/game/client/components/hud.cpp @@ -106,7 +106,7 @@ void CHud::RenderGameTimer() } else if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_RACETIME) { - //The Warmup timer is negative in this case to make sure that incompatible clients will not see a warmup timer + // The Warmup timer is negative in this case to make sure that incompatible clients will not see a warmup timer Time = (Client()->GameTick(g_Config.m_ClDummy) + m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer) / Client()->GameTickSpeed(); } else @@ -1474,7 +1474,7 @@ void CHud::RenderSpectatorHud() // draw the text char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "%s: %s", Localize("Spectate"), m_pClient->m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW ? m_pClient->m_aClients[m_pClient->m_Snap.m_SpecInfo.m_SpectatorID].m_aName : Localize("Free-View")); + str_format(aBuf, sizeof(aBuf), "%s: %s", Localize("Spectate"), GameClient()->m_MultiViewActivated ? Localize("Multi-View") : m_pClient->m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW ? m_pClient->m_aClients[m_pClient->m_Snap.m_SpecInfo.m_SpectatorID].m_aName : Localize("Free-View")); TextRender()->Text(m_Width - 174.0f, m_Height - 15.0f + (15.f - 8.f) / 2.f, 8.0f, aBuf, -1.0f); } @@ -1527,7 +1527,11 @@ void CHud::OnRender() { RenderAmmoHealthAndArmor(&m_pClient->m_Snap.m_aCharacters[SpectatorID].m_Cur); } - if(SpectatorID != SPEC_FREEVIEW && m_pClient->m_Snap.m_aCharacters[SpectatorID].m_HasExtendedData && g_Config.m_ClShowhudDDRace && GameClient()->m_GameInfo.m_HudDDRace) + if(SpectatorID != SPEC_FREEVIEW && + m_pClient->m_Snap.m_aCharacters[SpectatorID].m_HasExtendedData && + g_Config.m_ClShowhudDDRace && + (!GameClient()->m_MultiViewActivated || GameClient()->m_MultiViewShowHud) && + GameClient()->m_GameInfo.m_HudDDRace) { RenderPlayerState(SpectatorID); } diff --git a/src/game/client/components/spectator.cpp b/src/game/client/components/spectator.cpp index 397e73801..f04af59bc 100644 --- a/src/game/client/components/spectator.cpp +++ b/src/game/client/components/spectator.cpp @@ -149,6 +149,17 @@ void CSpectator::ConSpectateClosest(IConsole::IResult *pResult, void *pUserData) pSelf->Spectate(NewSpectatorID); } +void CSpectator::ConMultiView(IConsole::IResult *pResult, void *pUserData) +{ + CSpectator *pSelf = (CSpectator *)pUserData; + int Input = pResult->GetInteger(0); + + if(Input == -1) + std::fill(std::begin(pSelf->GameClient()->m_aMultiViewId), std::end(pSelf->GameClient()->m_aMultiViewId), false); // remove everyone from multiview + else if(Input < MAX_CLIENTS && Input >= 0) + pSelf->GameClient()->m_aMultiViewId[Input] = !pSelf->GameClient()->m_aMultiViewId[Input]; // activate or deactivate one player from multiview +} + CSpectator::CSpectator() { OnReset(); @@ -162,6 +173,7 @@ void CSpectator::OnConsoleInit() Console()->Register("spectate_next", "", CFGFLAG_CLIENT, ConSpectateNext, this, "Spectate the next player"); Console()->Register("spectate_previous", "", CFGFLAG_CLIENT, ConSpectatePrevious, this, "Spectate the previous player"); Console()->Register("spectate_closest", "", CFGFLAG_CLIENT, ConSpectateClosest, this, "Spectate the closest player"); + Console()->Register("spectate_multiview", "i[id]", CFGFLAG_CLIENT, ConMultiView, this, "Add/remove Client-IDs to spectate them exclusivly (-1 to reset)"); } bool CSpectator::OnCursorMove(float x, float y, IInput::ECursorType CursorType) @@ -186,7 +198,12 @@ void CSpectator::OnRender() if(m_WasActive) { if(m_SelectedSpectatorID != NO_SELECTION) - Spectate(m_SelectedSpectatorID); + { + if(m_SelectedSpectatorID != MULTI_VIEW) + Spectate(m_SelectedSpectatorID); + + GameClient()->m_MultiViewActivated = m_SelectedSpectatorID == MULTI_VIEW; + } m_WasActive = false; } return; @@ -213,6 +230,7 @@ void CSpectator::OnRender() float TeeSizeMod = 1.0f; float RoundRadius = 30.0f; bool Selected = false; + bool MultiViewSelected = false; int TotalPlayers = 0; int PerLine = 8; float BoxMove = -10.0f; @@ -253,34 +271,48 @@ void CSpectator::OnRender() if((Client()->State() == IClient::STATE_DEMOPLAYBACK && m_pClient->m_DemoSpecID == SPEC_FREEVIEW) || (Client()->State() != IClient::STATE_DEMOPLAYBACK && m_pClient->m_Snap.m_SpecInfo.m_SpectatorID == SPEC_FREEVIEW)) { - Graphics()->DrawRect(Width / 2.0f - (ObjWidth - 20.0f), Height / 2.0f - 280.0f, 270.0f, 60.0f, ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, 20.0f); + Graphics()->DrawRect(Width / 2.0f - (ObjWidth - 20.0f), Height / 2.0f - 280.0f, ((ObjWidth * 2.0f) / 3.0f) - 40.0f, 60.0f, ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, 20.0f); + } + + if(GameClient()->m_MultiViewActivated) + { + Graphics()->DrawRect(Width / 2.0f - (ObjWidth - 20.0f) + (ObjWidth * 2.0f / 3.0f), Height / 2.0f - 280.0f, ((ObjWidth * 2.0f) / 3.0f) - 40.0f, 60.0f, ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, 20.0f); } if(Client()->State() == IClient::STATE_DEMOPLAYBACK && m_pClient->m_Snap.m_LocalClientID >= 0 && m_pClient->m_DemoSpecID == SPEC_FOLLOW) { - Graphics()->DrawRect(Width / 2.0f - (ObjWidth - 310.0f), Height / 2.0f - 280.0f, 270.0f, 60.0f, ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, 20.0f); + Graphics()->DrawRect(Width / 2.0f - (ObjWidth - 20.0f) + (ObjWidth * 2.0f * 2.0f / 3.0f), Height / 2.0f - 280.0f, ((ObjWidth * 2.0f) / 3.0f) - 40.0f, 60.0f, ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, 20.0f); } - if(m_SelectorMouse.x >= -(ObjWidth - 20.0f) && m_SelectorMouse.x <= -(ObjWidth - 290 + 10.0f) && + if(m_SelectorMouse.x >= -(ObjWidth - 20.0f) && m_SelectorMouse.x <= -(ObjWidth - 20.0f) + ((ObjWidth * 2.0f) / 3.0f) - 40.0f && m_SelectorMouse.y >= -280.0f && m_SelectorMouse.y <= -220.0f) { m_SelectedSpectatorID = SPEC_FREEVIEW; Selected = true; } TextRender()->TextColor(1.0f, 1.0f, 1.0f, Selected ? 1.0f : 0.5f); - TextRender()->Text(Width / 2.0f - (ObjWidth - 60.0f), Height / 2.0f - 280.f + (60.f - BigFontSize) / 2.f, BigFontSize, Localize("Free-View"), -1.0f); + TextRender()->Text(Width / 2.0f - (ObjWidth - 40.0f), Height / 2.0f - 280.f + (60.f - BigFontSize) / 2.f, BigFontSize, Localize("Free-View"), -1.0f); + + if(m_SelectorMouse.x >= -(ObjWidth - 20.0f) + (ObjWidth * 2.0f / 3.0f) && m_SelectorMouse.x <= -(ObjWidth - 20.0f) + (ObjWidth * 2.0f / 3.0f) + ((ObjWidth * 2.0f) / 3.0f) - 40.0f && + m_SelectorMouse.y >= -280.0f && m_SelectorMouse.y <= -220.0f) + { + m_SelectedSpectatorID = MULTI_VIEW; + MultiViewSelected = true; + } + TextRender()->TextColor(1.0f, 1.0f, 1.0f, MultiViewSelected ? 1.0f : 0.5f); + TextRender()->Text(Width / 2.0f - (ObjWidth - 40.0f) + (ObjWidth * 2.0f / 3.0f), Height / 2.0f - 280.f + (60.f - BigFontSize) / 2.f, BigFontSize, Localize("Multi-View"), -1.0f); if(Client()->State() == IClient::STATE_DEMOPLAYBACK && m_pClient->m_Snap.m_LocalClientID >= 0) { Selected = false; - if(m_SelectorMouse.x > -(ObjWidth - 290.0f) && m_SelectorMouse.x <= -(ObjWidth - 590.0f) && + if(m_SelectorMouse.x >= -(ObjWidth - 20.0f) + (ObjWidth * 2.0f * 2.0f / 3.0f) && m_SelectorMouse.x <= -(ObjWidth - 20.0f) + (ObjWidth * 2.0f * 2.0f / 3.0f) + ((ObjWidth * 2.0f) / 3.0f) - 40.0f && m_SelectorMouse.y >= -280.0f && m_SelectorMouse.y <= -220.0f) { m_SelectedSpectatorID = SPEC_FOLLOW; Selected = true; } TextRender()->TextColor(1.0f, 1.0f, 1.0f, Selected ? 1.0f : 0.5f); - TextRender()->Text(Width / 2.0f - (ObjWidth - 350.0f), Height / 2.0f - 280.0f + (60.f - BigFontSize) / 2.f, BigFontSize, Localize("Follow"), -1.0f); + TextRender()->Text(Width / 2.0f - (ObjWidth - 40.0f) + (ObjWidth * 2.0f * 2.0f / 3.0f), Height / 2.0f - 280.0f + (60.f - BigFontSize) / 2.f, BigFontSize, Localize("Follow"), -1.0f); } float x = -(ObjWidth - 35.0f), y = StartY; diff --git a/src/game/client/components/spectator.h b/src/game/client/components/spectator.h index ff1deac00..711d697a6 100644 --- a/src/game/client/components/spectator.h +++ b/src/game/client/components/spectator.h @@ -10,6 +10,7 @@ class CSpectator : public CComponent { enum { + MULTI_VIEW = -4, NO_SELECTION = -3, }; @@ -30,6 +31,7 @@ class CSpectator : public CComponent static void ConSpectateNext(IConsole::IResult *pResult, void *pUserData); static void ConSpectatePrevious(IConsole::IResult *pResult, void *pUserData); static void ConSpectateClosest(IConsole::IResult *pResult, void *pUserData); + static void ConMultiView(IConsole::IResult *pResult, void *pUserData); public: CSpectator(); diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 72288d017..d62e56b23 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -604,7 +604,11 @@ void CGameClient::UpdatePositions() // spectator position if(m_Snap.m_SpecInfo.m_Active) { - if(Client()->State() == IClient::STATE_DEMOPLAYBACK && m_DemoSpecID != SPEC_FOLLOW && m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW) + if(m_MultiViewActivated) + { + HandleMultiView(); + } + else if(Client()->State() == IClient::STATE_DEMOPLAYBACK && m_DemoSpecID != SPEC_FOLLOW && m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW) { m_Snap.m_SpecInfo.m_Position = mix( vec2(m_Snap.m_aCharacters[m_Snap.m_SpecInfo.m_SpectatorID].m_Prev.m_X, m_Snap.m_aCharacters[m_Snap.m_SpecInfo.m_SpectatorID].m_Prev.m_Y), @@ -623,11 +627,28 @@ void CGameClient::UpdatePositions() } } + if(!m_MultiViewActivated && m_MultiView.m_IsInit) + ResetMultiView(); + UpdateRenderedCharacters(); } void CGameClient::OnRender() { + // check if multi view got activated + if(!m_MultiView.m_IsInit && m_MultiViewActivated) + { + int TeamId = 0; + if(m_Snap.m_SpecInfo.m_SpectatorID >= 0) + TeamId = m_Teams.Team(m_Snap.m_SpecInfo.m_SpectatorID); + + if(!InitMultiViewFromFreeview(TeamId)) + { + dbg_msg("MultiView", "No players found to spectate"); + m_MultiViewActivated = false; + } + } + // update the local character and spectate position UpdatePositions(); @@ -843,6 +864,18 @@ void CGameClient::OnMessage(int MsgId, CUnpacker *pUnpacker, int Conn, bool Dumm pChar->ResetPrediction(); m_GameWorld.ReleaseHooked(pMsg->m_Victim); } + + // if we are spectating a static id set (team 0) and somebody killed, we remove him from the list + if(IsMultiViewIdSet() && m_aMultiViewId[pMsg->m_Victim] && !m_aClients[pMsg->m_Victim].m_Spec) + { + // is multi view even activated and we are not spectating a solo guy + if(m_MultiViewActivated && !m_MultiView.m_Solo && m_MultiView.m_Team == 0) + m_aMultiViewId[pMsg->m_Victim] = false; + + // if everyone of a team killed, we have no ids to spectate anymore, so we disable multi view + if(!IsMultiViewIdSet()) + m_MultiViewActivated = false; + } } } @@ -1666,7 +1699,17 @@ void CGameClient::OnNewSnapshot() m_aDDRaceMsgSent[i] = true; } - if(m_aShowOthers[g_Config.m_ClDummy] == SHOW_OTHERS_NOT_SET || m_aShowOthers[g_Config.m_ClDummy] != g_Config.m_ClShowOthers) + if(m_MultiViewActivated) + { + // dont show other teams while spectating in multi view + CNetMsg_Cl_ShowOthers Msg; + Msg.m_Show = SHOW_OTHERS_ONLY_TEAM; + Client()->SendPackMsgActive(&Msg, MSGFLAG_VITAL); + + // update state + m_aShowOthers[g_Config.m_ClDummy] = SHOW_OTHERS_ONLY_TEAM; + } + else if(m_aShowOthers[g_Config.m_ClDummy] == SHOW_OTHERS_NOT_SET || m_aShowOthers[g_Config.m_ClDummy] != g_Config.m_ClShowOthers) { { CNetMsg_Cl_ShowOthers Msg; @@ -3350,3 +3393,214 @@ void CGameClient::SnapCollectEntities() m_vSnapEntities.push_back({Ent.m_Item, Ent.m_pData, pDataEx}); } } + +void CGameClient::HandleMultiView() +{ + bool IsTeamZero = IsMultiViewIdSet(); + bool Init = false; + int AmountPlayers = 0; + vec2 Minpos, Maxpos; + float TmpVel = 0.0f; + + for(int i = 0; i < MAX_CLIENTS; i++) + { + // look at players who are vanished + if(m_MultiView.m_aVanish[i]) + { + // not in freeze anymore and the delay is over + if(m_MultiView.m_aLastFreeze[i] + 6.0f <= Client()->LocalTime() && m_aClients[i].m_FreezeEnd == 0) + { + m_MultiView.m_aVanish[i] = false; + m_MultiView.m_aLastFreeze[i] = 0.0f; + } + } + + // we look at team 0 and the player is not in the spec list + if(IsTeamZero && !m_aMultiViewId[i]) + continue; + + // player is vanished + if(m_MultiView.m_aVanish[i]) + continue; + + // the player is not in the team we are spectating + if(m_Teams.Team(i) != m_MultiView.m_Team) + continue; + + vec2 PlayerPos; + if(m_Snap.m_aCharacters[i].m_Active) + PlayerPos = vec2(m_aClients[i].m_RenderPos.x, m_aClients[i].m_RenderPos.y); + else if(m_aClients[i].m_Spec) // tee is in spec + PlayerPos = m_aClients[i].m_SpecChar; + else + continue; + + // player is far away and frozen + if(distance(m_MultiView.m_OldPos, PlayerPos) > 1100 && m_aClients[i].m_FreezeEnd != 0) + { + // check if the player is frozen for more than 3 seconds, if so vanish him + if(m_MultiView.m_aLastFreeze[i] == 0.0f) + m_MultiView.m_aLastFreeze[i] = Client()->LocalTime(); + else if(m_MultiView.m_aLastFreeze[i] + 3.0f <= Client()->LocalTime()) + m_MultiView.m_aVanish[i] = true; + } + else if(m_MultiView.m_aLastFreeze[i] != 0) + m_MultiView.m_aLastFreeze[i] = 0; + + // set the minimum and maximum position + if(!Init) + { + Minpos = PlayerPos; + Maxpos = PlayerPos; + Init = true; + } + else + { + Minpos.x = std::min(Minpos.x, PlayerPos.x); + Maxpos.x = std::max(Maxpos.x, PlayerPos.x); + Minpos.y = std::min(Minpos.y, PlayerPos.y); + Maxpos.y = std::max(Maxpos.y, PlayerPos.y); + } + + // sum up the velocity of all players we are spectating + const CNetObj_Character &CurrentCharacter = m_Snap.m_aCharacters[i].m_Cur; + TmpVel += (length(vec2(CurrentCharacter.m_VelX / 256.0f, CurrentCharacter.m_VelY / 256.0f)) * 50) / 32.0f; + AmountPlayers++; + } + + // if we have found no players, we disable multi view + if(AmountPlayers == 0) + { + m_MultiViewActivated = false; + return; + } + + vec2 TargetPos = vec2((Minpos.x + Maxpos.x) / 2.0f, (Minpos.y + Maxpos.y) / 2.0f); + // dont hide the position hud if its only one player + m_MultiViewShowHud = AmountPlayers == 1; + // get the average velocity + float AvgVel = clamp(TmpVel / AmountPlayers ? TmpVel / (float)AmountPlayers : 0.0f, 0.0f, 1000.0f); + + if(m_MultiView.m_OldPersonalZoom == m_MultiViewPersonalZoom) + m_Camera.SetZoom(CalculateMultiViewZoom(Minpos, Maxpos, AvgVel), g_Config.m_ClMultiViewZoomSmoothness); + else + m_Camera.SetZoom(CalculateMultiViewZoom(Minpos, Maxpos, AvgVel), 50); + + m_Snap.m_SpecInfo.m_Position = m_MultiView.m_OldPos + ((TargetPos - m_MultiView.m_OldPos) * CalculateMultiViewMultiplier(TargetPos)); + m_MultiView.m_OldPos = m_Snap.m_SpecInfo.m_Position; + m_Snap.m_SpecInfo.m_UsePosition = true; +} + +bool CGameClient::InitMultiViewFromFreeview(int Team) +{ + float Width, Height; + CleanMultiViewIds(); + m_MultiView.m_IsInit = true; + + // get the current view coordinates + RenderTools()->CalcScreenParams(Graphics()->ScreenAspect(), m_Camera.m_Zoom, &Width, &Height); + vec2 AxisX = vec2(m_Camera.m_Center.x - (Width / 2), m_Camera.m_Center.x + (Width / 2)); + vec2 AxisY = vec2(m_Camera.m_Center.y - (Height / 2), m_Camera.m_Center.y + (Height / 2)); + + m_MultiView.m_Team = Team; + + if(Team > 0) + return true; // spectating a team, not necessary to search the players in view + else + { + int Count = 0; + for(int i = 0; i < MAX_CLIENTS; i++) + { + vec2 PlayerPos; + + // get the position of the player + if(m_Snap.m_aCharacters[i].m_Active) + PlayerPos = vec2(m_Snap.m_aCharacters[i].m_Cur.m_X, m_Snap.m_aCharacters[i].m_Cur.m_Y); + else if(m_aClients[i].m_Spec) + PlayerPos = m_aClients[i].m_SpecChar; + else + continue; + + // player isnt in the correct team + if(m_Teams.Team(i) != Team) + continue; + + if(PlayerPos.x != 0 && PlayerPos.y != 0) + { + // is the player in view + if(PlayerPos.x > AxisX.x && PlayerPos.x < AxisX.y && PlayerPos.y > AxisY.x && PlayerPos.y < AxisY.y) + { + m_aMultiViewId[i] = true; + Count++; + } + } + } + + // we are spectating only one player + m_MultiView.m_Solo = Count == 1; + + // found players to spectate + return Count > 0; + } +} + +float CGameClient::CalculateMultiViewMultiplier(vec2 CameraPos) +{ + float MaxCameraDist = 200.0f; + float MinCameraDist = 20.0f; + float MaxVel = 0.1f; + float MinVel = 0.007f; + + float CurrentCameraDistance = distance(m_MultiView.m_OldPos, CameraPos); + + return clamp(MapValue(MaxCameraDist, MinCameraDist, MaxVel, MinVel, CurrentCameraDistance), MinVel, 1.0f); +} + +float CGameClient::CalculateMultiViewZoom(vec2 MinPos, vec2 MaxPos, float Vel) +{ + float Ratio = Graphics()->ScreenAspect(); + float ZoomX = 0.0f, ZoomY; + + // only calc two axis if the aspect ratio is not 1:1 + if(Ratio != 1.0f) + ZoomX = ZoomX = (0.001309f - 0.000328 * Ratio) * (MaxPos.x - MinPos.x) + (0.741413f - 0.032959 * Ratio); + + // calculate the according zoom with linear function + ZoomY = ZoomY = 0.001309f * (MaxPos.y - MinPos.y) + 0.741413f; + // choose the highest zoom + float Zoom = std::max(ZoomX, ZoomY); + // zoom out to maximum 10 percent of the current zoom for 70 velocity + float Diff = clamp(MapValue(70.0f, 15.0f, Zoom * 0.10f, 0.0f, Vel), 0.0f, Zoom * 0.10f); + // zoom should stay inbetween 1.1 and 20.0 + Zoom = clamp(Zoom + Diff, 1.1f, 20.0f); + // add the user preference + Zoom -= (Zoom * 0.075f) * m_MultiViewPersonalZoom; + m_MultiView.m_OldPersonalZoom = m_MultiViewPersonalZoom; + + return Zoom; +} + +float CGameClient::MapValue(float MaxValue, float MinValue, float MaxRange, float MinRange, float Value) +{ + return (MaxRange - MinRange) / (MaxValue - MinValue) * (Value - MinValue) + MinRange; +} + +void CGameClient::ResetMultiView() +{ + m_MultiView.m_Solo = false; + m_MultiView.m_IsInit = false; + m_MultiViewActivated = false; + m_MultiViewPersonalZoom = 0; +} + +void CGameClient::CleanMultiViewIds() +{ + std::fill(std::begin(m_aMultiViewId), std::end(m_aMultiViewId), false); + std::fill(std::begin(m_MultiView.m_aLastFreeze), std::end(m_MultiView.m_aLastFreeze), 0.0f); + std::fill(std::begin(m_MultiView.m_aVanish), std::end(m_MultiView.m_aVanish), false); +} + +bool CGameClient::IsMultiViewIdSet() +{ + return std::any_of(std::begin(m_aMultiViewId), std::end(m_aMultiViewId), [](bool IsSet) { return IsSet; }); +} diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h index 222cf40dd..3f5ca18c9 100644 --- a/src/game/client/gameclient.h +++ b/src/game/client/gameclient.h @@ -698,6 +698,11 @@ public: const std::vector &SnapEntities() { return m_vSnapEntities; } + int m_MultiViewPersonalZoom; + bool m_MultiViewShowHud; + bool m_MultiViewActivated; + bool m_aMultiViewId[MAX_CLIENTS]; + private: std::vector m_vSnapEntities; void SnapCollectEntities(); @@ -726,6 +731,28 @@ private: float m_LastZoom; float m_LastScreenAspect; bool m_LastDummyConnected; + + void ResetMultiView(); + void HandleMultiView(); + bool IsMultiViewIdSet(); + void CleanMultiViewIds(); + bool InitMultiViewFromFreeview(int Team); + float CalculateMultiViewMultiplier(vec2 CameraPos); + float CalculateMultiViewZoom(vec2 MinPos, vec2 MaxPos, float Vel); + float MapValue(float MaxValue, float MinValue, float MaxRange, float MinRange, float Value); + + struct SMultiView + { + bool m_Solo; + bool m_IsInit; + bool m_aVanish[MAX_CLIENTS]; + vec2 m_OldPos; + int m_Team; + int m_OldPersonalZoom; + float m_aLastFreeze[MAX_CLIENTS]; + }; + + SMultiView m_MultiView; }; ColorRGBA CalculateNameColor(ColorHSLA TextColorHSL); diff --git a/src/game/variables.h b/src/game/variables.h index d1dcf7189..8bfcb11d9 100644 --- a/src/game/variables.h +++ b/src/game/variables.h @@ -90,6 +90,8 @@ MACRO_CONFIG_INT(ClDyncamFollowFactor, cl_dyncam_follow_factor, 60, 0, 200, CFGF MACRO_CONFIG_INT(ClDyncamSmoothness, cl_dyncam_smoothness, 0, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Transition amount of the camera movement, 0=instant, 100=slow and smooth") MACRO_CONFIG_INT(ClDyncamStabilizing, cl_dyncam_stabilizing, 0, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Amount of camera slowdown during fast cursor movement. High value can cause delay in camera movement") +MACRO_CONFIG_INT(ClMultiViewZoomSmoothness, cl_multi_view_zoom_smoothness, 700, 0, 5000, CFGFLAG_CLIENT, "Set the smoothness of the multi view zoom (in ms, higher = slower)") + MACRO_CONFIG_INT(EdSmoothZoomTime, ed_smooth_zoom_time, 250, 0, 5000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Time of smooth zoom animation in the editor in ms (0 for off)") MACRO_CONFIG_INT(EdLimitMaxZoomLevel, ed_limit_max_zoom_level, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Specifies, if zooming in the editor should be limited or not (0 = no limit)") MACRO_CONFIG_INT(EdZoomTarget, ed_zoom_target, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Zoom to the current mouse target")