1 /*
2  * Copyright 2014, 2016 Peter Olsson
3  *
4  * This file is part of Brum Brum Rally.
5  *
6  * Brum Brum Rally is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Brum Brum Rally is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "Chat.h"
21 #include "GameState.h"
22 #include "Settings.h"
23 #include "StateManager.h"
24 #include "Font.h"
25 #include <cstddef>
26 
Chat()27 Chat::Chat()
28 :	needUpdate(false),
29 	gameState(0),
30 	enabled(true),
31 	specialMessageVisible(false)
32 {
33 	specialMessage.renderObject.moveTo(2,2);
34 	specialMessage.timestamp = 0;
35 }
36 
addMessage(const std::string & message,Color color)37 void Chat::addMessage(const std::string& message, Color color)
38 {
39 	const Font& font = getFont(Settings::network.chatFont ? BIG_FONT : SMALL_FONT);
40 	int messageWidth = font.calculateTextWidth(message);
41 	if (messageWidth >= GAME_WIDTH)
42 	{
43 		std::size_t halfWidth = message.size() / 2;
44 		addMessage(message.substr(0, halfWidth) + "...", color);
45 		addMessage("..." + message.substr(halfWidth), color);
46 		return;
47 	}
48 
49 	messages.push_back(ChatMessage());
50 	ChatMessage& cm = messages.back();
51 
52 	cm.renderObject.setSurface(Surface(message, Settings::network.chatFont ? BIG_FONT : SMALL_FONT, color, TEXT_BORDER_COLOR));
53 	if (gameState && enabled)
54 	{
55 		if (specialMessageVisible)
56 		{
57 			gameState->removeRenderObject(specialMessage.renderObject);
58 			specialMessageVisible = false;
59 		}
60 		gameState->addRenderObject(cm.renderObject);
61 	}
62 	// The exact time when the message was written is not important.
63 	// What's important is that it's shown long enough on the screen.
64 	cm.timestamp = SDL_GetTicks();
65 
66 	needUpdate = true;
67 
68 	if (messages.size() > Settings::network.maxChatMessages)
69 	{
70 		if (gameState)
71 		{
72 			gameState->removeRenderObject(messages.front().renderObject);
73 		}
74 		messages.pop_front();
75 	}
76 }
77 
update()78 void Chat::update()
79 {
80 	Uint32 now = SDL_GetTicks();
81 	if (Settings::network.chatMessageDuration > 0)
82 	{
83 		if (SDL_GetAppState() & SDL_APPINPUTFOCUS)
84 		{
85 			while (!messages.empty() && messages.front().timestamp + Settings::network.chatMessageDuration * 1000 < now)
86 			{
87 				if (gameState)
88 				{
89 					gameState->removeRenderObject(messages.front().renderObject);
90 				}
91 				messages.pop_front();
92 				needUpdate = true;
93 			}
94 		}
95 		else
96 		{
97 			// Don't let the chat messages time out when the window isn't focused.
98 			std::deque<ChatMessage>::iterator it;
99 			for (it = messages.begin(); it != messages.end(); ++it)
100 			{
101 				// Reset timeout.
102 				it->timestamp = now;
103 			}
104 		}
105 	}
106 
107 	if (specialMessageVisible && specialMessage.timestamp + 2500 < now)
108 	{
109 		if (gameState)
110 		{
111 			gameState->removeRenderObject(specialMessage.renderObject);
112 		}
113 		specialMessageVisible = false;
114 	}
115 
116 	if (!needUpdate)
117 	{
118 		return;
119 	}
120 
121 	int x = 2;
122 	int y = 2;
123 	std::deque<ChatMessage>::iterator it;
124 	for (it = messages.begin(); it != messages.end(); ++it)
125 	{
126 		it->renderObject.moveTo(x, y);
127 		y += Settings::network.chatFont ? 10 : 8;
128 	}
129 
130 	needUpdate = false;
131 }
132 
setGameState(GameState * newGameState)133 void Chat::setGameState(GameState* newGameState)
134 {
135 	std::deque<ChatMessage>::iterator it;
136 	for (it = messages.begin(); it != messages.end(); ++it)
137 	{
138 		if (gameState)
139 		{
140 			gameState->removeRenderObject(it->renderObject);
141 		}
142 		if (newGameState && enabled)
143 		{
144 			newGameState->addRenderObject(it->renderObject);
145 		}
146 	}
147 
148 	gameState = newGameState;
149 }
150 
toggle()151 void Chat::toggle()
152 {
153 	specialMessageVisible = false;
154 	if (gameState)
155 	{
156 		gameState->removeRenderObject(specialMessage.renderObject);
157 	}
158 
159 	std::deque<ChatMessage>::iterator it;
160 	if (enabled)
161 	{
162 		if (gameState)
163 		{
164 			for (it = messages.begin(); it != messages.end(); ++it)
165 			{
166 				gameState->removeRenderObject(it->renderObject);
167 			}
168 			setSpecialMessage("Chat off");
169 		}
170 		enabled = false;
171 	}
172 	else
173 	{
174 		if (gameState)
175 		{
176 			// Avoid showing messages with less than a second left to timeout.
177 			if (Settings::network.chatMessageDuration > 0)
178 			{
179 				const int MINIMUM_SHOW_TIME = 1000;
180 				while (!messages.empty() && messages.front().timestamp + Settings::network.chatMessageDuration * 1000 - MINIMUM_SHOW_TIME < SDL_GetTicks())
181 				{
182 					messages.front().timestamp -= MINIMUM_SHOW_TIME;
183 				}
184 				update();
185 			}
186 
187 			if (messages.empty())
188 			{
189 				setSpecialMessage("Chat on");
190 			}
191 			else
192 			{
193 				for (it = messages.begin(); it != messages.end(); ++it)
194 				{
195 					gameState->addRenderObject(it->renderObject);
196 				}
197 			}
198 		}
199 		enabled = true;
200 	}
201 }
202 
toggle(bool enable)203 void Chat::toggle(bool enable)
204 {
205 	if (enable != isEnabled())
206 	{
207 		toggle();
208 	}
209 }
210 
isEnabled() const211 bool Chat::isEnabled() const
212 {
213 	return enabled;
214 }
215 
setSpecialMessage(const std::string & message)216 void Chat::setSpecialMessage(const std::string& message)
217 {
218 	specialMessage.renderObject.setSurface(Surface(message, Settings::network.chatFont ? BIG_FONT : SMALL_FONT, CHAT_COLOR, TEXT_BORDER_COLOR));
219 	if (gameState)
220 	{
221 		gameState->addRenderObject(specialMessage.renderObject);
222 	}
223 	specialMessage.timestamp = SDL_GetTicks();
224 	specialMessageVisible = true;
225 }
226 
clear()227 void Chat::clear()
228 {
229 	setGameState(0);
230 	messages.clear();
231 }
232