1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3#include "important_alert.h"
4
5#include <base/log.h>
6
7#include <engine/client.h>
8#include <engine/graphics.h>
9#include <engine/shared/config.h>
10#include <engine/textrender.h>
11
12#include <generated/protocol.h>
13
14#include <game/client/components/menus.h>
15#include <game/client/gameclient.h>
16#include <game/localization.h>
17
18inline constexpr LOG_COLOR IMPORTANT_ALERT_COLOR{.r: 255, .g: 0, .b: 0};
19inline constexpr float MINIMUM_ACTIVE_SECONDS = 5.0f;
20
21void CImportantAlert::OnReset()
22{
23 m_Active = false;
24 DeleteTextContainers();
25}
26
27void CImportantAlert::OnWindowResize()
28{
29 DeleteTextContainers();
30}
31
32void CImportantAlert::OnRender()
33{
34 if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK)
35 {
36 return;
37 }
38 if(!m_Active)
39 {
40 return;
41 }
42#if defined(CONF_VIDEORECORDER)
43 if(IVideo::Current() && !g_Config.m_ClVideoShowImportantAlerts)
44 {
45 return;
46 }
47#endif
48 RenderImportantAlert();
49}
50
51void CImportantAlert::DeleteTextContainers()
52{
53 TextRender()->DeleteTextContainer(TextContainerIndex&: m_TitleTextContainerIndex);
54 TextRender()->DeleteTextContainer(TextContainerIndex&: m_MessageTextContainerIndex);
55 TextRender()->DeleteTextContainer(TextContainerIndex&: m_CloseHintTextContainerIndex);
56}
57
58void CImportantAlert::RenderImportantAlert()
59{
60 if(m_FadeOutSince >= 0.0f && Client()->GlobalTime() - m_FadeOutSince >= 1.0f)
61 {
62 OnReset();
63 return;
64 }
65
66 const float Seconds = SecondsActive();
67
68 const float Height = 300.0f;
69 const float Width = Height * Graphics()->ScreenAspect();
70 Graphics()->MapScreen(TopLeftX: 0.0f, TopLeftY: 0.0f, BottomRightX: Width, BottomRightY: Height);
71
72 const float TitleFontSize = 20.0f;
73 const float MessageFontSize = 16.0f;
74 const float CloseHintFontSize = 6.0f;
75 const float Alpha = m_FadeOutSince >= 0.0f ? 1.0f - (Client()->GlobalTime() - m_FadeOutSince) : 1.0f;
76
77 if(!m_TitleTextContainerIndex.Valid())
78 {
79 CTextCursor Cursor;
80 Cursor.m_FontSize = TitleFontSize;
81 Cursor.m_LineWidth = Width;
82 TextRender()->CreateTextContainer(TextContainerIndex&: m_TitleTextContainerIndex, pCursor: &Cursor, pText: m_aTitleText);
83 }
84 if(m_TitleTextContainerIndex.Valid())
85 {
86 TextRender()->RenderTextContainer(TextContainerIndex: m_TitleTextContainerIndex,
87 TextColor: ColorRGBA(1.0f, 0.0f, 0.0f, Alpha),
88 TextOutlineColor: TextRender()->DefaultTextOutlineColor().WithMultipliedAlpha(alpha: Alpha),
89 X: Width / 2.0f - TextRender()->GetBoundingBoxTextContainer(TextContainerIndex: m_TitleTextContainerIndex).m_W / 2.0f,
90 Y: 40.0f);
91 }
92
93 if(!m_MessageTextContainerIndex.Valid())
94 {
95 CTextCursor Cursor;
96 Cursor.m_FontSize = MessageFontSize;
97 Cursor.m_LineWidth = Width;
98 TextRender()->CreateTextContainer(TextContainerIndex&: m_MessageTextContainerIndex, pCursor: &Cursor, pText: m_aMessageText);
99 }
100 if(m_MessageTextContainerIndex.Valid())
101 {
102 TextRender()->RenderTextContainer(TextContainerIndex: m_MessageTextContainerIndex,
103 TextColor: TextRender()->DefaultTextColor().WithMultipliedAlpha(alpha: Alpha),
104 TextOutlineColor: TextRender()->DefaultTextOutlineColor().WithMultipliedAlpha(alpha: Alpha),
105 X: Width / 2.0f - TextRender()->GetBoundingBoxTextContainer(TextContainerIndex: m_MessageTextContainerIndex).m_W / 2.0f, Y: 40.0f + TitleFontSize + 10.0f);
106 }
107
108 if(Seconds > MINIMUM_ACTIVE_SECONDS)
109 {
110 if(!m_CloseHintTextContainerIndex.Valid())
111 {
112 CTextCursor Cursor;
113 Cursor.m_FontSize = CloseHintFontSize;
114 Cursor.m_LineWidth = Width;
115 TextRender()->CreateTextContainer(TextContainerIndex&: m_CloseHintTextContainerIndex, pCursor: &Cursor, pText: Localize(pStr: "Press Esc or Tab to dismiss…", pContext: "Important alert message"));
116 }
117 if(m_CloseHintTextContainerIndex.Valid())
118 {
119 const float CloseHintAlpha = Seconds < (MINIMUM_ACTIVE_SECONDS + 1.0f) ? (Seconds - MINIMUM_ACTIVE_SECONDS) : 1.0f;
120 TextRender()->RenderTextContainer(TextContainerIndex: m_CloseHintTextContainerIndex,
121 TextColor: TextRender()->DefaultTextColor().WithMultipliedAlpha(alpha: Alpha * CloseHintAlpha),
122 TextOutlineColor: TextRender()->DefaultTextOutlineColor().WithMultipliedAlpha(alpha: Alpha * CloseHintAlpha),
123 X: Width / 2.0f - TextRender()->GetBoundingBoxTextContainer(TextContainerIndex: m_CloseHintTextContainerIndex).m_W / 2.0f, Y: 40.0f - CloseHintFontSize - 2.0f);
124 }
125 }
126}
127
128void CImportantAlert::DoImportantAlert(const char *pTitle, const char *pLogGroup, const char *pMessage)
129{
130 str_copy(dst&: m_aTitleText, src: pTitle);
131 str_copy(dst&: m_aMessageText, src: pMessage);
132 m_Active = true;
133 m_ActiveSince = Client()->GlobalTime();
134 m_FadeOutSince = -1.0f;
135 DeleteTextContainers();
136
137 char aLine[sizeof(m_aMessageText)];
138 while((pMessage = str_next_token(str: pMessage, delim: "\n", buffer: aLine, buffer_size: sizeof(aLine))))
139 {
140 if(aLine[0] != '\0')
141 {
142 log_info_color(IMPORTANT_ALERT_COLOR, pLogGroup, "%s", aLine);
143 }
144 }
145
146 if(Client()->State() != IClient::STATE_DEMOPLAYBACK)
147 {
148 Client()->Notify(pTitle: m_aTitleText, pMessage: m_aMessageText);
149 }
150}
151
152float CImportantAlert::SecondsActive() const
153{
154 return Client()->GlobalTime() - m_ActiveSince;
155}
156
157void CImportantAlert::OnMessage(int MsgType, void *pRawMsg)
158{
159 if(MsgType == NETMSGTYPE_SV_SERVERALERT)
160 {
161 const CNetMsg_Sv_ServerAlert *pMsg = static_cast<const CNetMsg_Sv_ServerAlert *>(pRawMsg);
162 DoImportantAlert(pTitle: Localize(pStr: "Server alert"), pLogGroup: "server_alert", pMessage: pMsg->m_pMessage);
163 }
164 else if(MsgType == NETMSGTYPE_SV_MODERATORALERT)
165 {
166 const CNetMsg_Sv_ModeratorAlert *pMsg = static_cast<const CNetMsg_Sv_ModeratorAlert *>(pRawMsg);
167 DoImportantAlert(pTitle: Localize(pStr: "Moderator alert"), pLogGroup: "moderator_alert", pMessage: pMsg->m_pMessage);
168 }
169}
170
171bool CImportantAlert::OnInput(const IInput::CEvent &Event)
172{
173 if(IsActive() &&
174 !GameClient()->m_Menus.IsActive() &&
175 SecondsActive() >= MINIMUM_ACTIVE_SECONDS &&
176 m_FadeOutSince < 0.0f &&
177 (Event.m_Flags & IInput::FLAG_PRESS) != 0 &&
178 (Event.m_Flags & IInput::FLAG_REPEAT) == 0 &&
179 (Event.m_Key == KEY_ESCAPE || Event.m_Key == KEY_TAB))
180 {
181 m_FadeOutSince = Client()->GlobalTime();
182 return true;
183 }
184 return false;
185}
186
187bool CImportantAlert::IsActive() const
188{
189 return m_Active;
190}
191