ddnet/src/game/client/components/menus_ingame.cpp

1157 lines
36 KiB
C++
Raw Normal View History

2010-11-20 10:37:14 +00:00
/* (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. */
2010-05-29 07:25:38 +00:00
#include <base/math.h>
2022-05-18 16:00:05 +00:00
#include <base/system.h>
2010-08-09 12:14:15 +00:00
#include <engine/demo.h>
2011-03-23 12:06:35 +00:00
#include <engine/friends.h>
#include <engine/ghost.h>
#include <engine/graphics.h>
2010-05-29 07:25:38 +00:00
#include <engine/serverbrowser.h>
#include <engine/shared/config.h>
#include <engine/textrender.h>
2010-05-29 07:25:38 +00:00
#include <game/generated/client_data.h>
#include <game/generated/protocol.h>
#include <game/client/animstate.h>
#include <game/client/components/countryflags.h>
#include <game/client/gameclient.h>
#include <game/client/render.h>
#include <game/client/ui.h>
#include <game/localization.h>
2010-05-29 07:25:38 +00:00
#include "menus.h"
#include "motd.h"
#include "voting.h"
#include "ghost.h"
#include <engine/keys.h>
#include <engine/storage.h>
2022-05-18 16:00:05 +00:00
#include <chrono>
using namespace std::chrono_literals;
2010-05-29 07:25:38 +00:00
void CMenus::RenderGame(CUIRect MainView)
{
CUIRect Button, ButtonBar, ButtonBar2;
MainView.HSplitTop(45.0f, &ButtonBar, &MainView);
RenderTools()->DrawUIRect(&ButtonBar, ms_ColorTabbarActive, CUI::CORNER_B, 10.0f);
// button bar
ButtonBar.HSplitTop(10.0f, 0, &ButtonBar);
ButtonBar.HSplitTop(25.0f, &ButtonBar, 0);
ButtonBar.VMargin(10.0f, &ButtonBar);
ButtonBar.HSplitTop(30.0f, 0, &ButtonBar2);
ButtonBar2.HSplitTop(25.0f, &ButtonBar2, 0);
ButtonBar.VSplitRight(120.0f, &ButtonBar, &Button);
2010-05-29 07:25:38 +00:00
static int s_DisconnectButton = 0;
if(DoButton_Menu(&s_DisconnectButton, Localize("Disconnect"), 0, &Button))
{
if(Client()->GetCurrentRaceTime() / 60 >= g_Config.m_ClConfirmDisconnectTime && g_Config.m_ClConfirmDisconnectTime >= 0)
{
m_Popup = POPUP_DISCONNECT;
}
else
{
Client()->Disconnect();
}
}
ButtonBar.VSplitRight(5.0f, &ButtonBar, 0);
ButtonBar.VSplitRight(170.0f, &ButtonBar, &Button);
bool DummyConnecting = Client()->DummyConnecting();
static int s_DummyButton = 0;
if(!Client()->DummyAllowed())
{
DoButton_Menu(&s_DummyButton, Localize("Connect Dummy"), 1, &Button, 0, 15, 5.0f, 0.0f, vec4(1.0f, 0.5f, 0.5f, 0.75f), vec4(1, 0.5f, 0.5f, 0.5f));
}
else if(DummyConnecting)
{
DoButton_Menu(&s_DummyButton, Localize("Connecting dummy"), 1, &Button);
}
else if(DoButton_Menu(&s_DummyButton, Client()->DummyConnected() ? Localize("Disconnect Dummy") : Localize("Connect Dummy"), 0, &Button))
{
if(!Client()->DummyConnected())
{
Client()->DummyConnect();
}
else
{
if(Client()->GetCurrentRaceTime() / 60 >= g_Config.m_ClConfirmDisconnectTime && g_Config.m_ClConfirmDisconnectTime >= 0)
{
m_Popup = POPUP_DISCONNECT_DUMMY;
}
else
{
Client()->DummyDisconnect(0);
SetActive(false);
}
}
}
ButtonBar.VSplitRight(5.0f, &ButtonBar, 0);
ButtonBar.VSplitRight(140.0f, &ButtonBar, &Button);
static int s_DemoButton = 0;
bool Recording = DemoRecorder(RECORDER_MANUAL)->IsRecording();
if(DoButton_Menu(&s_DemoButton, Recording ? Localize("Stop record") : Localize("Record demo"), 0, &Button))
{
if(!Recording)
Client()->DemoRecorder_Start(Client()->GetCurrentMap(), true, RECORDER_MANUAL);
else
Client()->DemoRecorder_Stop(RECORDER_MANUAL);
}
static int s_SpectateButton = 0;
static int s_JoinRedButton = 0;
static int s_JoinBlueButton = 0;
Out of bounds access in CMenus::RenderGame src/game/client/components/menus_ingame.cpp:108:16: runtime error: index -1 out of bounds for type 'CGameClient::CClientData [64]' #0 0x55733e6a64c4 in CMenus::RenderGame(CUIRect) /media/ddnet/src/game/client/components/menus_ingame.cpp:108:73 #1 0x55733e632ba7 in CMenus::Render() /media/ddnet/src/game/client/components/menus.cpp:1228:6 #2 0x55733e64b141 in CMenus::OnRender() /media/ddnet/src/game/client/components/menus.cpp:2338:2 #3 0x55733e815706 in CGameClient::OnRender() /media/ddnet/src/game/client/gameclient.cpp:676:28 #4 0x55733e2c959e in CClient::Render() /media/ddnet/src/engine/client/client.cpp:1134:16 #5 0x55733e2fe90f in CClient::Run() /media/ddnet/src/engine/client/client.cpp:3285:7 #6 0x55733e31eb2e in main /media/ddnet/src/engine/client/client.cpp:4341:11 #7 0x7f9a2057e151 in __libc_start_main (/usr/lib/libc.so.6+0x28151) #8 0x55733e06be0d in _start (/media/ddnet/DDNet+0x705e0d) SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior src/game/client/components/menus_ingame.cpp:108:16 in src/game/client/components/menus_ingame.cpp:109:14: runtime error: index -1 out of bounds for type 'CGameClient::CClientData [64]' #0 0x55733e6a699a in CMenus::RenderGame(CUIRect) /media/ddnet/src/game/client/components/menus_ingame.cpp:109:71 #1 0x55733e632ba7 in CMenus::Render() /media/ddnet/src/game/client/components/menus.cpp:1228:6 #2 0x55733e64b141 in CMenus::OnRender() /media/ddnet/src/game/client/components/menus.cpp:2338:2 #3 0x55733e815706 in CGameClient::OnRender() /media/ddnet/src/game/client/gameclient.cpp:676:28 #4 0x55733e2c959e in CClient::Render() /media/ddnet/src/engine/client/client.cpp:1134:16 #5 0x55733e2fe90f in CClient::Run() /media/ddnet/src/engine/client/client.cpp:3285:7 #6 0x55733e31eb2e in main /media/ddnet/src/engine/client/client.cpp:4341:11 #7 0x7f9a2057e151 in __libc_start_main (/usr/lib/libc.so.6+0x28151) #8 0x55733e06be0d in _start (/media/ddnet/DDNet+0x705e0d)
2020-10-10 10:43:05 +00:00
bool Paused = false;
bool Spec = false;
if(m_pClient->m_Snap.m_LocalClientID >= 0)
{
Paused = m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_Paused;
Spec = m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_Spec;
}
if(m_pClient->m_Snap.m_pLocalInfo && m_pClient->m_Snap.m_pGameInfoObj && !Paused && !Spec)
{
2011-01-03 11:50:38 +00:00
if(m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_SPECTATORS)
{
ButtonBar.VSplitLeft(5.0f, 0, &ButtonBar);
ButtonBar.VSplitLeft(120.0f, &Button, &ButtonBar);
if(!DummyConnecting && DoButton_Menu(&s_SpectateButton, Localize("Spectate"), 0, &Button))
{
if(g_Config.m_ClDummy == 0 || Client()->DummyConnected())
{
m_pClient->SendSwitchTeam(TEAM_SPECTATORS);
SetActive(false);
}
}
}
2011-03-04 16:08:10 +00:00
if(m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS)
{
2011-01-03 11:50:38 +00:00
if(m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_RED)
{
ButtonBar.VSplitLeft(5.0f, 0, &ButtonBar);
ButtonBar.VSplitLeft(120.0f, &Button, &ButtonBar);
if(!DummyConnecting && DoButton_Menu(&s_JoinRedButton, Localize("Join red"), 0, &Button))
{
2011-01-03 11:50:38 +00:00
m_pClient->SendSwitchTeam(TEAM_RED);
2010-05-29 07:25:38 +00:00
SetActive(false);
}
}
2011-01-03 11:50:38 +00:00
if(m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_BLUE)
{
ButtonBar.VSplitLeft(5.0f, 0, &ButtonBar);
ButtonBar.VSplitLeft(120.0f, &Button, &ButtonBar);
if(!DummyConnecting && DoButton_Menu(&s_JoinBlueButton, Localize("Join blue"), 0, &Button))
{
2011-01-03 11:50:38 +00:00
m_pClient->SendSwitchTeam(TEAM_BLUE);
2010-05-29 07:25:38 +00:00
SetActive(false);
}
}
}
else
{
2010-05-29 07:25:38 +00:00
if(m_pClient->m_Snap.m_pLocalInfo->m_Team != 0)
{
ButtonBar.VSplitLeft(5.0f, 0, &ButtonBar);
ButtonBar.VSplitLeft(120.0f, &Button, &ButtonBar);
if(!DummyConnecting && DoButton_Menu(&s_SpectateButton, Localize("Join game"), 0, &Button))
{
2010-05-29 07:25:38 +00:00
m_pClient->SendSwitchTeam(0);
SetActive(false);
}
}
}
2010-08-09 12:14:15 +00:00
if(m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_SPECTATORS)
{
ButtonBar.VSplitLeft(5.0f, 0, &ButtonBar);
ButtonBar.VSplitLeft(65.0f, &Button, &ButtonBar);
static int s_KillButton = 0;
if(DoButton_Menu(&s_KillButton, Localize("Kill"), 0, &Button))
{
m_pClient->SendKill(-1);
SetActive(false);
}
}
}
if(m_pClient->m_ReceivedDDNetPlayer && m_pClient->m_Snap.m_pLocalInfo && m_pClient->m_Snap.m_pGameInfoObj)
{
if(m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_SPECTATORS || Paused || Spec)
{
ButtonBar.VSplitLeft(5.0f, 0, &ButtonBar);
ButtonBar.VSplitLeft((!Paused && !Spec) ? 65.0f : 120.0f, &Button, &ButtonBar);
static int s_PauseButton = 0;
if(DoButton_Menu(&s_PauseButton, (!Paused && !Spec) ? Localize("Pause") : Localize("Join game"), 0, &Button))
{
m_pClient->Console()->ExecuteLine("say /pause");
SetActive(false);
}
}
}
}
void CMenus::RenderPlayers(CUIRect MainView)
{
2014-02-19 13:49:08 +00:00
CUIRect Button, Button2, ButtonBar, Options, Player;
RenderTools()->DrawUIRect(&MainView, ms_ColorTabbarActive, CUI::CORNER_B, 10.0f);
// player options
MainView.Margin(10.0f, &Options);
RenderTools()->DrawUIRect(&Options, ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), CUI::CORNER_ALL, 10.0f);
Options.Margin(10.0f, &Options);
Options.HSplitTop(50.0f, &Button, &Options);
UI()->DoLabelScaled(&Button, Localize("Player options"), 34.0f, TEXTALIGN_LEFT);
// headline
Options.HSplitTop(34.0f, &ButtonBar, &Options);
2020-06-27 20:58:28 +00:00
ButtonBar.VSplitRight(231.0f, &Player, &ButtonBar);
UI()->DoLabelScaled(&Player, Localize("Player"), 24.0f, TEXTALIGN_LEFT);
ButtonBar.HMargin(1.0f, &ButtonBar);
float Width = ButtonBar.h * 2.0f;
ButtonBar.VSplitLeft(Width, &Button, &ButtonBar);
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GUIICONS].m_Id);
Graphics()->QuadsBegin();
RenderTools()->SelectSprite(SPRITE_GUIICON_MUTE);
IGraphics::CQuadItem QuadItem(Button.x, Button.y, Button.w, Button.h);
Graphics()->QuadsDrawTL(&QuadItem, 1);
Graphics()->QuadsEnd();
ButtonBar.VSplitLeft(20.0f, 0, &ButtonBar);
ButtonBar.VSplitLeft(Width, &Button, &ButtonBar);
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GUIICONS].m_Id);
Graphics()->QuadsBegin();
RenderTools()->SelectSprite(SPRITE_GUIICON_EMOTICON_MUTE);
QuadItem = IGraphics::CQuadItem(Button.x, Button.y, Button.w, Button.h);
Graphics()->QuadsDrawTL(&QuadItem, 1);
Graphics()->QuadsEnd();
ButtonBar.VSplitLeft(20.0f, 0, &ButtonBar);
ButtonBar.VSplitLeft(Width, &Button, &ButtonBar);
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GUIICONS].m_Id);
Graphics()->QuadsBegin();
RenderTools()->SelectSprite(SPRITE_GUIICON_FRIEND);
QuadItem = IGraphics::CQuadItem(Button.x, Button.y, Button.w, Button.h);
Graphics()->QuadsDrawTL(&QuadItem, 1);
Graphics()->QuadsEnd();
2013-12-31 21:14:59 +00:00
int TotalPlayers = 0;
2020-10-26 14:14:07 +00:00
for(auto &pInfoByName : m_pClient->m_Snap.m_paInfoByName)
2013-12-31 21:14:59 +00:00
{
2020-10-26 14:14:07 +00:00
if(!pInfoByName)
2013-12-31 21:14:59 +00:00
continue;
2020-10-26 14:14:07 +00:00
int Index = pInfoByName->m_ClientID;
2013-12-31 21:14:59 +00:00
if(Index == m_pClient->m_Snap.m_LocalClientID)
continue;
TotalPlayers++;
}
static int s_VoteList = 0;
static float s_ScrollValue = 0;
CUIRect List = Options;
// List.HSplitTop(28.0f, 0, &List);
2013-12-31 21:14:59 +00:00
UiDoListboxStart(&s_VoteList, &List, 24.0f, "", "", TotalPlayers, 1, -1, s_ScrollValue);
// options
static int s_aPlayerIDs[MAX_CLIENTS][3] = {{0}};
2013-12-31 21:14:59 +00:00
for(int i = 0, Count = 0; i < MAX_CLIENTS; ++i)
{
if(!m_pClient->m_Snap.m_paInfoByName[i])
continue;
int Index = m_pClient->m_Snap.m_paInfoByName[i]->m_ClientID;
2013-12-31 21:14:59 +00:00
if(Index == m_pClient->m_Snap.m_LocalClientID)
continue;
2014-02-02 20:06:05 +00:00
CListboxItem Item = UiDoListboxNextItem(&m_pClient->m_aClients[Index]);
2013-12-31 21:14:59 +00:00
Count++;
if(!Item.m_Visible)
continue;
if(Count % 2 == 1)
RenderTools()->DrawUIRect(&Item.m_Rect, ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), CUI::CORNER_ALL, 10.0f);
2014-02-19 13:49:08 +00:00
Item.m_Rect.VSplitRight(300.0f, &Player, &Item.m_Rect);
// player info
Player.VSplitLeft(28.0f, &Button, &Player);
2021-04-09 18:11:53 +00:00
CTeeRenderInfo TeeInfo = m_pClient->m_aClients[Index].m_RenderInfo;
TeeInfo.m_Size = Button.h;
CAnimState *pIdleState = CAnimState::GetIdle();
vec2 OffsetToMid;
RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid);
vec2 TeeRenderPos(Button.x + Button.h / 2, Button.y + Button.h / 2 + OffsetToMid.y);
RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos);
Player.HSplitTop(1.5f, 0, &Player);
Player.VSplitMid(&Player, &Button);
2014-02-19 13:49:08 +00:00
Item.m_Rect.VSplitRight(200.0f, &Button2, &Item.m_Rect);
CTextCursor Cursor;
TextRender()->SetCursor(&Cursor, Player.x, Player.y + (Player.h - 14.f) / 2.f, 14.0f, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = Player.w;
TextRender()->TextEx(&Cursor, m_pClient->m_aClients[Index].m_aName, -1);
TextRender()->SetCursor(&Cursor, Button.x, Button.y + (Button.h - 14.f) / 2.f, 14.0f, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = Button.w;
TextRender()->TextEx(&Cursor, m_pClient->m_aClients[Index].m_aClan, -1);
// TextRender()->SetCursor(&Cursor, Button2.x,Button2.y, 14.0f, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END);
// Cursor.m_LineWidth = Button.w;
ColorRGBA Color(1.0f, 1.0f, 1.0f, 0.5f);
2021-07-12 09:43:56 +00:00
m_pClient->m_CountryFlags.Render(m_pClient->m_aClients[Index].m_Country, &Color,
2022-03-21 06:10:44 +00:00
Button2.x, Button2.y + Button2.h / 2.0f - 0.75f * Button2.h / 2.0f, 1.5f * Button2.h, 0.75f * Button2.h);
2014-02-19 13:49:08 +00:00
// ignore chat button
2014-02-02 20:06:05 +00:00
Item.m_Rect.HMargin(2.0f, &Item.m_Rect);
Item.m_Rect.VSplitLeft(Width, &Button, &Item.m_Rect);
Button.VSplitLeft((Width - Button.h) / 4.0f, 0, &Button);
Button.VSplitLeft(Button.h, &Button, 0);
2013-11-14 15:23:15 +00:00
if(g_Config.m_ClShowChatFriends && !m_pClient->m_aClients[Index].m_Friend)
DoButton_Toggle(&s_aPlayerIDs[Index][0], 1, &Button, false);
else if(DoButton_Toggle(&s_aPlayerIDs[Index][0], m_pClient->m_aClients[Index].m_ChatIgnore, &Button, true))
m_pClient->m_aClients[Index].m_ChatIgnore ^= 1;
2011-03-23 12:06:35 +00:00
// ignore emoticon button
Item.m_Rect.VSplitLeft(20.0f, &Button, &Item.m_Rect);
Item.m_Rect.VSplitLeft(Width, &Button, &Item.m_Rect);
Button.VSplitLeft((Width - Button.h) / 4.0f, 0, &Button);
Button.VSplitLeft(Button.h, &Button, 0);
if(g_Config.m_ClShowChatFriends && !m_pClient->m_aClients[Index].m_Friend)
DoButton_Toggle(&s_aPlayerIDs[Index][1], 1, &Button, false);
else if(DoButton_Toggle(&s_aPlayerIDs[Index][1], m_pClient->m_aClients[Index].m_EmoticonIgnore, &Button, true))
m_pClient->m_aClients[Index].m_EmoticonIgnore ^= 1;
2011-03-23 12:06:35 +00:00
// friend button
2014-02-02 20:06:05 +00:00
Item.m_Rect.VSplitLeft(20.0f, &Button, &Item.m_Rect);
Item.m_Rect.VSplitLeft(Width, &Button, &Item.m_Rect);
Button.VSplitLeft((Width - Button.h) / 4.0f, 0, &Button);
Button.VSplitLeft(Button.h, &Button, 0);
if(DoButton_Toggle(&s_aPlayerIDs[Index][2], m_pClient->m_aClients[Index].m_Friend, &Button, true))
2011-05-04 23:43:27 +00:00
{
if(m_pClient->m_aClients[Index].m_Friend)
m_pClient->Friends()->RemoveFriend(m_pClient->m_aClients[Index].m_aName, m_pClient->m_aClients[Index].m_aClan);
else
m_pClient->Friends()->AddFriend(m_pClient->m_aClients[Index].m_aName, m_pClient->m_aClients[Index].m_aClan);
2011-05-04 23:43:27 +00:00
}
}
2014-01-10 12:55:31 +00:00
UiDoListboxEnd(&s_ScrollValue, 0);
}
2010-05-29 07:25:38 +00:00
void CMenus::RenderServerInfo(CUIRect MainView)
{
if(!m_pClient->m_Snap.m_pLocalInfo)
return;
// fetch server info
2010-05-29 07:25:38 +00:00
CServerInfo CurrentServerInfo;
Client()->GetServerInfo(&CurrentServerInfo);
// render background
RenderTools()->DrawUIRect(&MainView, ms_ColorTabbarActive, CUI::CORNER_B, 10.0f);
2010-05-29 07:25:38 +00:00
CUIRect View, ServerInfo, GameInfo, Motd;
float x = 0.0f;
float y = 0.0f;
2010-05-29 07:25:38 +00:00
char aBuf[1024];
// set view to use for all sub-modules
2010-05-29 07:25:38 +00:00
MainView.Margin(10.0f, &View);
2010-05-29 07:25:38 +00:00
// serverinfo
View.HSplitTop(View.h / 2 / UI()->Scale() - 5.0f, &ServerInfo, &Motd);
ServerInfo.VSplitLeft(View.w / 2 / UI()->Scale() - 5.0f, &ServerInfo, &GameInfo);
RenderTools()->DrawUIRect(&ServerInfo, ColorRGBA(1, 1, 1, 0.25f), CUI::CORNER_ALL, 10.0f);
2010-05-29 07:25:38 +00:00
ServerInfo.Margin(5.0f, &ServerInfo);
x = 5.0f;
y = 0.0f;
TextRender()->Text(0, ServerInfo.x + x, ServerInfo.y + y, 32, Localize("Server info"), 250.0f);
y += 32.0f + 5.0f;
2010-05-29 07:25:38 +00:00
mem_zero(aBuf, sizeof(aBuf));
str_format(
2010-05-29 07:25:38 +00:00
aBuf,
sizeof(aBuf),
"%s\n\n"
2009-06-15 07:34:25 +00:00
"%s: %s\n"
"%s: %d\n"
"%s: %s\n"
"%s: %s\n",
2010-05-29 07:25:38 +00:00
CurrentServerInfo.m_aName,
2013-12-18 03:11:26 +00:00
Localize("Address"), CurrentServerInfo.m_aAddress,
2010-05-29 07:25:38 +00:00
Localize("Ping"), m_pClient->m_Snap.m_pLocalInfo->m_Latency,
Localize("Version"), CurrentServerInfo.m_aVersion,
Localize("Password"), CurrentServerInfo.m_Flags & 1 ? Localize("Yes") : Localize("No"));
TextRender()->Text(0, ServerInfo.x + x, ServerInfo.y + y, 20, aBuf, 250.0f);
{
2010-05-29 07:25:38 +00:00
CUIRect Button;
int IsFavorite = ServerBrowser()->IsFavorite(CurrentServerInfo.m_NetAddr);
ServerInfo.HSplitBottom(20.0f, &ServerInfo, &Button);
static int s_AddFavButton = 0;
if(DoButton_CheckBox(&s_AddFavButton, Localize("Favorite"), IsFavorite, &Button))
{
2010-05-29 07:25:38 +00:00
if(IsFavorite)
ServerBrowser()->RemoveFavorite(CurrentServerInfo.m_NetAddr);
else
2010-05-29 07:25:38 +00:00
ServerBrowser()->AddFavorite(CurrentServerInfo.m_NetAddr);
}
}
2010-05-29 07:25:38 +00:00
// gameinfo
GameInfo.VSplitLeft(10.0f, 0x0, &GameInfo);
RenderTools()->DrawUIRect(&GameInfo, ColorRGBA(1, 1, 1, 0.25f), CUI::CORNER_ALL, 10.0f);
2010-05-29 07:25:38 +00:00
GameInfo.Margin(5.0f, &GameInfo);
x = 5.0f;
y = 0.0f;
TextRender()->Text(0, GameInfo.x + x, GameInfo.y + y, 32, Localize("Game info"), 250.0f);
y += 32.0f + 5.0f;
2011-03-04 16:08:10 +00:00
if(m_pClient->m_Snap.m_pGameInfoObj)
{
mem_zero(aBuf, sizeof(aBuf));
str_format(
aBuf,
sizeof(aBuf),
"\n\n"
"%s: %s\n"
"%s: %s\n"
"%s: %d\n"
"%s: %d\n"
"\n"
"%s: %d/%d\n",
Localize("Game type"), CurrentServerInfo.m_aGameType,
Localize("Map"), CurrentServerInfo.m_aMap,
Localize("Score limit"), m_pClient->m_Snap.m_pGameInfoObj->m_ScoreLimit,
Localize("Time limit"), m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit,
Localize("Players"), m_pClient->m_Snap.m_NumPlayers, CurrentServerInfo.m_MaxClients);
TextRender()->Text(0, GameInfo.x + x, GameInfo.y + y, 20, aBuf, 250.0f);
2011-03-04 16:08:10 +00:00
}
2010-05-29 07:25:38 +00:00
// motd
Motd.HSplitTop(10.0f, 0, &Motd);
RenderTools()->DrawUIRect(&Motd, ColorRGBA(1, 1, 1, 0.25f), CUI::CORNER_ALL, 10.0f);
2010-05-29 07:25:38 +00:00
Motd.Margin(5.0f, &Motd);
y = 0.0f;
x = 5.0f;
TextRender()->Text(0, Motd.x + x, Motd.y + y, 32, Localize("MOTD"), -1.0f);
y += 32.0f + 5.0f;
2021-07-12 09:43:56 +00:00
TextRender()->Text(0, Motd.x + x, Motd.y + y, 16, m_pClient->m_Motd.m_aServerMotd, Motd.w);
}
bool CMenus::RenderServerControlServer(CUIRect MainView)
{
2010-05-29 07:25:38 +00:00
static int s_VoteList = 0;
static float s_ScrollValue = 0;
CUIRect List = MainView;
2021-07-12 09:43:56 +00:00
int Total = m_pClient->m_Voting.m_NumVoteOptions;
int NumVoteOptions = 0;
2015-04-02 23:27:07 +00:00
int aIndices[MAX_VOTE_OPTIONS];
2015-03-14 14:32:04 +00:00
static int s_CurVoteOption = 0;
int TotalShown = 0;
2021-07-12 09:43:56 +00:00
for(CVoteOptionClient *pOption = m_pClient->m_Voting.m_pFirst; pOption; pOption = pOption->m_pNext)
{
if(m_aFilterString[0] != '\0' && !str_utf8_find_nocase(pOption->m_aDescription, m_aFilterString))
continue;
TotalShown++;
}
2015-03-14 10:05:08 +00:00
2020-10-27 17:31:27 +00:00
UiDoListboxStart(&s_VoteList, &List, 19.0f, "", "", TotalShown, 1, s_CurVoteOption, s_ScrollValue);
2015-03-14 14:32:04 +00:00
int i = -1;
2021-07-12 09:43:56 +00:00
for(CVoteOptionClient *pOption = m_pClient->m_Voting.m_pFirst; pOption; pOption = pOption->m_pNext)
{
2015-03-14 14:32:04 +00:00
i++;
if(m_aFilterString[0] != '\0' && !str_utf8_find_nocase(pOption->m_aDescription, m_aFilterString))
2015-03-13 15:44:05 +00:00
continue;
2010-05-29 07:25:38 +00:00
CListboxItem Item = UiDoListboxNextItem(pOption);
2010-05-29 07:25:38 +00:00
if(Item.m_Visible)
UI()->DoLabelScaled(&Item.m_Rect, pOption->m_aDescription, 13.0f, TEXTALIGN_LEFT);
2015-03-14 10:05:08 +00:00
2015-03-14 14:32:04 +00:00
if(NumVoteOptions < Total)
aIndices[NumVoteOptions] = i;
NumVoteOptions++;
}
bool Call;
s_CurVoteOption = UiDoListboxEnd(&s_ScrollValue, &Call);
2015-03-14 14:32:04 +00:00
if(s_CurVoteOption < Total)
m_CallvoteSelectedOption = aIndices[s_CurVoteOption];
return Call;
}
bool CMenus::RenderServerControlKick(CUIRect MainView, bool FilterSpectators)
{
2010-05-31 15:36:20 +00:00
int NumOptions = 0;
int Selected = 0;
static int aPlayerIDs[MAX_CLIENTS];
2020-10-26 14:14:07 +00:00
for(auto &pInfoByName : m_pClient->m_Snap.m_paInfoByName)
2010-05-31 15:36:20 +00:00
{
2020-10-26 14:14:07 +00:00
if(!pInfoByName)
2010-05-31 15:36:20 +00:00
continue;
2011-08-11 08:59:14 +00:00
2020-10-26 14:14:07 +00:00
int Index = pInfoByName->m_ClientID;
if(Index == m_pClient->m_Snap.m_LocalClientID || (FilterSpectators && pInfoByName->m_Team == TEAM_SPECTATORS))
2010-05-31 15:36:20 +00:00
continue;
2015-03-14 10:05:08 +00:00
if(!str_utf8_find_nocase(m_pClient->m_aClients[Index].m_aName, m_aFilterString))
2015-03-14 10:05:08 +00:00
continue;
if(m_CallvoteSelectedPlayer == Index)
Selected = NumOptions;
aPlayerIDs[NumOptions++] = Index;
2010-05-31 15:36:20 +00:00
}
2010-05-31 15:36:20 +00:00
static int s_VoteList = 0;
static float s_ScrollValue = 0;
2010-05-29 07:25:38 +00:00
CUIRect List = MainView;
2011-04-08 22:03:56 +00:00
UiDoListboxStart(&s_VoteList, &List, 24.0f, "", "", NumOptions, 1, Selected, s_ScrollValue);
for(int i = 0; i < NumOptions; i++)
{
CListboxItem Item = UiDoListboxNextItem(&aPlayerIDs[i]);
2010-05-31 15:36:20 +00:00
if(Item.m_Visible)
{
2021-04-09 18:11:53 +00:00
CTeeRenderInfo TeeInfo = m_pClient->m_aClients[aPlayerIDs[i]].m_RenderInfo;
TeeInfo.m_Size = Item.m_Rect.h;
CAnimState *pIdleState = CAnimState::GetIdle();
vec2 OffsetToMid;
RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid);
vec2 TeeRenderPos(Item.m_Rect.x + Item.m_Rect.h / 2, Item.m_Rect.y + Item.m_Rect.h / 2 + OffsetToMid.y);
RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos);
Item.m_Rect.x += TeeInfo.m_Size;
UI()->DoLabelScaled(&Item.m_Rect, m_pClient->m_aClients[aPlayerIDs[i]].m_aName, 16.0f, TEXTALIGN_LEFT);
2010-05-31 15:36:20 +00:00
}
}
bool Call;
Selected = UiDoListboxEnd(&s_ScrollValue, &Call);
m_CallvoteSelectedPlayer = Selected != -1 ? aPlayerIDs[Selected] : -1;
return Call;
}
2010-05-29 07:25:38 +00:00
void CMenus::RenderServerControl(CUIRect MainView)
{
2010-05-29 07:25:38 +00:00
static int s_ControlPage = 0;
// render background
CUIRect Bottom, RconExtension, TabBar, Button;
2011-04-08 22:03:56 +00:00
MainView.HSplitTop(20.0f, &Bottom, &MainView);
RenderTools()->DrawUIRect(&Bottom, ms_ColorTabbarActive, 0, 10.0f);
2011-04-08 22:03:56 +00:00
MainView.HSplitTop(20.0f, &TabBar, &MainView);
RenderTools()->DrawUIRect(&MainView, ms_ColorTabbarActive, CUI::CORNER_B, 10.0f);
2011-04-06 09:41:40 +00:00
MainView.Margin(10.0f, &MainView);
if(Client()->RconAuthed())
MainView.HSplitBottom(90.0f, &MainView, &RconExtension);
2011-04-06 09:41:40 +00:00
// tab bar
{
TabBar.VSplitLeft(TabBar.w / 3, &Button, &TabBar);
2011-04-06 09:41:40 +00:00
static int s_Button0 = 0;
2011-04-08 22:03:56 +00:00
if(DoButton_MenuTab(&s_Button0, Localize("Change settings"), s_ControlPage == 0, &Button, 0))
2011-04-06 09:41:40 +00:00
s_ControlPage = 0;
TabBar.VSplitMid(&Button, &TabBar);
static int s_Button1 = 0;
if(DoButton_MenuTab(&s_Button1, Localize("Kick player"), s_ControlPage == 1, &Button, 0))
s_ControlPage = 1;
static int s_Button2 = 0;
2011-04-08 22:03:56 +00:00
if(DoButton_MenuTab(&s_Button2, Localize("Move player to spectators"), s_ControlPage == 2, &TabBar, 0))
2011-04-06 09:41:40 +00:00
s_ControlPage = 2;
}
// render page
MainView.HSplitBottom(ms_ButtonHeight + 5 * 2, &MainView, &Bottom);
2010-05-29 07:25:38 +00:00
Bottom.HMargin(5.0f, &Bottom);
bool Call = false;
2010-05-29 07:25:38 +00:00
if(s_ControlPage == 0)
Call = RenderServerControlServer(MainView);
2010-05-29 07:25:38 +00:00
else if(s_ControlPage == 1)
Call = RenderServerControlKick(MainView, false);
else if(s_ControlPage == 2)
Call = RenderServerControlKick(MainView, true);
// vote menu
{
CUIRect QuickSearch;
// render quick search
{
Bottom.VSplitLeft(240.0f, &QuickSearch, &Bottom);
QuickSearch.HSplitTop(5.0f, 0, &QuickSearch);
2022-03-19 17:50:33 +00:00
const char *pSearchLabel = "\xEF\x80\x82";
TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT));
2020-10-06 10:25:10 +00:00
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
2022-03-11 16:34:48 +00:00
SLabelProperties Props;
Props.m_AlignVertically = 0;
UI()->DoLabelScaled(&QuickSearch, pSearchLabel, 14.0f, TEXTALIGN_LEFT, Props);
float wSearch = TextRender()->TextWidth(0, 14.0f, pSearchLabel, -1, -1.0f);
TextRender()->SetRenderFlags(0);
TextRender()->SetCurFont(NULL);
QuickSearch.VSplitLeft(wSearch, 0, &QuickSearch);
QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch);
static int s_ClearButton = 0;
static float s_Offset = 0.0f;
2022-03-13 18:09:33 +00:00
SUIExEditBoxProperties EditProps;
if(m_ControlPageOpening || (Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed()))
{
UI()->SetActiveItem(&m_aFilterString);
m_ControlPageOpening = false;
2022-03-13 18:09:33 +00:00
EditProps.m_SelectText = true;
}
2022-03-13 18:09:33 +00:00
EditProps.m_pEmptyText = Localize("Search");
UIEx()->DoClearableEditBox(&m_aFilterString, &s_ClearButton, &QuickSearch, m_aFilterString, sizeof(m_aFilterString), 14.0f, &s_Offset, false, CUI::CORNER_ALL, EditProps);
}
2010-05-29 07:25:38 +00:00
Bottom.VSplitRight(120.0f, &Bottom, &Button);
2010-05-29 07:25:38 +00:00
static int s_CallVoteButton = 0;
if(DoButton_Menu(&s_CallVoteButton, Localize("Call vote"), 0, &Button) || Call)
{
2010-05-29 07:25:38 +00:00
if(s_ControlPage == 0)
{
2021-07-12 09:43:56 +00:00
m_pClient->m_Voting.CallvoteOption(m_CallvoteSelectedOption, m_aCallvoteReason);
if(g_Config.m_UiCloseWindowAfterChangingSetting)
SetActive(false);
}
2010-05-29 07:25:38 +00:00
else if(s_ControlPage == 1)
{
if(m_CallvoteSelectedPlayer >= 0 && m_CallvoteSelectedPlayer < MAX_CLIENTS &&
m_pClient->m_Snap.m_paPlayerInfos[m_CallvoteSelectedPlayer])
{
2021-07-12 09:43:56 +00:00
m_pClient->m_Voting.CallvoteKick(m_CallvoteSelectedPlayer, m_aCallvoteReason);
2010-05-29 07:25:38 +00:00
SetActive(false);
}
}
else if(s_ControlPage == 2)
{
if(m_CallvoteSelectedPlayer >= 0 && m_CallvoteSelectedPlayer < MAX_CLIENTS &&
m_pClient->m_Snap.m_paPlayerInfos[m_CallvoteSelectedPlayer])
{
2021-07-12 09:43:56 +00:00
m_pClient->m_Voting.CallvoteSpectate(m_CallvoteSelectedPlayer, m_aCallvoteReason);
SetActive(false);
}
}
2011-03-25 11:06:45 +00:00
m_aCallvoteReason[0] = 0;
2010-05-29 07:25:38 +00:00
}
// render kick reason
CUIRect Reason;
Bottom.VSplitRight(40.0f, &Bottom, 0);
Bottom.VSplitRight(160.0f, &Bottom, &Reason);
Reason.HSplitTop(5.0f, 0, &Reason);
const char *pLabel = Localize("Reason:");
UI()->DoLabelScaled(&Reason, pLabel, 14.0f, TEXTALIGN_LEFT);
float w = TextRender()->TextWidth(0, 14.0f, pLabel, -1, -1.0f);
Reason.VSplitLeft(w + 10.0f, 0, &Reason);
static float s_Offset = 0.0f;
if(Input()->KeyPress(KEY_R) && Input()->ModifierIsPressed())
UI()->SetActiveItem(&m_aCallvoteReason);
UIEx()->DoEditBox(&m_aCallvoteReason, &Reason, m_aCallvoteReason, sizeof(m_aCallvoteReason), 14.0f, &s_Offset, false, CUI::CORNER_ALL);
// extended features (only available when authed in rcon)
2010-05-29 07:25:38 +00:00
if(Client()->RconAuthed())
{
// background
RconExtension.Margin(10.0f, &RconExtension);
RconExtension.HSplitTop(20.0f, &Bottom, &RconExtension);
RconExtension.HSplitTop(5.0f, 0, &RconExtension);
// force vote
Bottom.VSplitLeft(5.0f, 0, &Bottom);
2010-05-29 07:25:38 +00:00
Bottom.VSplitLeft(120.0f, &Button, &Bottom);
2010-05-29 07:25:38 +00:00
static int s_ForceVoteButton = 0;
if(DoButton_Menu(&s_ForceVoteButton, Localize("Force vote"), 0, &Button))
{
2010-05-29 07:25:38 +00:00
if(s_ControlPage == 0)
2021-07-12 09:43:56 +00:00
m_pClient->m_Voting.CallvoteOption(m_CallvoteSelectedOption, m_aCallvoteReason, true);
2010-05-29 07:25:38 +00:00
else if(s_ControlPage == 1)
{
if(m_CallvoteSelectedPlayer >= 0 && m_CallvoteSelectedPlayer < MAX_CLIENTS &&
m_pClient->m_Snap.m_paPlayerInfos[m_CallvoteSelectedPlayer])
{
2021-07-12 09:43:56 +00:00
m_pClient->m_Voting.CallvoteKick(m_CallvoteSelectedPlayer, m_aCallvoteReason, true);
2010-05-29 07:25:38 +00:00
SetActive(false);
}
}
else if(s_ControlPage == 2)
{
2010-05-29 07:25:38 +00:00
if(m_CallvoteSelectedPlayer >= 0 && m_CallvoteSelectedPlayer < MAX_CLIENTS &&
m_pClient->m_Snap.m_paPlayerInfos[m_CallvoteSelectedPlayer])
{
2021-07-12 09:43:56 +00:00
m_pClient->m_Voting.CallvoteSpectate(m_CallvoteSelectedPlayer, m_aCallvoteReason, true);
2010-05-29 07:25:38 +00:00
SetActive(false);
}
}
2011-03-25 11:06:45 +00:00
m_aCallvoteReason[0] = 0;
}
if(s_ControlPage == 0)
{
// remove vote
Bottom.VSplitRight(10.0f, &Bottom, 0);
Bottom.VSplitRight(120.0f, 0, &Button);
static int s_RemoveVoteButton = 0;
if(DoButton_Menu(&s_RemoveVoteButton, Localize("Remove"), 0, &Button))
2021-07-12 09:43:56 +00:00
m_pClient->m_Voting.RemovevoteOption(m_CallvoteSelectedOption);
// add vote
RconExtension.HSplitTop(20.0f, &Bottom, &RconExtension);
Bottom.VSplitLeft(5.0f, 0, &Bottom);
Bottom.VSplitLeft(250.0f, &Button, &Bottom);
UI()->DoLabelScaled(&Button, Localize("Vote description:"), 14.0f, TEXTALIGN_LEFT);
Bottom.VSplitLeft(20.0f, 0, &Button);
UI()->DoLabelScaled(&Button, Localize("Vote command:"), 14.0f, TEXTALIGN_LEFT);
static char s_aVoteDescription[64] = {0};
static char s_aVoteCommand[512] = {0};
RconExtension.HSplitTop(20.0f, &Bottom, &RconExtension);
Bottom.VSplitRight(10.0f, &Bottom, 0);
Bottom.VSplitRight(120.0f, &Bottom, &Button);
static int s_AddVoteButton = 0;
if(DoButton_Menu(&s_AddVoteButton, Localize("Add"), 0, &Button))
if(s_aVoteDescription[0] != 0 && s_aVoteCommand[0] != 0)
2021-07-12 09:43:56 +00:00
m_pClient->m_Voting.AddvoteOption(s_aVoteDescription, s_aVoteCommand);
Bottom.VSplitLeft(5.0f, 0, &Bottom);
Bottom.VSplitLeft(250.0f, &Button, &Bottom);
static float s_OffsetDesc = 0.0f;
UIEx()->DoEditBox(&s_aVoteDescription, &Button, s_aVoteDescription, sizeof(s_aVoteDescription), 14.0f, &s_OffsetDesc, false, CUI::CORNER_ALL);
Bottom.VMargin(20.0f, &Button);
static float s_OffsetCmd = 0.0f;
UIEx()->DoEditBox(&s_aVoteCommand, &Button, s_aVoteCommand, sizeof(s_aVoteCommand), 14.0f, &s_OffsetCmd, false, CUI::CORNER_ALL);
}
}
}
2011-02-23 07:33:21 +00:00
}
void CMenus::RenderInGameNetwork(CUIRect MainView)
{
CUIRect Box = MainView;
CUIRect Button;
2015-10-04 16:54:13 +00:00
int Page = g_Config.m_UiPage;
int NewPage = -1;
RenderTools()->DrawUIRect(&MainView, ms_ColorTabbarActive, CUI::CORNER_B, 10.0f);
Box.HSplitTop(5.0f, &MainView, &MainView);
Box.HSplitTop(24.0f, &Box, &MainView);
Box.VSplitLeft(100.0f, &Button, &Box);
static int s_InternetButton = 0;
if(DoButton_MenuTab(&s_InternetButton, Localize("Internet"), Page == PAGE_INTERNET, &Button, 0))
{
if(Page != PAGE_INTERNET)
2015-10-04 16:54:13 +00:00
ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET);
NewPage = PAGE_INTERNET;
}
Box.VSplitLeft(80.0f, &Button, &Box);
static int s_LanButton = 0;
if(DoButton_MenuTab(&s_LanButton, Localize("LAN"), Page == PAGE_LAN, &Button, 0))
{
if(Page != PAGE_LAN)
2015-10-04 16:54:13 +00:00
ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN);
NewPage = PAGE_LAN;
}
Box.VSplitLeft(110.0f, &Button, &Box);
static int s_FavoritesButton = 0;
if(DoButton_MenuTab(&s_FavoritesButton, Localize("Favorites"), Page == PAGE_FAVORITES, &Button, 0))
{
if(Page != PAGE_FAVORITES)
2015-10-04 16:54:13 +00:00
ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES);
NewPage = PAGE_FAVORITES;
}
2014-09-20 11:41:20 +00:00
Box.VSplitLeft(110.0f, &Button, &Box);
static int s_DDNetButton = 0;
if(DoButton_MenuTab(&s_DDNetButton, "DDNet", Page == PAGE_DDNET, &Button, 0) || Page < PAGE_INTERNET || Page > PAGE_KOG)
2014-09-20 11:41:20 +00:00
{
if(Page != PAGE_DDNET)
2017-09-03 16:17:21 +00:00
{
Client()->RequestDDNetInfo();
2015-10-04 16:54:13 +00:00
ServerBrowser()->Refresh(IServerBrowser::TYPE_DDNET);
2017-09-03 16:17:21 +00:00
}
NewPage = PAGE_DDNET;
}
Box.VSplitLeft(110.0f, &Button, &Box);
static int s_KoGButton = 0;
if(DoButton_MenuTab(&s_KoGButton, "KoG", Page == PAGE_KOG, &Button, CUI::CORNER_BR))
{
if(Page != PAGE_KOG)
{
Client()->RequestDDNetInfo();
ServerBrowser()->Refresh(IServerBrowser::TYPE_KOG);
}
NewPage = PAGE_KOG;
2014-09-20 11:41:20 +00:00
}
if(NewPage != -1)
{
if(Client()->State() != IClient::STATE_OFFLINE)
SetMenuPage(NewPage);
}
RenderServerbrowser(MainView);
}
// ghost stuff
int CMenus::GhostlistFetchCallback(const char *pName, int IsDir, int StorageType, void *pUser)
{
CMenus *pSelf = (CMenus *)pUser;
const char *pMap = pSelf->Client()->GetCurrentMap();
if(IsDir || !str_endswith(pName, ".gho") || !str_startswith(pName, pMap))
return 0;
char aFilename[IO_MAX_PATH_LENGTH];
2021-07-12 09:43:56 +00:00
str_format(aFilename, sizeof(aFilename), "%s/%s", pSelf->m_pClient->m_Ghost.GetGhostDir(), pName);
CGhostInfo Info;
2021-07-12 09:43:56 +00:00
if(!pSelf->m_pClient->m_Ghost.GhostLoader()->GetGhostInfo(aFilename, &Info, pMap, pSelf->Client()->GetCurrentMapSha256(), pSelf->Client()->GetCurrentMapCrc()))
return 0;
CGhostItem Item;
str_copy(Item.m_aFilename, aFilename, sizeof(Item.m_aFilename));
str_copy(Item.m_aPlayer, Info.m_aOwner, sizeof(Item.m_aPlayer));
Item.m_Time = Info.m_Time;
if(Item.m_Time > 0)
pSelf->m_vGhosts.push_back(Item);
2022-05-18 16:00:05 +00:00
if(tw::time_get() - pSelf->m_GhostPopulateStartTime > 500ms)
{
pSelf->GameClient()->m_Menus.RenderLoading(false, false);
}
return 0;
}
void CMenus::GhostlistPopulate()
{
m_vGhosts.clear();
2022-05-18 16:00:05 +00:00
m_GhostPopulateStartTime = tw::time_get();
2021-07-12 09:43:56 +00:00
Storage()->ListDirectory(IStorage::TYPE_ALL, m_pClient->m_Ghost.GetGhostDir(), GhostlistFetchCallback, this);
std::sort(m_vGhosts.begin(), m_vGhosts.end());
CGhostItem *pOwnGhost = 0;
for(auto &Ghost : m_vGhosts)
if(str_comp(Ghost.m_aPlayer, Client()->PlayerName()) == 0 && (!pOwnGhost || Ghost < *pOwnGhost))
pOwnGhost = &Ghost;
if(pOwnGhost)
{
pOwnGhost->m_Own = true;
2021-07-12 09:43:56 +00:00
pOwnGhost->m_Slot = m_pClient->m_Ghost.Load(pOwnGhost->m_aFilename);
}
}
CMenus::CGhostItem *CMenus::GetOwnGhost()
{
for(auto &Ghost : m_vGhosts)
if(Ghost.m_Own)
return &Ghost;
return nullptr;
}
2017-09-10 01:48:22 +00:00
void CMenus::UpdateOwnGhost(CGhostItem Item)
{
int Own = -1;
for(size_t i = 0; i < m_vGhosts.size(); i++)
if(m_vGhosts[i].m_Own)
2017-09-10 01:48:22 +00:00
Own = i;
if(Own != -1)
{
m_vGhosts[Own].m_Slot = -1;
m_vGhosts[Own].m_Own = false;
if(Item.HasFile() || !m_vGhosts[Own].HasFile())
2017-09-10 01:48:22 +00:00
DeleteGhostItem(Own);
}
Item.m_Own = true;
m_vGhosts.insert(std::lower_bound(m_vGhosts.begin(), m_vGhosts.end(), Item), Item);
2017-09-10 01:48:22 +00:00
}
void CMenus::DeleteGhostItem(int Index)
{
if(m_vGhosts[Index].HasFile())
Storage()->RemoveFile(m_vGhosts[Index].m_aFilename, IStorage::TYPE_SAVE);
m_vGhosts.erase(m_vGhosts.begin() + Index);
2017-09-10 01:48:22 +00:00
}
void CMenus::RenderGhost(CUIRect MainView)
{
// render background
RenderTools()->DrawUIRect(&MainView, ms_ColorTabbarActive, CUI::CORNER_B, 10.0f);
MainView.HSplitTop(10.0f, 0, &MainView);
MainView.HSplitBottom(5.0f, &MainView, 0);
MainView.VSplitLeft(5.0f, 0, &MainView);
MainView.VSplitRight(5.0f, &MainView, 0);
CUIRect Headers, Status;
CUIRect View = MainView;
View.HSplitTop(17.0f, &Headers, &View);
View.HSplitBottom(28.0f, &View, &Status);
// split of the scrollbar
RenderTools()->DrawUIRect(&Headers, ColorRGBA(1, 1, 1, 0.25f), CUI::CORNER_T, 5.0f);
Headers.VSplitRight(20.0f, &Headers, 0);
struct CColumn
{
int m_Id;
CLocConstString m_Caption;
float m_Width;
CUIRect m_Rect;
CUIRect m_Spacer;
};
enum
{
COL_ACTIVE = 0,
COL_NAME,
COL_TIME,
};
static CColumn s_aCols[] = {
{-1, " ", 2.0f, {0}, {0}},
{COL_ACTIVE, " ", 30.0f, {0}, {0}},
{COL_NAME, "Name", 300.0f, {0}, {0}}, // Localize("Name")
{COL_TIME, "Time", 200.0f, {0}, {0}}, // Localize("Time")
};
int NumCols = std::size(s_aCols);
// do layout
for(int i = 0; i < NumCols; i++)
{
Headers.VSplitLeft(s_aCols[i].m_Width, &s_aCols[i].m_Rect, &Headers);
if(i + 1 < NumCols)
Headers.VSplitLeft(2, &s_aCols[i].m_Spacer, &Headers);
}
// do headers
for(int i = 0; i < NumCols; i++)
DoButton_GridHeader(s_aCols[i].m_Caption, Localize(s_aCols[i].m_Caption), 0, &s_aCols[i].m_Rect);
RenderTools()->DrawUIRect(&View, ColorRGBA(0, 0, 0, 0.15f), 0, 0);
CUIRect Scroll;
2021-11-26 20:55:31 +00:00
View.VSplitRight(20.0f, &View, &Scroll);
static float s_ScrollValue = 0;
2021-12-03 18:54:22 +00:00
s_ScrollValue = UIEx()->DoScrollbarV(&s_ScrollValue, &Scroll, s_ScrollValue);
int NumGhosts = m_vGhosts.size();
2021-12-03 18:54:22 +00:00
static int s_SelectedIndex = 0;
HandleListInputs(View, s_ScrollValue, 1.0f, nullptr, s_aCols[0].m_Rect.h, s_SelectedIndex, NumGhosts);
// set clipping
UI()->ClipEnable(&View);
CUIRect OriginalView = View;
int Num = (int)(View.h / s_aCols[0].m_Rect.h) + 1;
2020-11-21 17:30:24 +00:00
int ScrollNum = maximum(NumGhosts - Num + 1, 0);
View.y -= s_ScrollValue * ScrollNum * s_aCols[0].m_Rect.h;
int NewSelected = -1;
bool DoubleClicked = false;
for(int i = 0; i < NumGhosts; i++)
{
const CGhostItem *pItem = &m_vGhosts[i];
CUIRect Row;
View.HSplitTop(17.0f, &Row, &View);
// make sure that only those in view can be selected
if(Row.y + Row.h > OriginalView.y && Row.y < OriginalView.y + OriginalView.h)
{
if(i == s_SelectedIndex)
{
CUIRect r = Row;
r.Margin(1.5f, &r);
RenderTools()->DrawUIRect(&r, ColorRGBA(1, 1, 1, 0.5f), CUI::CORNER_ALL, 4.0f);
}
if(UI()->DoButtonLogic(pItem, 0, &Row))
{
NewSelected = i;
DoubleClicked |= NewSelected == m_DoubleClickIndex;
m_DoubleClickIndex = NewSelected;
}
}
2019-04-26 12:06:32 +00:00
ColorRGBA rgb = ColorRGBA(1.0f, 1.0f, 1.0f);
2017-09-09 18:19:44 +00:00
if(pItem->m_Own)
2019-04-26 12:06:32 +00:00
rgb = color_cast<ColorRGBA>(ColorHSLA(0.33f, 1.0f, 0.75f));
2017-09-10 01:48:22 +00:00
TextRender()->TextColor(rgb.WithAlpha(pItem->HasFile() ? 1.0f : 0.5f));
2017-09-09 18:19:44 +00:00
for(int c = 0; c < NumCols; c++)
{
CUIRect Button;
Button.x = s_aCols[c].m_Rect.x;
Button.y = Row.y;
Button.h = Row.h;
Button.w = s_aCols[c].m_Rect.w;
int Id = s_aCols[c].m_Id;
if(Id == COL_ACTIVE)
{
if(pItem->Active())
{
Graphics()->WrapClamp();
Graphics()->TextureSet(GameClient()->m_EmoticonsSkin.m_SpriteEmoticons[(SPRITE_OOP + 7) - SPRITE_OOP]);
Graphics()->QuadsBegin();
IGraphics::CQuadItem QuadItem(Button.x + Button.w / 2, Button.y + Button.h / 2, 20.0f, 20.0f);
Graphics()->QuadsDraw(&QuadItem, 1);
Graphics()->QuadsEnd();
Graphics()->WrapNormal();
}
}
else if(Id == COL_NAME)
{
CTextCursor Cursor;
TextRender()->SetCursor(&Cursor, Button.x, Button.y + (Button.h - 12.0f * UI()->Scale()) / 2.f, 12.0f * UI()->Scale(), TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = Button.w;
2017-09-09 18:19:44 +00:00
TextRender()->TextEx(&Cursor, pItem->m_aPlayer, -1);
}
else if(Id == COL_TIME)
{
CTextCursor Cursor;
TextRender()->SetCursor(&Cursor, Button.x, Button.y + (Button.h - 12.0f * UI()->Scale()) / 2.f, 12.0f * UI()->Scale(), TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = Button.w;
char aBuf[64];
2020-10-18 21:33:45 +00:00
str_time(pItem->m_Time / 10, TIME_HOURS_CENTISECS, aBuf, sizeof(aBuf));
TextRender()->TextEx(&Cursor, aBuf, -1);
}
}
2017-09-09 18:19:44 +00:00
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
}
UI()->ClipDisable();
if(NewSelected != -1)
s_SelectedIndex = NewSelected;
RenderTools()->DrawUIRect(&Status, ColorRGBA(1, 1, 1, 0.25f), CUI::CORNER_B, 5.0f);
Status.Margin(5.0f, &Status);
CUIRect Button;
Status.VSplitLeft(120.0f, &Button, &Status);
2017-09-10 02:09:38 +00:00
static int s_ReloadButton = 0;
if(DoButton_Menu(&s_ReloadButton, Localize("Reload"), 0, &Button) || Input()->KeyPress(KEY_F5))
{
2021-07-12 09:43:56 +00:00
m_pClient->m_Ghost.UnloadAll();
GhostlistPopulate();
}
if(s_SelectedIndex == -1 || s_SelectedIndex >= (int)m_vGhosts.size())
return;
CGhostItem *pGhost = &m_vGhosts[s_SelectedIndex];
CGhostItem *pOwnGhost = GetOwnGhost();
int ReservedSlots = !pGhost->m_Own && !(pOwnGhost && pOwnGhost->Active());
2021-07-12 09:43:56 +00:00
if(pGhost->HasFile() && (pGhost->Active() || m_pClient->m_Ghost.FreeSlots() > ReservedSlots))
{
2017-09-10 01:48:22 +00:00
Status.VSplitRight(120.0f, &Status, &Button);
static int s_GhostButton = 0;
2017-09-10 01:48:22 +00:00
const char *pText = pGhost->Active() ? Localize("Deactivate") : Localize("Activate");
if(DoButton_Menu(&s_GhostButton, pText, 0, &Button) || (DoubleClicked && Input()->MouseDoubleClick()))
{
2017-09-10 01:48:22 +00:00
if(pGhost->Active())
{
2021-07-12 09:43:56 +00:00
m_pClient->m_Ghost.Unload(pGhost->m_Slot);
2017-09-10 01:48:22 +00:00
pGhost->m_Slot = -1;
}
else
2021-07-12 09:43:56 +00:00
pGhost->m_Slot = m_pClient->m_Ghost.Load(pGhost->m_aFilename);
}
2017-09-10 01:48:22 +00:00
Status.VSplitRight(5.0f, &Status, 0);
}
Status.VSplitRight(120.0f, &Status, &Button);
static int s_DeleteButton = 0;
2017-09-10 01:48:22 +00:00
if(DoButton_Menu(&s_DeleteButton, Localize("Delete"), 0, &Button))
{
if(pGhost->Active())
2021-07-12 09:43:56 +00:00
m_pClient->m_Ghost.Unload(pGhost->m_Slot);
2017-09-10 01:48:22 +00:00
DeleteGhostItem(s_SelectedIndex);
}
Status.VSplitRight(5.0f, &Status, 0);
2021-07-12 09:43:56 +00:00
bool Recording = m_pClient->m_Ghost.GhostRecorder()->IsRecording();
2017-09-10 01:48:22 +00:00
if(!pGhost->HasFile() && !Recording && pGhost->Active())
{
static int s_SaveButton = 0;
2017-09-10 01:48:22 +00:00
Status.VSplitRight(120.0f, &Status, &Button);
if(DoButton_Menu(&s_SaveButton, Localize("Save"), 0, &Button))
2021-07-12 09:43:56 +00:00
m_pClient->m_Ghost.SaveGhost(pGhost);
}
}