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