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