9 #include "strings.h" |
9 #include "strings.h" |
10 #include "gfx.h" |
10 #include "gfx.h" |
11 #include "landscape.h" |
11 #include "landscape.h" |
12 #include "viewport.h" |
12 #include "viewport.h" |
13 #include "saveload.h" |
13 #include "saveload.h" |
14 #include "hal.h" |
|
15 #include "console.h" |
14 #include "console.h" |
16 #include "string.h" |
15 #include "string.h" |
17 #include "variables.h" |
16 #include "variables.h" |
18 #include "table/sprites.h" |
17 #include "table/sprites.h" |
|
18 #include "blitter/factory.hpp" |
19 #include <stdarg.h> /* va_list */ |
19 #include <stdarg.h> /* va_list */ |
20 #include "date.h" |
20 #include "date.h" |
|
21 #include "texteff.hpp" |
|
22 #include "video/video_driver.hpp" |
21 |
23 |
22 enum { |
24 enum { |
23 MAX_TEXTMESSAGE_LENGTH = 200, |
25 MAX_TEXTMESSAGE_LENGTH = 200, |
24 MAX_TEXT_MESSAGES = 30, |
26 INIT_NUM_TEXT_MESSAGES = 20, |
25 MAX_CHAT_MESSAGES = 10, |
27 MAX_CHAT_MESSAGES = 10, |
26 MAX_ANIMATED_TILES = 256, |
28 MAX_ANIMATED_TILES = 256, |
27 }; |
29 }; |
28 |
30 |
29 struct TextEffect { |
31 struct TextEffect { |
31 int32 x; |
33 int32 x; |
32 int32 y; |
34 int32 y; |
33 int32 right; |
35 int32 right; |
34 int32 bottom; |
36 int32 bottom; |
35 uint16 duration; |
37 uint16 duration; |
36 uint32 params_1; |
38 uint64 params_1; |
37 uint32 params_2; |
39 uint64 params_2; |
|
40 TextEffectMode mode; |
38 }; |
41 }; |
39 |
42 |
40 |
43 |
41 struct TextMessage { |
44 struct TextMessage { |
42 char message[MAX_TEXTMESSAGE_LENGTH]; |
45 char message[MAX_TEXTMESSAGE_LENGTH]; |
43 uint16 color; |
46 uint16 color; |
44 Date end_date; |
47 Date end_date; |
45 }; |
48 }; |
46 |
49 |
47 static TextEffect _text_effect_list[MAX_TEXT_MESSAGES]; |
50 static TextEffect *_text_effect_list = NULL; |
48 static TextMessage _textmsg_list[MAX_CHAT_MESSAGES]; |
51 static TextMessage _textmsg_list[MAX_CHAT_MESSAGES]; |
49 TileIndex _animated_tile_list[MAX_ANIMATED_TILES]; |
52 TileIndex _animated_tile_list[MAX_ANIMATED_TILES]; |
50 |
53 |
51 static bool _textmessage_dirty = false; |
54 static bool _textmessage_dirty = false; |
52 static bool _textmessage_visible = false; |
55 static bool _textmessage_visible = false; |
|
56 static uint16 _num_text_effects = INIT_NUM_TEXT_MESSAGES; |
53 |
57 |
54 /* The chatbox grows from the bottom so the coordinates are pixels from |
58 /* The chatbox grows from the bottom so the coordinates are pixels from |
55 * the left and pixels from the bottom. The height is the maximum height */ |
59 * the left and pixels from the bottom. The height is the maximum height */ |
56 static const Oblong _textmsg_box = {10, 30, 500, 150}; |
60 static const Oblong _textmsg_box = {10, 30, 500, 150}; |
57 static Pixel _textmessage_backup[150 * 500]; // (height * width) |
61 static uint8 _textmessage_backup[150 * 500 * 6]; // (height * width) |
58 |
|
59 extern void memcpy_pitch(void *dst, void *src, int w, int h, int srcpitch, int dstpitch); |
|
60 |
62 |
61 static inline uint GetTextMessageCount() |
63 static inline uint GetTextMessageCount() |
62 { |
64 { |
63 uint i; |
65 uint i; |
64 |
66 |
126 |
128 |
127 /** Hide the textbox */ |
129 /** Hide the textbox */ |
128 void UndrawTextMessage() |
130 void UndrawTextMessage() |
129 { |
131 { |
130 if (_textmessage_visible) { |
132 if (_textmessage_visible) { |
|
133 Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter(); |
131 /* Sometimes we also need to hide the cursor |
134 /* Sometimes we also need to hide the cursor |
132 * This is because both textmessage and the cursor take a shot of the |
135 * This is because both textmessage and the cursor take a shot of the |
133 * screen before drawing. |
136 * screen before drawing. |
134 * Now the textmessage takes his shot and paints his data before the cursor |
137 * Now the textmessage takes his shot and paints his data before the cursor |
135 * does, so in the shot of the cursor is the screen-data of the textmessage |
138 * does, so in the shot of the cursor is the screen-data of the textmessage |
161 } |
164 } |
162 if (width <= 0 || height <= 0) return; |
165 if (width <= 0 || height <= 0) return; |
163 |
166 |
164 _textmessage_visible = false; |
167 _textmessage_visible = false; |
165 /* Put our 'shot' back to the screen */ |
168 /* Put our 'shot' back to the screen */ |
166 memcpy_pitch( |
169 blitter->CopyFromBuffer(blitter->MoveTo(_screen.dst_ptr, x, y), _textmessage_backup, width, height); |
167 _screen.dst_ptr + x + y * _screen.pitch, |
|
168 _textmessage_backup, |
|
169 width, height, _textmsg_box.width, _screen.pitch); |
|
170 |
|
171 /* And make sure it is updated next time */ |
170 /* And make sure it is updated next time */ |
172 _video_driver->make_dirty(x, y, width, height); |
171 _video_driver->MakeDirty(x, y, width, height); |
173 |
172 |
174 _textmessage_dirty = true; |
173 _textmessage_dirty = true; |
175 } |
174 } |
176 } |
175 } |
177 |
176 |
224 if (x + width >= _screen.width) { |
224 if (x + width >= _screen.width) { |
225 width = _screen.width - x; |
225 width = _screen.width - x; |
226 } |
226 } |
227 if (width <= 0 || height <= 0) return; |
227 if (width <= 0 || height <= 0) return; |
228 |
228 |
|
229 assert(blitter->BufferSize(width, height) < (int)sizeof(_textmessage_backup)); |
|
230 |
229 /* Make a copy of the screen as it is before painting (for undraw) */ |
231 /* Make a copy of the screen as it is before painting (for undraw) */ |
230 memcpy_pitch( |
232 blitter->CopyToBuffer(blitter->MoveTo(_screen.dst_ptr, x, y), _textmessage_backup, width, height); |
231 _textmessage_backup, |
|
232 _screen.dst_ptr + x + y * _screen.pitch, |
|
233 width, height, _screen.pitch, _textmsg_box.width); |
|
234 |
233 |
235 _cur_dpi = &_screen; // switch to _screen painting |
234 _cur_dpi = &_screen; // switch to _screen painting |
236 |
235 |
237 /* Paint a half-transparent box behind the text messages */ |
236 /* Paint a half-transparent box behind the text messages */ |
238 GfxFillRect( |
237 GfxFillRect( |
247 for (uint y = 13; count-- != 0; y += 13) { |
246 for (uint y = 13; count-- != 0; y += 13) { |
248 DoDrawString(_textmsg_list[count].message, _textmsg_box.x + 3, _screen.height - _textmsg_box.y - y + 1, _textmsg_list[count].color); |
247 DoDrawString(_textmsg_list[count].message, _textmsg_box.x + 3, _screen.height - _textmsg_box.y - y + 1, _textmsg_list[count].color); |
249 } |
248 } |
250 |
249 |
251 /* Make sure the data is updated next flush */ |
250 /* Make sure the data is updated next flush */ |
252 _video_driver->make_dirty(x, y, width, height); |
251 _video_driver->MakeDirty(x, y, width, height); |
253 |
252 |
254 _textmessage_visible = true; |
253 _textmessage_visible = true; |
255 _textmessage_dirty = false; |
254 _textmessage_dirty = false; |
256 } |
255 } |
257 |
256 |
263 (te->right - te->x)*2 + te->x + 1, |
262 (te->right - te->x)*2 + te->x + 1, |
264 (te->bottom - (te->y - 1)) * 2 + (te->y - 1) + 1 |
263 (te->bottom - (te->y - 1)) * 2 + (te->y - 1) + 1 |
265 ); |
264 ); |
266 } |
265 } |
267 |
266 |
268 void AddTextEffect(StringID msg, int x, int y, uint16 duration) |
267 TextEffectID AddTextEffect(StringID msg, int x, int y, uint16 duration, TextEffectMode mode) |
269 { |
268 { |
270 TextEffect *te; |
269 TextEffect *te; |
271 int w; |
270 int w; |
272 char buffer[100]; |
271 char buffer[100]; |
273 |
272 TextEffectID i; |
274 if (_game_mode == GM_MENU) return; |
273 |
275 |
274 if (_game_mode == GM_MENU) return INVALID_TE_ID; |
276 for (te = _text_effect_list; te->string_id != INVALID_STRING_ID; ) { |
275 |
277 if (++te == endof(_text_effect_list)) return; |
276 /* Look for a free spot in the text effect array */ |
278 } |
277 for (i = 0; i < _num_text_effects; i++) { |
279 |
278 if (_text_effect_list[i].string_id == INVALID_STRING_ID) break; |
|
279 } |
|
280 |
|
281 /* If there is none found, we grow the array */ |
|
282 if (i == _num_text_effects) { |
|
283 _num_text_effects += 25; |
|
284 _text_effect_list = (TextEffect*) realloc(_text_effect_list, _num_text_effects * sizeof(TextEffect)); |
|
285 for (; i < _num_text_effects; i++) _text_effect_list[i].string_id = INVALID_STRING_ID; |
|
286 i = _num_text_effects - 1; |
|
287 } |
|
288 |
|
289 te = &_text_effect_list[i]; |
|
290 |
|
291 /* Start defining this object */ |
280 te->string_id = msg; |
292 te->string_id = msg; |
281 te->duration = duration; |
293 te->duration = duration; |
282 te->y = y - 5; |
294 te->y = y - 5; |
283 te->bottom = y + 5; |
295 te->bottom = y + 5; |
284 te->params_1 = GetDParam(0); |
296 te->params_1 = GetDParam(0); |
285 te->params_2 = GetDParam(4); |
297 te->params_2 = GetDParam(4); |
|
298 te->mode = mode; |
286 |
299 |
287 GetString(buffer, msg, lastof(buffer)); |
300 GetString(buffer, msg, lastof(buffer)); |
288 w = GetStringBoundingBox(buffer).width; |
301 w = GetStringBoundingBox(buffer).width; |
289 |
302 |
290 te->x = x - (w >> 1); |
303 te->x = x - (w >> 1); |
291 te->right = x + (w >> 1) - 1; |
304 te->right = x + (w >> 1) - 1; |
292 MarkTextEffectAreaDirty(te); |
305 MarkTextEffectAreaDirty(te); |
|
306 |
|
307 return i; |
|
308 } |
|
309 |
|
310 void UpdateTextEffect(TextEffectID te_id, StringID msg) |
|
311 { |
|
312 assert(te_id < _num_text_effects); |
|
313 TextEffect *te; |
|
314 |
|
315 /* Update details */ |
|
316 te = &_text_effect_list[te_id]; |
|
317 te->string_id = msg; |
|
318 te->params_1 = GetDParam(0); |
|
319 te->params_2 = GetDParam(4); |
|
320 |
|
321 MarkTextEffectAreaDirty(te); |
|
322 } |
|
323 |
|
324 void RemoveTextEffect(TextEffectID te_id) |
|
325 { |
|
326 assert(te_id < _num_text_effects); |
|
327 TextEffect *te; |
|
328 |
|
329 te = &_text_effect_list[te_id]; |
|
330 MarkTextEffectAreaDirty(te); |
|
331 te->string_id = INVALID_STRING_ID; |
293 } |
332 } |
294 |
333 |
295 static void MoveTextEffect(TextEffect *te) |
334 static void MoveTextEffect(TextEffect *te) |
296 { |
335 { |
|
336 /* Never expire for duration of 0xFFFF */ |
|
337 if (te->duration == 0xFFFF) return; |
297 if (te->duration < 8) { |
338 if (te->duration < 8) { |
298 te->string_id = INVALID_STRING_ID; |
339 te->string_id = INVALID_STRING_ID; |
299 } else { |
340 } else { |
300 te->duration -= 8; |
341 te->duration -= 8; |
301 te->y--; |
342 te->y--; |
304 MarkTextEffectAreaDirty(te); |
345 MarkTextEffectAreaDirty(te); |
305 } |
346 } |
306 |
347 |
307 void MoveAllTextEffects() |
348 void MoveAllTextEffects() |
308 { |
349 { |
309 TextEffect *te; |
350 for (TextEffectID i = 0; i < _num_text_effects; i++) { |
310 |
351 TextEffect *te = &_text_effect_list[i]; |
311 for (te = _text_effect_list; te != endof(_text_effect_list); te++) { |
352 if (te->string_id != INVALID_STRING_ID && te->mode == TE_RISING) MoveTextEffect(te); |
312 if (te->string_id != INVALID_STRING_ID) MoveTextEffect(te); |
|
313 } |
353 } |
314 } |
354 } |
315 |
355 |
316 void InitTextEffects() |
356 void InitTextEffects() |
317 { |
357 { |
318 TextEffect *te; |
358 if (_text_effect_list == NULL) _text_effect_list = MallocT<TextEffect>(_num_text_effects); |
319 |
359 |
320 for (te = _text_effect_list; te != endof(_text_effect_list); te++) { |
360 for (TextEffectID i = 0; i < _num_text_effects; i++) _text_effect_list[i].string_id = INVALID_STRING_ID; |
321 te->string_id = INVALID_STRING_ID; |
|
322 } |
|
323 } |
361 } |
324 |
362 |
325 void DrawTextEffects(DrawPixelInfo *dpi) |
363 void DrawTextEffects(DrawPixelInfo *dpi) |
326 { |
364 { |
327 const TextEffect* te; |
|
328 |
|
329 switch (dpi->zoom) { |
365 switch (dpi->zoom) { |
330 case ZOOM_LVL_NORMAL: |
366 case ZOOM_LVL_NORMAL: |
331 for (te = _text_effect_list; te != endof(_text_effect_list); te++) { |
367 for (TextEffectID i = 0; i < _num_text_effects; i++) { |
|
368 TextEffect *te = &_text_effect_list[i]; |
332 if (te->string_id != INVALID_STRING_ID && |
369 if (te->string_id != INVALID_STRING_ID && |
333 dpi->left <= te->right && |
370 dpi->left <= te->right && |
334 dpi->top <= te->bottom && |
371 dpi->top <= te->bottom && |
335 dpi->left + dpi->width > te->x && |
372 dpi->left + dpi->width > te->x && |
336 dpi->top + dpi->height > te->y) { |
373 dpi->top + dpi->height > te->y) { |
337 AddStringToDraw(te->x, te->y, te->string_id, te->params_1, te->params_2); |
374 if (te->mode == TE_RISING || (_patches.loading_indicators && !HASBIT(_transparent_opt, TO_LOADING))) { |
|
375 AddStringToDraw(te->x, te->y, te->string_id, te->params_1, te->params_2); |
|
376 } |
338 } |
377 } |
339 } |
378 } |
340 break; |
379 break; |
341 |
380 |
342 case ZOOM_LVL_OUT_2X: |
381 case ZOOM_LVL_OUT_2X: |
343 for (te = _text_effect_list; te != endof(_text_effect_list); te++) { |
382 for (TextEffectID i = 0; i < _num_text_effects; i++) { |
|
383 TextEffect *te = &_text_effect_list[i]; |
344 if (te->string_id != INVALID_STRING_ID && |
384 if (te->string_id != INVALID_STRING_ID && |
345 dpi->left <= te->right * 2 - te->x && |
385 dpi->left <= te->right * 2 - te->x && |
346 dpi->top <= te->bottom * 2 - te->y && |
386 dpi->top <= te->bottom * 2 - te->y && |
347 dpi->left + dpi->width > te->x && |
387 dpi->left + dpi->width > te->x && |
348 dpi->top + dpi->height > te->y) { |
388 dpi->top + dpi->height > te->y) { |
349 AddStringToDraw(te->x, te->y, (StringID)(te->string_id-1), te->params_1, te->params_2); |
389 if (te->mode == TE_RISING || (_patches.loading_indicators && !HASBIT(_transparent_opt, TO_LOADING))) { |
|
390 AddStringToDraw(te->x, te->y, (StringID)(te->string_id - 1), te->params_1, te->params_2); |
|
391 } |
350 } |
392 } |
351 } |
393 } |
352 break; |
394 break; |
353 |
395 |
354 case ZOOM_LVL_OUT_4X: |
396 case ZOOM_LVL_OUT_4X: |
355 case ZOOM_LVL_OUT_8X: |
397 case ZOOM_LVL_OUT_8X: |
356 case ZOOM_LVL_OUT_16X: |
|
357 break; |
398 break; |
358 |
399 |
359 default: NOT_REACHED(); |
400 default: NOT_REACHED(); |
360 } |
401 } |
361 } |
402 } |