diff --git a/src/engine/client.h b/src/engine/client.h index 8493e92a1..440afd9a6 100644 --- a/src/engine/client.h +++ b/src/engine/client.h @@ -200,6 +200,7 @@ public: virtual bool RconAuthed() const = 0; virtual bool UseTempRconCommands() const = 0; virtual void Rcon(const char *pLine) = 0; + virtual bool ReceivingRconCommands() const = 0; // server info virtual void GetServerInfo(class CServerInfo *pServerInfo) const = 0; diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index c00621686..2ed047512 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -543,6 +543,7 @@ void CClient::DisconnectWithReason(const char *pReason) mem_zero(m_aRconPassword, sizeof(m_aRconPassword)); m_ServerSentCapabilities = false; m_UseTempRconCommands = 0; + m_ReceivingRconCommands = false; m_pConsole->DeregisterTempAll(); m_aNetClient[CONN_MAIN].Disconnect(pReason); SetState(IClient::STATE_OFFLINE); @@ -1605,6 +1606,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) if(Old != 0 && m_UseTempRconCommands == 0) { m_pConsole->DeregisterTempAll(); + m_ReceivingRconCommands = false; } } } @@ -1940,6 +1942,14 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) GameClient()->OnRconType(UsernameReq); } } + else if(Conn == CONN_MAIN && (pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_CMD_GROUP_START) + { + m_ReceivingRconCommands = true; + } + else if(Conn == CONN_MAIN && (pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_CMD_GROUP_END) + { + m_ReceivingRconCommands = false; + } } else if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0) { diff --git a/src/engine/client/client.h b/src/engine/client/client.h index 694af9ec0..afb6b0309 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -116,6 +116,7 @@ class CClient : public IClient, public CDemoPlayer::IListener char m_aRconUsername[32] = ""; char m_aRconPassword[sizeof(g_Config.m_SvRconPassword)] = ""; int m_UseTempRconCommands = 0; + bool m_ReceivingRconCommands = false; char m_aPassword[sizeof(g_Config.m_Password)] = ""; bool m_SendPassword = false; @@ -283,6 +284,7 @@ public: bool UseTempRconCommands() const override { return m_UseTempRconCommands != 0; } void RconAuth(const char *pName, const char *pPassword) override; void Rcon(const char *pCmd) override; + bool ReceivingRconCommands() const override { return m_ReceivingRconCommands; } bool ConnectionProblems() const override; diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index 16e3db90b..7c5face3d 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -1279,10 +1279,15 @@ void CGameConsole::OnRender() str_format(aBuf, sizeof(aBuf), Localize("Lines %d - %d (%s)"), pConsole->m_BacklogCurLine + 1, pConsole->m_BacklogCurLine + pConsole->m_LinesRendered, pConsole->m_BacklogCurLine != 0 ? Localize("Locked") : Localize("Following")); TextRender()->Text(10.0f, FONT_SIZE / 2.f, FONT_SIZE, aBuf); + if(m_ConsoleType == CONSOLETYPE_REMOTE && Client()->ReceivingRconCommands()) + { + UI()->RenderProgressSpinner(vec2(Screen.w / 4.0f + FONT_SIZE / 2.f, FONT_SIZE), FONT_SIZE / 2.f); + TextRender()->Text(Screen.w / 4.0f + FONT_SIZE + 2.0f, FONT_SIZE / 2.f, FONT_SIZE, Localize("Loading commands…")); + } + // render version str_copy(aBuf, "v" GAME_VERSION " on " CONF_PLATFORM_STRING " " CONF_ARCH_STRING); - float Width = TextRender()->TextWidth(FONT_SIZE, aBuf, -1, -1.0f); - TextRender()->Text(Screen.w - Width - 10.0f, FONT_SIZE / 2.f, FONT_SIZE, aBuf); + TextRender()->Text(Screen.w - TextRender()->TextWidth(FONT_SIZE, aBuf) - 10.0f, FONT_SIZE / 2.f, FONT_SIZE, aBuf); } } diff --git a/src/game/client/components/menus_ingame.cpp b/src/game/client/components/menus_ingame.cpp index 315d21771..e75e55584 100644 --- a/src/game/client/components/menus_ingame.cpp +++ b/src/game/client/components/menus_ingame.cpp @@ -657,6 +657,7 @@ void CMenus::RenderServerControl(CUIRect MainView) // render page MainView.HSplitBottom(ms_ButtonHeight + 5 * 2, &MainView, &Bottom); Bottom.HMargin(5.0f, &Bottom); + Bottom.HSplitTop(5.0f, nullptr, &Bottom); bool Call = false; if(s_ControlPage == EServerControlTab::SETTINGS) @@ -667,12 +668,11 @@ void CMenus::RenderServerControl(CUIRect MainView) Call = RenderServerControlKick(MainView, true); // vote menu - CUIRect QuickSearch; // render quick search + CUIRect QuickSearch; Bottom.VSplitLeft(5.0f, 0, &Bottom); Bottom.VSplitLeft(250.0f, &QuickSearch, &Bottom); - QuickSearch.HSplitTop(5.0f, 0, &QuickSearch); TextRender()->SetFontPreset(EFontPreset::ICON_FONT); 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); @@ -695,7 +695,6 @@ void CMenus::RenderServerControl(CUIRect MainView) // call vote Bottom.VSplitRight(10.0f, &Bottom, 0); Bottom.VSplitRight(120.0f, &Bottom, &Button); - Button.HSplitTop(5.0f, 0, &Button); static CButtonContainer s_CallVoteButton; if(DoButton_Menu(&s_CallVoteButton, Localize("Call vote"), 0, &Button) || Call) @@ -731,7 +730,6 @@ void CMenus::RenderServerControl(CUIRect MainView) CUIRect Reason; Bottom.VSplitRight(20.0f, &Bottom, 0); Bottom.VSplitRight(200.0f, &Bottom, &Reason); - Reason.HSplitTop(5.0f, 0, &Reason); const char *pLabel = Localize("Reason:"); UI()->DoLabel(&Reason, pLabel, 14.0f, TEXTALIGN_ML); float w = TextRender()->TextWidth(14.0f, pLabel, -1, -1.0f); @@ -743,6 +741,18 @@ void CMenus::RenderServerControl(CUIRect MainView) } UI()->DoEditBox(&m_CallvoteReasonInput, &Reason, 14.0f); + // vote option loading indicator + if(s_ControlPage == EServerControlTab::SETTINGS && m_pClient->m_Voting.IsReceivingOptions()) + { + CUIRect Spinner, LoadingLabel; + Bottom.VSplitLeft(20.0f, nullptr, &Bottom); + Bottom.VSplitLeft(16.0f, &Spinner, &Bottom); + Bottom.VSplitLeft(5.0f, nullptr, &Bottom); + Bottom.VSplitRight(10.0f, &LoadingLabel, nullptr); + UI()->RenderProgressSpinner(Spinner.Center(), 8.0f); + UI()->DoLabel(&LoadingLabel, Localize("Loading…"), 14.0f, TEXTALIGN_ML); + } + // extended features (only available when authed in rcon) if(Client()->RconAuthed()) { diff --git a/src/game/client/components/voting.cpp b/src/game/client/components/voting.cpp index c686833cb..538f9484e 100644 --- a/src/game/client/components/voting.cpp +++ b/src/game/client/components/voting.cpp @@ -225,6 +225,7 @@ void CVoting::OnReset() m_aReason[0] = 0; m_Yes = m_No = m_Pass = m_Total = 0; m_Voted = 0; + m_ReceivingOptions = false; } void CVoting::OnConsoleInit() @@ -308,6 +309,14 @@ void CVoting::OnMessage(int MsgType, void *pRawMsg) CNetMsg_Sv_YourVote *pMsg = (CNetMsg_Sv_YourVote *)pRawMsg; m_Voted = pMsg->m_Voted; } + else if(MsgType == NETMSGTYPE_SV_VOTEOPTIONGROUPSTART) + { + m_ReceivingOptions = true; + } + else if(MsgType == NETMSGTYPE_SV_VOTEOPTIONGROUPEND) + { + m_ReceivingOptions = false; + } } void CVoting::Render() diff --git a/src/game/client/components/voting.h b/src/game/client/components/voting.h index adf8c9173..ed4fe360d 100644 --- a/src/game/client/components/voting.h +++ b/src/game/client/components/voting.h @@ -24,6 +24,7 @@ class CVoting : public CComponent char m_aReason[VOTE_REASON_LENGTH]; int m_Voted; int m_Yes, m_No, m_Pass, m_Total; + bool m_ReceivingOptions; void AddOption(const char *pDescription); void RemoveOption(const char *pDescription); @@ -61,6 +62,7 @@ public: int TakenChoice() const { return m_Voted; } const char *VoteDescription() const { return m_aDescription; } const char *VoteReason() const { return m_aReason; } + bool IsReceivingOptions() const { return m_ReceivingOptions; } }; #endif