ddnet/src/game/client/components/spectator.cpp
heinrich5991 49bc150afd Refactor: Distinguish between two concepts previously called "Dummy"
On the one hand variables called "Dummy" would tell us whether the
current action refers to the currently inactive tee ("dummy"). On the
other hand, these variables could tell us whether the current action
refers to the main connection to the server, or the secondary one. The
latter use case is now renamed to "Client", with the choices
`CLIENT_MAIN`, `CLIENT_DUMMY` (and `CLIENT_CONTACT`).

Perhaps better names could be found, especially since `Client` also
refers to the engine client class in the game code.

I tried to not fix bugs unless it would complicate the code.
2022-01-21 01:54:14 +01:00

471 lines
14 KiB
C++

/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#include <limits.h>
#include <engine/demo.h>
#include <engine/graphics.h>
#include <engine/shared/config.h>
#include <engine/textrender.h>
#include <game/generated/client_data.h>
#include <game/generated/protocol.h>
#include <game/client/animstate.h>
#include <game/client/render.h>
#include "camera.h"
#include "spectator.h"
#include <game/client/gameclient.h>
bool CSpectator::CanChangeSpectator()
{
// Don't change SpectatorID when not spectating
return m_pClient->m_Snap.m_SpecInfo.m_Active;
}
void CSpectator::SpectateNext(bool Reverse)
{
int CurIndex = -1;
const CNetObj_PlayerInfo **paPlayerInfos = m_pClient->m_Snap.m_paInfoByDDTeamName;
// m_SpectatorID may be uninitialized if m_Active is false
if(m_pClient->m_Snap.m_SpecInfo.m_Active)
{
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(paPlayerInfos[i] && paPlayerInfos[i]->m_ClientID == m_pClient->m_Snap.m_SpecInfo.m_SpectatorID)
{
CurIndex = i;
break;
}
}
}
int Start;
if(CurIndex != -1)
{
if(Reverse)
Start = CurIndex - 1;
else
Start = CurIndex + 1;
}
else
{
if(Reverse)
Start = -1;
else
Start = 0;
}
int Increment = Reverse ? -1 : 1;
for(int i = 0; i < MAX_CLIENTS; i++)
{
int PlayerIndex = (Start + i * Increment) % MAX_CLIENTS;
// % in C++ takes the sign of the dividend, not divisor
if(PlayerIndex < 0)
PlayerIndex += MAX_CLIENTS;
const CNetObj_PlayerInfo *pPlayerInfo = paPlayerInfos[PlayerIndex];
if(pPlayerInfo && pPlayerInfo->m_Team != TEAM_SPECTATORS)
{
Spectate(pPlayerInfo->m_ClientID);
break;
}
}
}
void CSpectator::ConKeySpectator(IConsole::IResult *pResult, void *pUserData)
{
CSpectator *pSelf = (CSpectator *)pUserData;
if(pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active || pSelf->Client()->State() == IClient::STATE_DEMOPLAYBACK)
pSelf->m_Active = pResult->GetInteger(0) != 0;
else
pSelf->m_Active = false;
}
void CSpectator::ConSpectate(IConsole::IResult *pResult, void *pUserData)
{
CSpectator *pSelf = (CSpectator *)pUserData;
if(!pSelf->CanChangeSpectator())
return;
pSelf->Spectate(pResult->GetInteger(0));
}
void CSpectator::ConSpectateNext(IConsole::IResult *pResult, void *pUserData)
{
CSpectator *pSelf = (CSpectator *)pUserData;
if(!pSelf->CanChangeSpectator())
return;
pSelf->SpectateNext(false);
}
void CSpectator::ConSpectatePrevious(IConsole::IResult *pResult, void *pUserData)
{
CSpectator *pSelf = (CSpectator *)pUserData;
if(!pSelf->CanChangeSpectator())
return;
pSelf->SpectateNext(true);
}
void CSpectator::ConSpectateClosest(IConsole::IResult *pResult, void *pUserData)
{
CSpectator *pSelf = (CSpectator *)pUserData;
if(!pSelf->CanChangeSpectator())
return;
CGameClient::CSnapState &Snap = pSelf->m_pClient->m_Snap;
int SpectatorID = Snap.m_SpecInfo.m_SpectatorID;
int NewSpectatorID = -1;
vec2 CurPosition(pSelf->m_pClient->m_Camera.m_Center);
if(SpectatorID != SPEC_FREEVIEW)
{
const CNetObj_Character &CurCharacter = Snap.m_aCharacters[SpectatorID].m_Cur;
CurPosition.x = CurCharacter.m_X;
CurPosition.y = CurCharacter.m_Y;
}
int ClosestDistance = INT_MAX;
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(i == SpectatorID || !Snap.m_paPlayerInfos[i] || Snap.m_paPlayerInfos[i]->m_Team == TEAM_SPECTATORS)
continue;
const CNetObj_Character &MaybeClosestCharacter = Snap.m_aCharacters[i].m_Cur;
int Distance = distance(CurPosition, vec2(MaybeClosestCharacter.m_X, MaybeClosestCharacter.m_Y));
if(NewSpectatorID == -1 || Distance < ClosestDistance)
{
NewSpectatorID = i;
ClosestDistance = Distance;
}
}
if(NewSpectatorID > -1)
pSelf->Spectate(NewSpectatorID);
}
CSpectator::CSpectator()
{
OnReset();
m_OldMouseX = m_OldMouseY = 0.0f;
}
void CSpectator::OnConsoleInit()
{
Console()->Register("+spectate", "", CFGFLAG_CLIENT, ConKeySpectator, this, "Open spectator mode selector");
Console()->Register("spectate", "i[spectator-id]", CFGFLAG_CLIENT, ConSpectate, this, "Switch spectator mode");
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");
}
bool CSpectator::OnMouseMove(float x, float y)
{
if(!m_Active)
return false;
UI()->ConvertMouseMove(&x, &y);
m_SelectorMouse += vec2(x, y);
return true;
}
void CSpectator::OnRelease()
{
OnReset();
}
void CSpectator::OnRender()
{
if(!m_Active)
{
if(m_WasActive)
{
if(m_SelectedSpectatorID != NO_SELECTION)
Spectate(m_SelectedSpectatorID);
m_WasActive = false;
}
return;
}
if(!m_pClient->m_Snap.m_SpecInfo.m_Active && Client()->State() != IClient::STATE_DEMOPLAYBACK)
{
m_Active = false;
m_WasActive = false;
return;
}
m_WasActive = true;
m_SelectedSpectatorID = NO_SELECTION;
// draw background
float Width = 400 * 3.0f * Graphics()->ScreenAspect();
float Height = 400 * 3.0f;
float ObjWidth = 300.0f;
float FontSize = 20.0f;
float BigFontSize = 20.0f;
float StartY = -190.0f;
float LineHeight = 60.0f;
float TeeSizeMod = 1.0f;
float RoundRadius = 30.0f;
bool Selected = false;
int TotalPlayers = 0;
int PerLine = 8;
float BoxMove = -10.0f;
float BoxOffset = 0.0f;
for(auto &pInfo : m_pClient->m_Snap.m_paInfoByDDTeamName)
{
if(!pInfo || pInfo->m_Team == TEAM_SPECTATORS)
continue;
++TotalPlayers;
}
if(TotalPlayers > 32)
{
FontSize = 18.0f;
LineHeight = 30.0f;
TeeSizeMod = 0.7f;
PerLine = 16;
RoundRadius = 10.0f;
BoxMove = 3.0f;
BoxOffset = 6.0f;
}
if(TotalPlayers > 16)
{
ObjWidth = 600.0f;
}
Graphics()->MapScreen(0, 0, Width, Height);
Graphics()->BlendNormal();
Graphics()->TextureClear();
Graphics()->QuadsBegin();
Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.3f);
RenderTools()->DrawRoundRect(Width / 2.0f - ObjWidth, Height / 2.0f - 300.0f, ObjWidth * 2, 600.0f, 20.0f);
Graphics()->QuadsEnd();
// clamp mouse position to selector area
m_SelectorMouse.x = clamp(m_SelectorMouse.x, -(ObjWidth - 20.0f), ObjWidth - 20.0f);
m_SelectorMouse.y = clamp(m_SelectorMouse.y, -280.0f, 280.0f);
// draw selections
if((Client()->State() == IClient::STATE_DEMOPLAYBACK && m_pClient->m_DemoSpecID == SPEC_FREEVIEW) ||
m_pClient->m_Snap.m_SpecInfo.m_SpectatorID == SPEC_FREEVIEW)
{
Graphics()->TextureClear();
Graphics()->QuadsBegin();
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.25f);
RenderTools()->DrawRoundRect(Width / 2.0f - (ObjWidth - 20.0f), Height / 2.0f - 280.0f, 270.0f, 60.0f, 20.0f);
Graphics()->QuadsEnd();
}
if(Client()->State() == IClient::STATE_DEMOPLAYBACK && m_pClient->m_DemoSpecID == SPEC_FOLLOW)
{
Graphics()->TextureClear();
Graphics()->QuadsBegin();
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.25f);
RenderTools()->DrawRoundRect(Width / 2.0f - (ObjWidth - 310.0f), Height / 2.0f - 280.0f, 270.0f, 60.0f, 20.0f);
Graphics()->QuadsEnd();
}
if(m_SelectorMouse.x >= -(ObjWidth - 20.0f) && m_SelectorMouse.x <= -(ObjWidth - 290 + 10.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(0, Width / 2.0f - (ObjWidth - 60.0f), Height / 2.0f - 280.f + (60.f - BigFontSize) / 2.f, BigFontSize, Localize("Free-View"), -1.0f);
if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
{
Selected = false;
if(m_SelectorMouse.x > -(ObjWidth - 290.0f) && m_SelectorMouse.x <= -(ObjWidth - 590.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(0, Width / 2.0f - (ObjWidth - 350.0f), Height / 2.0f - 280.0f + (60.f - BigFontSize) / 2.f, BigFontSize, Localize("Follow"), -1.0f);
}
float x = -(ObjWidth - 35.0f), y = StartY;
int OldDDTeam = -1;
for(int i = 0, Count = 0; i < MAX_CLIENTS; ++i)
{
if(!m_pClient->m_Snap.m_paInfoByDDTeamName[i] || m_pClient->m_Snap.m_paInfoByDDTeamName[i]->m_Team == TEAM_SPECTATORS)
continue;
++Count;
if(Count == PerLine + 1 || (Count > PerLine + 1 && (Count - 1) % PerLine == 0))
{
x += 290.0f;
y = StartY;
}
const CNetObj_PlayerInfo *pInfo = m_pClient->m_Snap.m_paInfoByDDTeamName[i];
int DDTeam = m_pClient->m_Teams.Team(pInfo->m_ClientID);
int NextDDTeam = 0;
for(int j = i + 1; j < MAX_CLIENTS; j++)
{
const CNetObj_PlayerInfo *pInfo2 = m_pClient->m_Snap.m_paInfoByDDTeamName[j];
if(!pInfo2 || pInfo2->m_Team == TEAM_SPECTATORS)
continue;
NextDDTeam = m_pClient->m_Teams.Team(pInfo2->m_ClientID);
break;
}
if(OldDDTeam == -1)
{
for(int j = i - 1; j >= 0; j--)
{
const CNetObj_PlayerInfo *pInfo2 = m_pClient->m_Snap.m_paInfoByDDTeamName[j];
if(!pInfo2 || pInfo2->m_Team == TEAM_SPECTATORS)
continue;
OldDDTeam = m_pClient->m_Teams.Team(pInfo2->m_ClientID);
break;
}
}
if(DDTeam != TEAM_FLOCK)
{
Graphics()->TextureClear();
Graphics()->QuadsBegin();
ColorRGBA rgb = color_cast<ColorRGBA>(ColorHSLA(DDTeam / 64.0f, 1.0f, 0.5f, 0.5f));
Graphics()->SetColor(rgb);
int Corners = 0;
if(OldDDTeam != DDTeam)
Corners |= CUI::CORNER_TL | CUI::CORNER_TR;
if(NextDDTeam != DDTeam)
Corners |= CUI::CORNER_BL | CUI::CORNER_BR;
RenderTools()->DrawRoundRectExt(Width / 2.0f + x - 10.0f + BoxOffset, Height / 2.0f + y + BoxMove, 270.0f - BoxOffset, LineHeight, RoundRadius, Corners);
Graphics()->QuadsEnd();
}
OldDDTeam = DDTeam;
if((Client()->State() == IClient::STATE_DEMOPLAYBACK && m_pClient->m_DemoSpecID == m_pClient->m_Snap.m_paInfoByDDTeamName[i]->m_ClientID) || (Client()->State() != IClient::STATE_DEMOPLAYBACK && m_pClient->m_Snap.m_SpecInfo.m_SpectatorID == m_pClient->m_Snap.m_paInfoByDDTeamName[i]->m_ClientID))
{
Graphics()->TextureClear();
Graphics()->QuadsBegin();
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.25f);
RenderTools()->DrawRoundRect(Width / 2.0f + x - 10.0f + BoxOffset, Height / 2.0f + y + BoxMove, 270.0f - BoxOffset, LineHeight, RoundRadius);
Graphics()->QuadsEnd();
}
Selected = false;
if(m_SelectorMouse.x >= x - 10.0f && m_SelectorMouse.x < x + 260.0f &&
m_SelectorMouse.y >= y - (LineHeight / 6.0f) && m_SelectorMouse.y < y + (LineHeight * 5.0f / 6.0f))
{
m_SelectedSpectatorID = m_pClient->m_Snap.m_paInfoByDDTeamName[i]->m_ClientID;
Selected = true;
}
float TeeAlpha;
if(Client()->State() == IClient::STATE_DEMOPLAYBACK &&
!m_pClient->m_Snap.m_aCharacters[m_pClient->m_Snap.m_paInfoByDDTeamName[i]->m_ClientID].m_Active)
{
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 0.25f);
TeeAlpha = 0.5f;
}
else
{
TextRender()->TextColor(1.0f, 1.0f, 1.0f, Selected ? 1.0f : 0.5f);
TeeAlpha = 1.0f;
}
TextRender()->Text(0, Width / 2.0f + x + 50.0f, Height / 2.0f + y + BoxMove + (LineHeight - FontSize) / 2.f, FontSize, m_pClient->m_aClients[m_pClient->m_Snap.m_paInfoByDDTeamName[i]->m_ClientID].m_aName, 220.0f);
// flag
if(m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_FLAGS &&
m_pClient->m_Snap.m_pGameDataObj && (m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierRed == m_pClient->m_Snap.m_paInfoByDDTeamName[i]->m_ClientID || m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierBlue == m_pClient->m_Snap.m_paInfoByDDTeamName[i]->m_ClientID))
{
Graphics()->BlendNormal();
if(m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierBlue == m_pClient->m_Snap.m_paInfoByDDTeamName[i]->m_ClientID)
Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpriteFlagBlue);
else
Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpriteFlagRed);
Graphics()->QuadsBegin();
Graphics()->QuadsSetSubset(1, 0, 0, 1);
float Size = LineHeight;
IGraphics::CQuadItem QuadItem(Width / 2.0f + x - LineHeight / 5.0f, Height / 2.0f + y - LineHeight / 3.0f, Size / 2.0f, Size);
Graphics()->QuadsDrawTL(&QuadItem, 1);
Graphics()->QuadsEnd();
}
CTeeRenderInfo TeeInfo = m_pClient->m_aClients[m_pClient->m_Snap.m_paInfoByDDTeamName[i]->m_ClientID].m_RenderInfo;
TeeInfo.m_Size *= TeeSizeMod;
CAnimState *pIdleState = CAnimState::GetIdle();
vec2 OffsetToMid;
RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid);
vec2 TeeRenderPos(Width / 2.0f + x + 20.0f, Height / 2.0f + y + BoxMove + LineHeight / 2.0f + OffsetToMid.y);
RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos, TeeAlpha);
if(m_pClient->m_aClients[m_pClient->m_Snap.m_paInfoByDDTeamName[i]->m_ClientID].m_Friend)
{
ColorRGBA rgb = color_cast<ColorRGBA>(ColorHSLA(g_Config.m_ClMessageFriendColor));
TextRender()->TextColor(rgb.WithAlpha(1.f));
TextRender()->Text(0, Width / 2.0f + x - TeeInfo.m_Size / 2.0f, Height / 2.0f + y + BoxMove + (LineHeight - FontSize) / 2.f, FontSize, "", 220.0f);
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
}
y += LineHeight;
}
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
// draw cursor
Graphics()->WrapClamp();
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_CURSOR].m_Id);
Graphics()->QuadsBegin();
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
IGraphics::CQuadItem QuadItem(m_SelectorMouse.x + Width / 2.0f, m_SelectorMouse.y + Height / 2.0f, 48.0f, 48.0f);
Graphics()->QuadsDrawTL(&QuadItem, 1);
Graphics()->QuadsEnd();
Graphics()->WrapNormal();
}
void CSpectator::OnReset()
{
m_WasActive = false;
m_Active = false;
m_SelectedSpectatorID = NO_SELECTION;
}
void CSpectator::Spectate(int SpectatorID)
{
if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
{
m_pClient->m_DemoSpecID = clamp(SpectatorID, (int)SPEC_FOLLOW, MAX_CLIENTS - 1);
return;
}
if(m_pClient->m_Snap.m_SpecInfo.m_SpectatorID == SpectatorID)
return;
CNetMsg_Cl_SetSpectatorMode Msg;
Msg.m_SpectatorID = SpectatorID;
Client()->SendPackMsgActive(&Msg, MSGFLAG_VITAL);
}