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 RenderImportantAlert();
43}
44
45void CImportantAlert::DeleteTextContainers()
46{
47 TextRender()->DeleteTextContainer(TextContainerIndex&: m_TitleTextContainerIndex);
48 TextRender()->DeleteTextContainer(TextContainerIndex&: m_MessageTextContainerIndex);
49 TextRender()->DeleteTextContainer(TextContainerIndex&: m_CloseHintTextContainerIndex);
50}
51
52void CImportantAlert::RenderImportantAlert()
53{
54 if(m_FadeOutSince >= 0.0f && Client()->GlobalTime() - m_FadeOutSince >= 1.0f)
55 {
56 OnReset();
57 return;
58 }
59
60 const float Seconds = SecondsActive();
61
62 const float Height = 300.0f;
63 const float Width = Height * Graphics()->ScreenAspect();
64 Graphics()->MapScreen(TopLeftX: 0.0f, TopLeftY: 0.0f, BottomRightX: Width, BottomRightY: Height);
65
66 const float TitleFontSize = 20.0f;
67 const float MessageFontSize = 16.0f;
68 const float CloseHintFontSize = 6.0f;
69 const float Alpha = m_FadeOutSince >= 0.0f ? 1.0f - (Client()->GlobalTime() - m_FadeOutSince) : 1.0f;
70
71 if(!m_TitleTextContainerIndex.Valid())
72 {
73 CTextCursor Cursor;
74 Cursor.m_FontSize = TitleFontSize;
75 Cursor.m_LineWidth = Width;
76 TextRender()->CreateTextContainer(TextContainerIndex&: m_TitleTextContainerIndex, pCursor: &Cursor, pText: m_aTitleText);
77 }
78 if(m_TitleTextContainerIndex.Valid())
79 {
80 TextRender()->RenderTextContainer(TextContainerIndex: m_TitleTextContainerIndex,
81 TextColor: ColorRGBA(1.0f, 0.0f, 0.0f, Alpha),
82 TextOutlineColor: TextRender()->DefaultTextOutlineColor().WithMultipliedAlpha(alpha: Alpha),
83 X: Width / 2.0f - TextRender()->GetBoundingBoxTextContainer(TextContainerIndex: m_TitleTextContainerIndex).m_W / 2.0f,
84 Y: 40.0f);
85 }
86
87 if(!m_MessageTextContainerIndex.Valid())
88 {
89 CTextCursor Cursor;
90 Cursor.m_FontSize = MessageFontSize;
91 Cursor.m_LineWidth = Width;
92 TextRender()->CreateTextContainer(TextContainerIndex&: m_MessageTextContainerIndex, pCursor: &Cursor, pText: m_aMessageText);
93 }
94 if(m_MessageTextContainerIndex.Valid())
95 {
96 TextRender()->RenderTextContainer(TextContainerIndex: m_MessageTextContainerIndex,
97 TextColor: TextRender()->DefaultTextColor().WithMultipliedAlpha(alpha: Alpha),
98 TextOutlineColor: TextRender()->DefaultTextOutlineColor().WithMultipliedAlpha(alpha: Alpha),
99 X: Width / 2.0f - TextRender()->GetBoundingBoxTextContainer(TextContainerIndex: m_MessageTextContainerIndex).m_W / 2.0f, Y: 40.0f + TitleFontSize + 10.0f);
100 }
101
102 if(Seconds > MINIMUM_ACTIVE_SECONDS)
103 {
104 if(!m_CloseHintTextContainerIndex.Valid())
105 {
106 CTextCursor Cursor;
107 Cursor.m_FontSize = CloseHintFontSize;
108 Cursor.m_LineWidth = Width;
109 TextRender()->CreateTextContainer(TextContainerIndex&: m_CloseHintTextContainerIndex, pCursor: &Cursor, pText: Localize(pStr: "Press Esc or Tab to dismiss…", pContext: "Important alert message"));
110 }
111 if(m_CloseHintTextContainerIndex.Valid())
112 {
113 const float CloseHintAlpha = Seconds < (MINIMUM_ACTIVE_SECONDS + 1.0f) ? (Seconds - MINIMUM_ACTIVE_SECONDS) : 1.0f;
114 TextRender()->RenderTextContainer(TextContainerIndex: m_CloseHintTextContainerIndex,
115 TextColor: TextRender()->DefaultTextColor().WithMultipliedAlpha(alpha: Alpha * CloseHintAlpha),
116 TextOutlineColor: TextRender()->DefaultTextOutlineColor().WithMultipliedAlpha(alpha: Alpha * CloseHintAlpha),
117 X: Width / 2.0f - TextRender()->GetBoundingBoxTextContainer(TextContainerIndex: m_CloseHintTextContainerIndex).m_W / 2.0f, Y: 40.0f - CloseHintFontSize - 2.0f);
118 }
119 }
120}
121
122void CImportantAlert::DoImportantAlert(const char *pTitle, const char *pLogGroup, const char *pMessage)
123{
124 str_copy(dst&: m_aTitleText, src: pTitle);
125 str_copy(dst&: m_aMessageText, src: pMessage);
126 m_Active = true;
127 m_ActiveSince = Client()->GlobalTime();
128 m_FadeOutSince = -1.0f;
129 DeleteTextContainers();
130
131 char aLine[sizeof(m_aMessageText)];
132 while((pMessage = str_next_token(str: pMessage, delim: "\n", buffer: aLine, buffer_size: sizeof(aLine))))
133 {
134 if(aLine[0] != '\0')
135 {
136 log_info_color(IMPORTANT_ALERT_COLOR, pLogGroup, "%s", aLine);
137 }
138 }
139
140 if(Client()->State() != IClient::STATE_DEMOPLAYBACK)
141 {
142 Client()->Notify(pTitle: m_aTitleText, pMessage: m_aMessageText);
143 }
144}
145
146float CImportantAlert::SecondsActive() const
147{
148 return Client()->GlobalTime() - m_ActiveSince;
149}
150
151void CImportantAlert::OnMessage(int MsgType, void *pRawMsg)
152{
153 if(MsgType == NETMSGTYPE_SV_SERVERALERT)
154 {
155 const CNetMsg_Sv_ServerAlert *pMsg = static_cast<const CNetMsg_Sv_ServerAlert *>(pRawMsg);
156 DoImportantAlert(pTitle: Localize(pStr: "Server alert"), pLogGroup: "server_alert", pMessage: pMsg->m_pMessage);
157 }
158 else if(MsgType == NETMSGTYPE_SV_MODERATORALERT)
159 {
160 const CNetMsg_Sv_ModeratorAlert *pMsg = static_cast<const CNetMsg_Sv_ModeratorAlert *>(pRawMsg);
161 DoImportantAlert(pTitle: Localize(pStr: "Moderator alert"), pLogGroup: "moderator_alert", pMessage: pMsg->m_pMessage);
162 }
163}
164
165bool CImportantAlert::OnInput(const IInput::CEvent &Event)
166{
167 if(IsActive() &&
168 !GameClient()->m_Menus.IsActive() &&
169 SecondsActive() >= MINIMUM_ACTIVE_SECONDS &&
170 m_FadeOutSince < 0.0f &&
171 (Event.m_Flags & IInput::FLAG_PRESS) != 0 &&
172 (Event.m_Flags & IInput::FLAG_REPEAT) == 0 &&
173 (Event.m_Key == KEY_ESCAPE || Event.m_Key == KEY_TAB))
174 {
175 m_FadeOutSince = Client()->GlobalTime();
176 return true;
177 }
178 return false;
179}
180
181bool CImportantAlert::IsActive() const
182{
183 return m_Active;
184}
185