mirror of
https://github.com/ddnet/ddnet.git
synced 2024-09-21 18:14:19 +00:00
49bc150afd
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.
471 lines
14 KiB
C++
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);
|
|
}
|