39 uint64 params_1; |
30 uint64 params_1; |
40 uint64 params_2; |
31 uint64 params_2; |
41 TextEffectMode mode; |
32 TextEffectMode mode; |
42 }; |
33 }; |
43 |
34 |
44 |
|
45 struct ChatMessage { |
|
46 char message[MAX_TEXTMESSAGE_LENGTH]; |
|
47 uint16 color; |
|
48 Date end_date; |
|
49 }; |
|
50 |
|
51 /* used for text effects */ |
35 /* used for text effects */ |
52 static TextEffect *_text_effect_list = NULL; |
36 static TextEffect *_text_effect_list = NULL; |
53 static uint16 _num_text_effects = INIT_NUM_TEXT_MESSAGES; |
37 static uint16 _num_text_effects = INIT_NUM_TEXT_EFFECTS; |
54 |
|
55 /* used for chat window */ |
|
56 static ChatMessage _chatmsg_list[MAX_CHAT_MESSAGES]; |
|
57 static bool _chatmessage_dirty = false; |
|
58 static bool _chatmessage_visible = false; |
|
59 |
|
60 /* The chatbox grows from the bottom so the coordinates are pixels from |
|
61 * the left and pixels from the bottom. The height is the maximum height */ |
|
62 static const PointDimension _chatmsg_box = {10, 30, 500, 150}; |
|
63 static uint8 _chatmessage_backup[150 * 500 * 6]; // (height * width) |
|
64 |
|
65 static inline uint GetChatMessageCount() |
|
66 { |
|
67 uint i; |
|
68 |
|
69 for (i = 0; i < MAX_CHAT_MESSAGES; i++) { |
|
70 if (_chatmsg_list[i].message[0] == '\0') break; |
|
71 } |
|
72 |
|
73 return i; |
|
74 } |
|
75 |
|
76 /* Add a text message to the 'chat window' to be shown |
|
77 * @param color The colour this message is to be shown in |
|
78 * @param duration The duration of the chat message in game-days |
|
79 * @param message message itself in printf() style */ |
|
80 void CDECL AddChatMessage(uint16 color, uint8 duration, const char *message, ...) |
|
81 { |
|
82 char buf[MAX_TEXTMESSAGE_LENGTH]; |
|
83 const char *bufp; |
|
84 va_list va; |
|
85 uint msg_count; |
|
86 uint16 lines; |
|
87 |
|
88 va_start(va, message); |
|
89 vsnprintf(buf, lengthof(buf), message, va); |
|
90 va_end(va); |
|
91 |
|
92 |
|
93 Utf8TrimString(buf, MAX_TEXTMESSAGE_LENGTH); |
|
94 |
|
95 /* Force linebreaks for strings that are too long */ |
|
96 lines = GB(FormatStringLinebreaks(buf, _chatmsg_box.width - 8), 0, 16) + 1; |
|
97 if (lines >= MAX_CHAT_MESSAGES) return; |
|
98 |
|
99 msg_count = GetChatMessageCount(); |
|
100 /* We want to add more chat messages than there is free space for, remove 'old' */ |
|
101 if (lines > MAX_CHAT_MESSAGES - msg_count) { |
|
102 int i = lines - (MAX_CHAT_MESSAGES - msg_count); |
|
103 memmove(&_chatmsg_list[0], &_chatmsg_list[i], sizeof(_chatmsg_list[0]) * (msg_count - i)); |
|
104 msg_count = MAX_CHAT_MESSAGES - lines; |
|
105 } |
|
106 |
|
107 for (bufp = buf; lines != 0; lines--) { |
|
108 ChatMessage *cmsg = &_chatmsg_list[msg_count++]; |
|
109 ttd_strlcpy(cmsg->message, bufp, sizeof(cmsg->message)); |
|
110 |
|
111 /* The default colour for a message is player colour. Replace this with |
|
112 * white for any additional lines */ |
|
113 cmsg->color = (bufp == buf && color & IS_PALETTE_COLOR) ? color : (0x1D - 15) | IS_PALETTE_COLOR; |
|
114 cmsg->end_date = _date + duration; |
|
115 |
|
116 bufp += strlen(bufp) + 1; // jump to 'next line' in the formatted string |
|
117 } |
|
118 |
|
119 _chatmessage_dirty = true; |
|
120 } |
|
121 |
|
122 void InitChatMessage() |
|
123 { |
|
124 uint i; |
|
125 |
|
126 for (i = 0; i < MAX_CHAT_MESSAGES; i++) { |
|
127 _chatmsg_list[i].message[0] = '\0'; |
|
128 } |
|
129 } |
|
130 |
|
131 /** Hide the chatbox */ |
|
132 void UndrawChatMessage() |
|
133 { |
|
134 if (_chatmessage_visible) { |
|
135 Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); |
|
136 /* Sometimes we also need to hide the cursor |
|
137 * This is because both textmessage and the cursor take a shot of the |
|
138 * screen before drawing. |
|
139 * Now the textmessage takes his shot and paints his data before the cursor |
|
140 * does, so in the shot of the cursor is the screen-data of the textmessage |
|
141 * included when the cursor hangs somewhere over the textmessage. To |
|
142 * avoid wrong repaints, we undraw the cursor in that case, and everything |
|
143 * looks nicely ;) |
|
144 * (and now hope this story above makes sense to you ;)) |
|
145 */ |
|
146 |
|
147 if (_cursor.visible) { |
|
148 if (_cursor.draw_pos.x + _cursor.draw_size.x >= _chatmsg_box.x && |
|
149 _cursor.draw_pos.x <= _chatmsg_box.x + _chatmsg_box.width && |
|
150 _cursor.draw_pos.y + _cursor.draw_size.y >= _screen.height - _chatmsg_box.y - _chatmsg_box.height && |
|
151 _cursor.draw_pos.y <= _screen.height - _chatmsg_box.y) { |
|
152 UndrawMouseCursor(); |
|
153 } |
|
154 } |
|
155 |
|
156 int x = _chatmsg_box.x; |
|
157 int y = _screen.height - _chatmsg_box.y - _chatmsg_box.height; |
|
158 int width = _chatmsg_box.width; |
|
159 int height = _chatmsg_box.height; |
|
160 if (y < 0) { |
|
161 height = max(height + y, min(_chatmsg_box.height, _screen.height)); |
|
162 y = 0; |
|
163 } |
|
164 if (x + width >= _screen.width) { |
|
165 width = _screen.width - x; |
|
166 } |
|
167 if (width <= 0 || height <= 0) return; |
|
168 |
|
169 _chatmessage_visible = false; |
|
170 /* Put our 'shot' back to the screen */ |
|
171 blitter->CopyFromBuffer(blitter->MoveTo(_screen.dst_ptr, x, y), _chatmessage_backup, width, height); |
|
172 /* And make sure it is updated next time */ |
|
173 _video_driver->MakeDirty(x, y, width, height); |
|
174 |
|
175 _chatmessage_dirty = true; |
|
176 } |
|
177 } |
|
178 |
|
179 /** Check if a message is expired every day */ |
|
180 void ChatMessageDailyLoop() |
|
181 { |
|
182 uint i; |
|
183 |
|
184 for (i = 0; i < MAX_CHAT_MESSAGES; i++) { |
|
185 ChatMessage *cmsg = &_chatmsg_list[i]; |
|
186 if (cmsg->message[0] == '\0') continue; |
|
187 |
|
188 /* Message has expired, remove from the list */ |
|
189 if (cmsg->end_date < _date) { |
|
190 /* Move the remaining messages over the current message */ |
|
191 if (i != MAX_CHAT_MESSAGES - 1) memmove(cmsg, cmsg + 1, sizeof(*cmsg) * (MAX_CHAT_MESSAGES - i - 1)); |
|
192 |
|
193 /* Mark the last item as empty */ |
|
194 _chatmsg_list[MAX_CHAT_MESSAGES - 1].message[0] = '\0'; |
|
195 _chatmessage_dirty = true; |
|
196 |
|
197 /* Go one item back, because we moved the array 1 to the left */ |
|
198 i--; |
|
199 } |
|
200 } |
|
201 } |
|
202 |
|
203 /** Draw the chat message-box */ |
|
204 void DrawChatMessage() |
|
205 { |
|
206 Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); |
|
207 if (!_chatmessage_dirty) return; |
|
208 |
|
209 /* First undraw if needed */ |
|
210 UndrawChatMessage(); |
|
211 |
|
212 if (_iconsole_mode == ICONSOLE_FULL) return; |
|
213 |
|
214 /* Check if we have anything to draw at all */ |
|
215 uint count = GetChatMessageCount(); |
|
216 if (count == 0) return; |
|
217 |
|
218 int x = _chatmsg_box.x; |
|
219 int y = _screen.height - _chatmsg_box.y - _chatmsg_box.height; |
|
220 int width = _chatmsg_box.width; |
|
221 int height = _chatmsg_box.height; |
|
222 if (y < 0) { |
|
223 height = max(height + y, min(_chatmsg_box.height, _screen.height)); |
|
224 y = 0; |
|
225 } |
|
226 if (x + width >= _screen.width) { |
|
227 width = _screen.width - x; |
|
228 } |
|
229 if (width <= 0 || height <= 0) return; |
|
230 |
|
231 assert(blitter->BufferSize(width, height) < (int)sizeof(_chatmessage_backup)); |
|
232 |
|
233 /* Make a copy of the screen as it is before painting (for undraw) */ |
|
234 blitter->CopyToBuffer(blitter->MoveTo(_screen.dst_ptr, x, y), _chatmessage_backup, width, height); |
|
235 |
|
236 _cur_dpi = &_screen; // switch to _screen painting |
|
237 |
|
238 /* Paint a half-transparent box behind the chat messages */ |
|
239 GfxFillRect( |
|
240 _chatmsg_box.x, |
|
241 _screen.height - _chatmsg_box.y - count * 13 - 2, |
|
242 _chatmsg_box.x + _chatmsg_box.width - 1, |
|
243 _screen.height - _chatmsg_box.y - 2, |
|
244 PALETTE_TO_TRANSPARENT, FILLRECT_RECOLOR // black, but with some alpha for background |
|
245 ); |
|
246 |
|
247 /* Paint the chat messages starting with the lowest at the bottom */ |
|
248 for (uint y = 13; count-- != 0; y += 13) { |
|
249 DoDrawString(_chatmsg_list[count].message, _chatmsg_box.x + 3, _screen.height - _chatmsg_box.y - y + 1, _chatmsg_list[count].color); |
|
250 } |
|
251 |
|
252 /* Make sure the data is updated next flush */ |
|
253 _video_driver->MakeDirty(x, y, width, height); |
|
254 |
|
255 _chatmessage_visible = true; |
|
256 _chatmessage_dirty = false; |
|
257 } |
|
258 |
38 |
259 /* Text Effects */ |
39 /* Text Effects */ |
260 /** |
40 /** |
261 * Mark the area of the text effect as dirty. |
41 * Mark the area of the text effect as dirty. |
262 * |
42 * |