1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 1998-2000, Matthes Bender
5  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
6  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
7  *
8  * Distributed under the terms of the ISC license; see accompanying file
9  * "COPYING" for details.
10  *
11  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12  * See accompanying file "TRADEMARK" for details.
13  *
14  * To redistribute this file separately, substitute the full license texts
15  * for the above references.
16  */
17 
18 /* Fullscreen startup log and chat type-in */
19 
20 #include "C4Include.h"
21 #include "gui/C4MessageBoard.h"
22 
23 #include "game/C4Application.h"
24 #include "game/C4FullScreen.h"
25 #include "game/C4GraphicsSystem.h"
26 #include "graphics/C4Draw.h"
27 #include "graphics/C4GraphicsResource.h"
28 #include "gui/C4Gui.h"
29 #include "gui/C4LoaderScreen.h"
30 #include "gui/C4MessageInput.h"
31 #include "lib/StdColors.h"
32 #include "player/C4Player.h"
33 #include "player/C4PlayerList.h"
34 
35 const int C4LogSize=30000, C4LogMaxLines=1000;
36 
C4MessageBoard()37 C4MessageBoard::C4MessageBoard() : LogBuffer(C4LogSize, C4LogMaxLines, 0, "  ", false)
38 {
39 	Delay = -1;
40 	Fader = 0;
41 	Speed = 2;
42 	Output.Default();
43 	Startup = false;
44 	ScreenFader = 0;
45 	iBackScroll = -1;
46 	ScrollUpBinding = nullptr;
47 	ScrollDownBinding = nullptr;
48 	iLineHgt = 1; // Prevent unitialized access with USE_CONSOLE
49 }
50 
~C4MessageBoard()51 C4MessageBoard::~C4MessageBoard()
52 {
53 	LogBuffer.Clear();
54 	LogBuffer.SetLBWidth(0);
55 }
56 
Execute()57 void C4MessageBoard::Execute()
58 {
59 	// Startup? draw only
60 	if (Startup) { Draw(Output); return; }
61 
62 	// typein or messages waiting? fade in
63 	if (::MessageInput.IsTypeIn() || iBackScroll >= 0)
64 		ScreenFader = std::max(ScreenFader - 0.20f, -1.0f);
65 
66 	// no curr msg?
67 	if (iBackScroll<0)
68 	{
69 		// draw anyway
70 		Draw(Output);
71 		if (!::MessageInput.IsTypeIn())
72 			ScreenFader = std::min(ScreenFader + 0.05f, 1.0f);
73 		return;
74 	}
75 
76 	// recalc fade/delay speed
77 	Speed = std::max(1, iBackScroll / 5);
78 	// fade msg in?
79 	if (Fader > 0)
80 		Fader = std::max(Fader - Speed, 0);
81 	// hold curr msg? (delay)
82 	if (Fader <= 0)
83 	{
84 		// no delay set yet?
85 		if (Delay == -1)
86 		{
87 			// set delay based on msg length
88 			const char *szCurrMsg = LogBuffer.GetLine(std::min(-iBackScroll, -1), nullptr, nullptr, nullptr);
89 			if (szCurrMsg) Delay = strlen(szCurrMsg); else Delay = 0;
90 		}
91 		// wait...
92 		if (Delay > 0) Delay = std::max(Delay - Speed, 0);
93 		// end of delay
94 		if (Delay == 0)
95 		{
96 			// set cursor to next msg (or at end of log)
97 			iBackScroll = std::max(iBackScroll - 1, -1);
98 			// reset fade
99 			Fader = iLineHgt;
100 			Delay = -1;
101 		}
102 	}
103 
104 	// Draw
105 	Draw(Output);
106 }
107 
Init(C4Facet & cgo,bool fStartup)108 void C4MessageBoard::Init(C4Facet &cgo, bool fStartup)
109 {
110 	Output=cgo;
111 	Startup=fStartup;
112 	iLineHgt=::GraphicsResource.FontRegular.GetLineHeight();
113 	LogBuffer.SetLBWidth(Output.Wdt);
114 
115 	if (!Startup)
116 	{
117 		// set cursor to end of log
118 		iBackScroll = -1;
119 		Fader = 0;
120 		Speed = 2;
121 		ScreenFader = 1.0f; // msgs faded out
122 
123 		LogBuffer.SetLBWidth(Output.Wdt);
124 	}
125 
126 	// messageboard
127 	ScrollUpBinding = std::make_unique<C4KeyBinding>(C4KeyCodeEx(K_UP, KEYS_Shift), "MsgBoardScrollUp", KEYSCOPE_Fullscreen, new C4KeyCB  <C4MessageBoard>(*GraphicsSystem.MessageBoard, &C4MessageBoard::ControlScrollUp));
128 	ScrollDownBinding = std::make_unique<C4KeyBinding>(C4KeyCodeEx(K_DOWN, KEYS_Shift), "MsgBoardScrollDown", KEYSCOPE_Fullscreen, new C4KeyCB  <C4MessageBoard>(*GraphicsSystem.MessageBoard, &C4MessageBoard::ControlScrollDown));
129 }
130 
Draw(C4Facet & cgo)131 void C4MessageBoard::Draw(C4Facet &cgo)
132 {
133 	if (!Application.Active) return;
134 
135 	// Startup: draw Loader
136 	if (Startup)
137 	{
138 		if (::GraphicsSystem.pLoaderScreen)
139 			::GraphicsSystem.pLoaderScreen->Draw(cgo, C4LoaderScreen::Flag::ALL, Game.InitProgress, &LogBuffer);
140 		else
141 			// loader not yet loaded: black BG
142 			pDraw->DrawBoxDw(cgo.Surface, 0,0, cgo.Wdt, cgo.Hgt, 0x00000000);
143 		return;
144 	}
145 
146 	// Game running: message fader
147 
148 	// draw messages
149 	// how many "extra" messages should be shown?
150 	int iMsgFader = C4MSGB_MaxMsgFading;
151 	// check screenfader range
152 	if (ScreenFader >= 1.0f)
153 	{
154 		return;
155 	}
156 	::GraphicsSystem.OverwriteBg();
157 	// show msgs
158 	for (int iMsg = -iMsgFader; iMsg < 0; iMsg++)
159 	{
160 		// get message at pos
161 		if (iMsg-iBackScroll >= 0) break;
162 		const char *Message = LogBuffer.GetLine(iMsg-iBackScroll, nullptr, nullptr, nullptr);
163 		if (!Message || !*Message) continue;
164 		// calc target position (y)
165 		int iMsgY = cgo.Y + cgo.Hgt + iMsg * iLineHgt + Fader;
166 
167 		// player message color?
168 		C4Player *pPlr = GetMessagePlayer(Message);
169 
170 		DWORD dwColor;
171 		if (pPlr)
172 			dwColor = PlrClr2TxtClr(pPlr->ColorDw) & 0xffffff;
173 		else
174 			dwColor = 0xffffff;
175 		// fade out (msg fade)
176 		float fade = std::max(ScreenFader, 0.0f) + ((iMsg + 2.0f + float(Fader) / iLineHgt) / std::min(2-iMsgFader, -1));
177 		DWORD dwFade = (0xff - Clamp(int(fade * 0xff), 0, 0xff)) << 24;
178 		dwColor |= dwFade;
179 		// Draw
180 		pDraw->StringOut(Message,::GraphicsResource.FontRegular,1.0,cgo.Surface,cgo.X,iMsgY,dwColor);
181 	}
182 }
183 
EnsureLastMessage()184 void C4MessageBoard::EnsureLastMessage()
185 {
186 	// Ingore if startup or typein
187 	if (Startup) return;
188 	// scroll until end of log
189 	for (int i = 0; i < 100; i++)
190 	{
191 		::GraphicsSystem.Execute();
192 		Execute();
193 		if (iBackScroll < 0) break;
194 		Delay=0;
195 	}
196 }
197 
AddLog(const char * szMessage)198 void C4MessageBoard::AddLog(const char *szMessage)
199 {
200 	// safety
201 	if (!szMessage || !*szMessage) return;
202 	// make sure new message will be drawn
203 	++iBackScroll;
204 	// register message in standard messageboard font
205 	LogBuffer.AppendLines(szMessage, &::GraphicsResource.FontRegular, 0, nullptr);
206 }
207 
ClearLog()208 void C4MessageBoard::ClearLog()
209 {
210 	LogBuffer.Clear();
211 }
212 
LogNotify()213 void C4MessageBoard::LogNotify()
214 {
215 	// do not show startup board if GUI is active
216 	if (::pGUI->IsActive()) return;
217 	// Reset
218 	iBackScroll=0;
219 	// Draw
220 	if (pDraw)
221 	{
222 		Draw(Output);
223 		// startup: Draw message board only and do page flip
224 		if (Startup) FullScreen.pSurface->PageFlip();
225 	}
226 }
227 
GetMessagePlayer(const char * szMessage)228 C4Player* C4MessageBoard::GetMessagePlayer(const char *szMessage)
229 {
230 	// Scan message text for heading player name
231 	if (SEqual2(szMessage, "* "))
232 	{
233 		StdStrBuf str;
234 		str.CopyUntil(szMessage + 2,' ');
235 		return ::Players.GetByName(str.getData());
236 	}
237 	if (SCharCount(':',szMessage))
238 	{
239 		StdStrBuf str;
240 		str.CopyUntil(szMessage + 2,':');
241 		return ::Players.GetByName(str.getData());
242 	}
243 	return nullptr;
244 }
245 
ControlScrollUp()246 bool C4MessageBoard::ControlScrollUp()
247 {
248 	Delay=-1; Fader=0;
249 	iBackScroll++;
250 	return true;
251 }
252 
ControlScrollDown()253 bool C4MessageBoard::ControlScrollDown()
254 {
255 	Delay=-1; Fader=0;
256 	if (iBackScroll > -1) iBackScroll--;
257 	return true;
258 }
259