mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 10:08:18 +00:00
Add multi view
This commit is contained in:
parent
422ff844b8
commit
d16fb877dc
|
@ -31,7 +31,7 @@ float CCamera::ZoomProgress(float CurrentTime) const
|
||||||
void CCamera::ScaleZoom(float Factor)
|
void CCamera::ScaleZoom(float Factor)
|
||||||
{
|
{
|
||||||
float CurrentTarget = m_Zooming ? m_ZoomSmoothingTarget : m_Zoom;
|
float CurrentTarget = m_Zooming ? m_ZoomSmoothingTarget : m_Zoom;
|
||||||
ChangeZoom(CurrentTarget * Factor);
|
ChangeZoom(CurrentTarget * Factor, g_Config.m_ClSmoothZoomTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
float CCamera::MaxZoomLevel()
|
float CCamera::MaxZoomLevel()
|
||||||
|
@ -44,7 +44,7 @@ float CCamera::MinZoomLevel()
|
||||||
return 0.01f;
|
return 0.01f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CCamera::ChangeZoom(float Target)
|
void CCamera::ChangeZoom(float Target, int Smoothness)
|
||||||
{
|
{
|
||||||
if(Target > MaxZoomLevel() || Target < MinZoomLevel())
|
if(Target > MaxZoomLevel() || Target < MinZoomLevel())
|
||||||
{
|
{
|
||||||
|
@ -64,7 +64,7 @@ void CCamera::ChangeZoom(float Target)
|
||||||
m_ZoomSmoothingTarget = Target;
|
m_ZoomSmoothingTarget = Target;
|
||||||
m_ZoomSmoothing = CCubicBezier::With(Current, Derivative, 0, m_ZoomSmoothingTarget);
|
m_ZoomSmoothing = CCubicBezier::With(Current, Derivative, 0, m_ZoomSmoothingTarget);
|
||||||
m_ZoomSmoothingStart = Now;
|
m_ZoomSmoothingStart = Now;
|
||||||
m_ZoomSmoothingEnd = Now + (float)g_Config.m_ClSmoothZoomTime / 1000;
|
m_ZoomSmoothingEnd = Now + (float)Smoothness / 1000;
|
||||||
|
|
||||||
m_Zooming = true;
|
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)
|
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);
|
pSelf->ScaleZoom(ZoomStep);
|
||||||
|
|
||||||
|
if(pSelf->GameClient()->m_MultiViewActivated)
|
||||||
|
pSelf->GameClient()->m_MultiViewPersonalZoom++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void CCamera::ConZoomMinus(IConsole::IResult *pResult, void *pUserData)
|
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)
|
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);
|
pSelf->ScaleZoom(1 / ZoomStep);
|
||||||
|
|
||||||
|
if(pSelf->GameClient()->m_MultiViewActivated)
|
||||||
|
pSelf->GameClient()->m_MultiViewPersonalZoom--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void CCamera::ConZoom(IConsole::IResult *pResult, void *pUserData)
|
void CCamera::ConZoom(IConsole::IResult *pResult, void *pUserData)
|
||||||
{
|
{
|
||||||
|
CCamera *pSelf = (CCamera *)pUserData;
|
||||||
float TargetLevel = pResult->NumArguments() ? pResult->GetFloat(0) : g_Config.m_ClDefaultZoom;
|
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)
|
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(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));
|
clamp(pResult->GetInteger(1) * 32.0f, 200.0f, pSelf->Collision()->GetWidth() * 32 - 200.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CCamera::SetZoom(float Target, int Smoothness)
|
||||||
|
{
|
||||||
|
ChangeZoom(Target, Smoothness);
|
||||||
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ class CCamera : public CComponent
|
||||||
float m_ZoomSmoothingEnd;
|
float m_ZoomSmoothingEnd;
|
||||||
|
|
||||||
void ScaleZoom(float Factor);
|
void ScaleZoom(float Factor);
|
||||||
void ChangeZoom(float Target);
|
void ChangeZoom(float Target, int Smoothness);
|
||||||
float ZoomProgress(float CurrentTime) const;
|
float ZoomProgress(float CurrentTime) const;
|
||||||
|
|
||||||
float MinZoomLevel();
|
float MinZoomLevel();
|
||||||
|
@ -52,6 +52,8 @@ public:
|
||||||
virtual void OnConsoleInit() override;
|
virtual void OnConsoleInit() override;
|
||||||
virtual void OnReset() override;
|
virtual void OnReset() override;
|
||||||
|
|
||||||
|
void SetZoom(float Target, int Smoothness);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void ConZoomPlus(IConsole::IResult *pResult, void *pUserData);
|
static void ConZoomPlus(IConsole::IResult *pResult, void *pUserData);
|
||||||
static void ConZoomMinus(IConsole::IResult *pResult, void *pUserData);
|
static void ConZoomMinus(IConsole::IResult *pResult, void *pUserData);
|
||||||
|
|
|
@ -106,7 +106,7 @@ void CHud::RenderGameTimer()
|
||||||
}
|
}
|
||||||
else if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_RACETIME)
|
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();
|
Time = (Client()->GameTick(g_Config.m_ClDummy) + m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer) / Client()->GameTickSpeed();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1474,7 +1474,7 @@ void CHud::RenderSpectatorHud()
|
||||||
|
|
||||||
// draw the text
|
// draw the text
|
||||||
char aBuf[128];
|
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);
|
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);
|
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);
|
RenderPlayerState(SpectatorID);
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,6 +149,17 @@ void CSpectator::ConSpectateClosest(IConsole::IResult *pResult, void *pUserData)
|
||||||
pSelf->Spectate(NewSpectatorID);
|
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()
|
CSpectator::CSpectator()
|
||||||
{
|
{
|
||||||
OnReset();
|
OnReset();
|
||||||
|
@ -162,6 +173,7 @@ void CSpectator::OnConsoleInit()
|
||||||
Console()->Register("spectate_next", "", CFGFLAG_CLIENT, ConSpectateNext, this, "Spectate the next player");
|
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_previous", "", CFGFLAG_CLIENT, ConSpectatePrevious, this, "Spectate the previous player");
|
||||||
Console()->Register("spectate_closest", "", CFGFLAG_CLIENT, ConSpectateClosest, this, "Spectate the closest 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)
|
bool CSpectator::OnCursorMove(float x, float y, IInput::ECursorType CursorType)
|
||||||
|
@ -186,7 +198,12 @@ void CSpectator::OnRender()
|
||||||
if(m_WasActive)
|
if(m_WasActive)
|
||||||
{
|
{
|
||||||
if(m_SelectedSpectatorID != NO_SELECTION)
|
if(m_SelectedSpectatorID != NO_SELECTION)
|
||||||
|
{
|
||||||
|
if(m_SelectedSpectatorID != MULTI_VIEW)
|
||||||
Spectate(m_SelectedSpectatorID);
|
Spectate(m_SelectedSpectatorID);
|
||||||
|
|
||||||
|
GameClient()->m_MultiViewActivated = m_SelectedSpectatorID == MULTI_VIEW;
|
||||||
|
}
|
||||||
m_WasActive = false;
|
m_WasActive = false;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -213,6 +230,7 @@ void CSpectator::OnRender()
|
||||||
float TeeSizeMod = 1.0f;
|
float TeeSizeMod = 1.0f;
|
||||||
float RoundRadius = 30.0f;
|
float RoundRadius = 30.0f;
|
||||||
bool Selected = false;
|
bool Selected = false;
|
||||||
|
bool MultiViewSelected = false;
|
||||||
int TotalPlayers = 0;
|
int TotalPlayers = 0;
|
||||||
int PerLine = 8;
|
int PerLine = 8;
|
||||||
float BoxMove = -10.0f;
|
float BoxMove = -10.0f;
|
||||||
|
@ -253,34 +271,48 @@ void CSpectator::OnRender()
|
||||||
if((Client()->State() == IClient::STATE_DEMOPLAYBACK && m_pClient->m_DemoSpecID == SPEC_FREEVIEW) ||
|
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))
|
(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)
|
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_SelectorMouse.y >= -280.0f && m_SelectorMouse.y <= -220.0f)
|
||||||
{
|
{
|
||||||
m_SelectedSpectatorID = SPEC_FREEVIEW;
|
m_SelectedSpectatorID = SPEC_FREEVIEW;
|
||||||
Selected = true;
|
Selected = true;
|
||||||
}
|
}
|
||||||
TextRender()->TextColor(1.0f, 1.0f, 1.0f, Selected ? 1.0f : 0.5f);
|
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)
|
if(Client()->State() == IClient::STATE_DEMOPLAYBACK && m_pClient->m_Snap.m_LocalClientID >= 0)
|
||||||
{
|
{
|
||||||
Selected = false;
|
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_SelectorMouse.y >= -280.0f && m_SelectorMouse.y <= -220.0f)
|
||||||
{
|
{
|
||||||
m_SelectedSpectatorID = SPEC_FOLLOW;
|
m_SelectedSpectatorID = SPEC_FOLLOW;
|
||||||
Selected = true;
|
Selected = true;
|
||||||
}
|
}
|
||||||
TextRender()->TextColor(1.0f, 1.0f, 1.0f, Selected ? 1.0f : 0.5f);
|
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;
|
float x = -(ObjWidth - 35.0f), y = StartY;
|
||||||
|
|
|
@ -10,6 +10,7 @@ class CSpectator : public CComponent
|
||||||
{
|
{
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
|
MULTI_VIEW = -4,
|
||||||
NO_SELECTION = -3,
|
NO_SELECTION = -3,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -30,6 +31,7 @@ class CSpectator : public CComponent
|
||||||
static void ConSpectateNext(IConsole::IResult *pResult, void *pUserData);
|
static void ConSpectateNext(IConsole::IResult *pResult, void *pUserData);
|
||||||
static void ConSpectatePrevious(IConsole::IResult *pResult, void *pUserData);
|
static void ConSpectatePrevious(IConsole::IResult *pResult, void *pUserData);
|
||||||
static void ConSpectateClosest(IConsole::IResult *pResult, void *pUserData);
|
static void ConSpectateClosest(IConsole::IResult *pResult, void *pUserData);
|
||||||
|
static void ConMultiView(IConsole::IResult *pResult, void *pUserData);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CSpectator();
|
CSpectator();
|
||||||
|
|
|
@ -604,7 +604,11 @@ void CGameClient::UpdatePositions()
|
||||||
// spectator position
|
// spectator position
|
||||||
if(m_Snap.m_SpecInfo.m_Active)
|
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(
|
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),
|
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();
|
UpdateRenderedCharacters();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CGameClient::OnRender()
|
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
|
// update the local character and spectate position
|
||||||
UpdatePositions();
|
UpdatePositions();
|
||||||
|
|
||||||
|
@ -843,6 +864,18 @@ void CGameClient::OnMessage(int MsgId, CUnpacker *pUnpacker, int Conn, bool Dumm
|
||||||
pChar->ResetPrediction();
|
pChar->ResetPrediction();
|
||||||
m_GameWorld.ReleaseHooked(pMsg->m_Victim);
|
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;
|
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;
|
CNetMsg_Cl_ShowOthers Msg;
|
||||||
|
@ -3350,3 +3393,214 @@ void CGameClient::SnapCollectEntities()
|
||||||
m_vSnapEntities.push_back({Ent.m_Item, Ent.m_pData, pDataEx});
|
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; });
|
||||||
|
}
|
||||||
|
|
|
@ -698,6 +698,11 @@ public:
|
||||||
|
|
||||||
const std::vector<CSnapEntities> &SnapEntities() { return m_vSnapEntities; }
|
const std::vector<CSnapEntities> &SnapEntities() { return m_vSnapEntities; }
|
||||||
|
|
||||||
|
int m_MultiViewPersonalZoom;
|
||||||
|
bool m_MultiViewShowHud;
|
||||||
|
bool m_MultiViewActivated;
|
||||||
|
bool m_aMultiViewId[MAX_CLIENTS];
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<CSnapEntities> m_vSnapEntities;
|
std::vector<CSnapEntities> m_vSnapEntities;
|
||||||
void SnapCollectEntities();
|
void SnapCollectEntities();
|
||||||
|
@ -726,6 +731,28 @@ private:
|
||||||
float m_LastZoom;
|
float m_LastZoom;
|
||||||
float m_LastScreenAspect;
|
float m_LastScreenAspect;
|
||||||
bool m_LastDummyConnected;
|
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);
|
ColorRGBA CalculateNameColor(ColorHSLA TextColorHSL);
|
||||||
|
|
|
@ -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(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(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(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(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")
|
MACRO_CONFIG_INT(EdZoomTarget, ed_zoom_target, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Zoom to the current mouse target")
|
||||||
|
|
Loading…
Reference in a new issue