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 "motd.h"
4
5#include <base/color.h>
6#include <base/log.h>
7#include <base/log_color.h>
8#include <base/time.h>
9
10#include <engine/graphics.h>
11#include <engine/keys.h>
12#include <engine/shared/config.h>
13#include <engine/textrender.h>
14
15#include <generated/protocol.h>
16
17#include <game/client/components/important_alert.h>
18#include <game/client/gameclient.h>
19
20CMotd::CMotd()
21{
22 m_aServerMotd[0] = '\0';
23 m_ServerMotdTime = 0;
24 m_ServerMotdUpdateTime = 0;
25}
26
27void CMotd::Clear()
28{
29 m_ServerMotdTime = 0;
30 Graphics()->DeleteQuadContainer(ContainerIndex&: m_RectQuadContainer);
31 TextRender()->DeleteTextContainer(TextContainerIndex&: m_TextContainerIndex);
32}
33
34bool CMotd::IsActive() const
35{
36 return time() < m_ServerMotdTime;
37}
38
39void CMotd::OnStateChange(int NewState, int OldState)
40{
41 if(OldState == IClient::STATE_ONLINE || OldState == IClient::STATE_OFFLINE)
42 Clear();
43}
44
45void CMotd::OnWindowResize()
46{
47 Graphics()->DeleteQuadContainer(ContainerIndex&: m_RectQuadContainer);
48 TextRender()->DeleteTextContainer(TextContainerIndex&: m_TextContainerIndex);
49}
50
51void CMotd::OnRender()
52{
53 if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK)
54 return;
55
56 if(!IsActive())
57 return;
58
59 if(GameClient()->m_ImportantAlert.IsActive())
60 {
61 Clear();
62 return;
63 }
64
65 const int MaxLines = 24;
66 const float FontSize = 32.0f; // also the size of the margin and rect rounding
67 const float ScreenHeight = 40.0f * FontSize; // multiple of the font size to get perfect alignment
68 const float ScreenWidth = ScreenHeight * Graphics()->ScreenAspect();
69 Graphics()->MapScreenToSize(Width: ScreenWidth, Height: ScreenHeight);
70
71 const float RectHeight = (MaxLines + 2) * FontSize;
72 const float RectWidth = 630.0f + 2.0f * FontSize;
73 const float RectX = ScreenWidth / 2.0f - RectWidth / 2.0f;
74 const float RectY = 160.0f;
75
76 if(m_RectQuadContainer == -1)
77 {
78 Graphics()->SetColor(r: 0.0f, g: 0.0f, b: 0.0f, a: 0.5f);
79 m_RectQuadContainer = Graphics()->CreateRectQuadContainer(x: RectX, y: RectY, w: RectWidth, h: RectHeight, r: FontSize, Corners: IGraphics::CORNER_ALL);
80 Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f);
81 }
82
83 if(m_RectQuadContainer != -1)
84 {
85 Graphics()->TextureClear();
86 Graphics()->RenderQuadContainer(ContainerIndex: m_RectQuadContainer, QuadDrawNum: -1);
87 }
88
89 if(!m_TextContainerIndex.Valid())
90 {
91 CTextCursor Cursor;
92 Cursor.SetPosition(vec2(RectX + FontSize, RectY + FontSize));
93 // TODO: Set TEXTFLAG_ELLIPSIS_AT_END when https://github.com/ddnet/ddnet/issues/11419 is fixed
94 // Cursor.m_Flags |= TEXTFLAG_ELLIPSIS_AT_END;
95 Cursor.m_FontSize = FontSize;
96 Cursor.m_LineWidth = RectWidth - 2.0f * FontSize;
97 Cursor.m_MaxLines = MaxLines;
98 TextRender()->CreateTextContainer(TextContainerIndex&: m_TextContainerIndex, pCursor: &Cursor, pText: ServerMotd());
99 }
100
101 if(m_TextContainerIndex.Valid())
102 TextRender()->RenderTextContainer(TextContainerIndex: m_TextContainerIndex, TextColor: TextRender()->DefaultTextColor(), TextOutlineColor: TextRender()->DefaultTextOutlineColor());
103}
104
105void CMotd::OnMessage(int MsgType, void *pRawMsg)
106{
107 if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
108 return;
109
110 if(MsgType == NETMSGTYPE_SV_MOTD)
111 {
112 const CNetMsg_Sv_Motd *pMsg = static_cast<CNetMsg_Sv_Motd *>(pRawMsg);
113
114 // copy it manually to process all \n
115 const char *pMsgStr = pMsg->m_pMessage;
116 const size_t MotdLen = str_length(str: pMsgStr) + 1;
117 const char *pLast = m_aServerMotd; // for console printing
118 const LOG_COLOR LogColor = color_cast<LOG_COLOR>(rgb: color_cast<ColorRGBA>(hsl: ColorHSLA(g_Config.m_ClMessageHighlightColor)));
119 for(size_t i = 0, k = 0; i < MotdLen && k < sizeof(m_aServerMotd); i++, k++)
120 {
121 // handle incoming "\\n"
122 if(pMsgStr[i] == '\\' && pMsgStr[i + 1] == 'n')
123 {
124 m_aServerMotd[k] = '\n';
125 i++; // skip the 'n'
126 }
127 else
128 {
129 m_aServerMotd[k] = pMsgStr[i];
130 }
131
132 // print the line to the console when receiving the newline character
133 if(g_Config.m_ClPrintMotd && m_aServerMotd[k] == '\n')
134 {
135 m_aServerMotd[k] = '\0';
136 log_info_color(LogColor, "motd", "%s", pLast);
137 m_aServerMotd[k] = '\n';
138 pLast = m_aServerMotd + k + 1;
139 }
140 }
141 m_aServerMotd[sizeof(m_aServerMotd) - 1] = '\0';
142 if(g_Config.m_ClPrintMotd && *pLast != '\0')
143 {
144 log_info_color(LogColor, "motd", "%s", pLast);
145 }
146
147 m_ServerMotdUpdateTime = time();
148 if(m_aServerMotd[0] && g_Config.m_ClMotdTime)
149 m_ServerMotdTime = m_ServerMotdUpdateTime + time_freq() * g_Config.m_ClMotdTime;
150 else
151 m_ServerMotdTime = 0;
152 TextRender()->DeleteTextContainer(TextContainerIndex&: m_TextContainerIndex);
153 }
154}
155
156bool CMotd::OnInput(const IInput::CEvent &Event)
157{
158 if(IsActive() && Event.m_Flags & IInput::FLAG_PRESS && Event.m_Key == KEY_ESCAPE)
159 {
160 Clear();
161 return true;
162 }
163 return false;
164}
165