22 #include "player_face.h" |
22 #include "player_face.h" |
23 |
23 |
24 #include "table/sprites.h" |
24 #include "table/sprites.h" |
25 #include "table/strings.h" |
25 #include "table/strings.h" |
26 |
26 |
27 /** @file news_gui.cpp |
|
28 * |
|
29 * News system is realized as a FIFO queue (in an array) |
|
30 * The positions in the queue can't be rearranged, we only access |
|
31 * the array elements through pointers to the elements. Once the |
|
32 * array is full, the oldest entry (\a _oldest_news) is being overwritten |
|
33 * by the newest (\a _latest_news). |
|
34 * |
|
35 * \verbatim |
|
36 * oldest current lastest |
|
37 * | | | |
|
38 * [O------------F-------------C---------L ] |
|
39 * | |
|
40 * forced |
|
41 * \endverbatim |
|
42 * |
|
43 * Of course by using an array we can have situations like |
|
44 * |
|
45 * \verbatim |
|
46 * [----L O-----F---------C-----------------] |
|
47 * This is where we have wrapped around the array and have |
|
48 * (MAX_NEWS - O) + L news items |
|
49 * \endverbatim |
|
50 */ |
|
51 |
|
52 #define NB_WIDG_PER_SETTING 4 |
27 #define NB_WIDG_PER_SETTING 4 |
53 |
|
54 typedef byte NewsID; |
|
55 #define INVALID_NEWS 255 |
|
56 |
28 |
57 NewsItem _statusbar_news_item; |
29 NewsItem _statusbar_news_item; |
58 bool _news_ticker_sound; |
30 bool _news_ticker_sound; |
59 static NewsItem *_news_items = NULL; ///< The news FIFO queue |
31 |
60 static uint _max_news_items = 0; ///< size of news FIFO queue |
32 static uint MIN_NEWS_AMOUNT = 30; ///< prefered minimum amount of news messages |
61 static NewsID _current_news = INVALID_NEWS; ///< points to news item that should be shown next |
33 static uint _total_news = 0; ///< current number of news items |
62 static NewsID _oldest_news = 0; ///< points to first item in fifo queue |
34 static NewsItem *_oldest_news = NULL; ///< head of news items queue |
63 static NewsID _latest_news = INVALID_NEWS; ///< points to last item in fifo queue |
35 static NewsItem *_latest_news = NULL; ///< tail of news items queue |
64 |
36 |
65 /** Forced news item. |
37 /** Forced news item. |
66 * Users can force an item by accessing the history or "last message". |
38 * Users can force an item by accessing the history or "last message". |
67 * If the message being shown was forced by the user, its index is stored in |
39 * If the message being shown was forced by the user, a pointer is stored |
68 * _forced_news. Otherwise, \a _forced_news variable is INVALID_NEWS. */ |
40 * in _forced_news. Otherwise, \a _forced_news variable is NULL. */ |
69 static NewsID _forced_news = INVALID_NEWS; |
41 static NewsItem *_forced_news = NULL; ///< item the user has asked for |
70 |
42 |
71 static uint _total_news = 0; ///< Number of news items in FIFO queue @see _news_items |
43 /** Current news item (last item shown regularly). */ |
72 static void MoveToNextItem(); |
44 static NewsItem *_current_news = NULL; |
73 |
45 |
74 |
46 |
75 typedef void DrawNewsCallbackProc(struct Window *w, const NewsItem *ni); |
47 typedef void DrawNewsCallbackProc(struct Window *w, const NewsItem *ni); |
76 void DrawNewsNewVehicleAvail(Window *w, const NewsItem *ni); |
48 void DrawNewsNewVehicleAvail(Window *w, const NewsItem *ni); |
77 |
49 |
440 } |
409 } |
441 |
410 |
442 /** Initialize the news-items data structures */ |
411 /** Initialize the news-items data structures */ |
443 void InitNewsItemStructs() |
412 void InitNewsItemStructs() |
444 { |
413 { |
445 free(_news_items); |
414 for (NewsItem *ni = _oldest_news; ni != NULL; ) { |
446 _max_news_items = max(ScaleByMapSize(30), 30U); |
415 NewsItem *next = ni->next; |
447 _news_items = CallocT<NewsItem>(_max_news_items); |
416 delete ni; |
448 _current_news = INVALID_NEWS; |
417 ni = next; |
449 _oldest_news = 0; |
418 } |
450 _latest_news = INVALID_NEWS; |
419 |
451 _forced_news = INVALID_NEWS; |
|
452 _total_news = 0; |
420 _total_news = 0; |
|
421 _oldest_news = NULL; |
|
422 _latest_news = NULL; |
|
423 _forced_news = NULL; |
|
424 _current_news = NULL; |
453 } |
425 } |
454 |
426 |
455 /** |
427 /** |
456 * Return the correct index in the pseudo-fifo |
428 * Are we ready to show another news item? |
457 * queue and deals with overflows when increasing the index |
429 * Only if nothing is in the newsticker and no newspaper is displayed |
458 */ |
430 */ |
459 static inline NewsID IncreaseIndex(NewsID i) |
431 static bool ReadyForNextItem() |
460 { |
432 { |
461 assert(i != INVALID_NEWS); |
433 NewsItem *ni = _forced_news == NULL ? _current_news : _forced_news; |
462 return (i + 1) % _max_news_items; |
434 if (ni == NULL) return true; |
463 } |
435 |
464 |
436 /* Ticker message |
465 /** |
437 * Check if the status bar message is still being displayed? */ |
466 * Return the correct index in the pseudo-fifo |
438 if (IsNewsTickerShown()) return false; |
467 * queue and deals with overflows when decreasing the index |
439 |
468 */ |
440 /* Newspaper message, decrement duration counter */ |
469 static inline NewsID DecreaseIndex(NewsID i) |
441 if (ni->duration != 0) ni->duration--; |
470 { |
442 |
471 assert(i != INVALID_NEWS); |
443 /* neither newsticker nor newspaper are running */ |
472 return (i + _max_news_items - 1) % _max_news_items; |
444 return (ni->duration == 0 || FindWindowById(WC_NEWS_WINDOW, 0) == NULL); |
|
445 } |
|
446 |
|
447 /** Move to the next news item */ |
|
448 static void MoveToNextItem() |
|
449 { |
|
450 DeleteWindowById(WC_NEWS_WINDOW, 0); |
|
451 _forced_news = NULL; |
|
452 |
|
453 /* if we're not at the last item, then move on */ |
|
454 if (_current_news != _latest_news) { |
|
455 _current_news = (_current_news == NULL) ? _oldest_news : _current_news->next; |
|
456 NewsItem *ni = _current_news; |
|
457 const NewsType type = _news_subtype_data[ni->subtype].type; |
|
458 |
|
459 /* check the date, don't show too old items */ |
|
460 if (_date - _news_type_data[type].age > ni->date) return; |
|
461 |
|
462 switch (_news_type_data[type].display) { |
|
463 default: NOT_REACHED(); |
|
464 case ND_OFF: // Off - show nothing only a small reminder in the status bar |
|
465 InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SHOW_REMINDER); |
|
466 break; |
|
467 |
|
468 case ND_SUMMARY: // Summary - show ticker, but if forced big, cascade to full |
|
469 if (!(ni->flags & NF_FORCE_BIG)) { |
|
470 ShowTicker(ni); |
|
471 break; |
|
472 } |
|
473 /* Fallthrough */ |
|
474 |
|
475 case ND_FULL: // Full - show newspaper |
|
476 ShowNewspaper(ni); |
|
477 break; |
|
478 } |
|
479 } |
473 } |
480 } |
474 |
481 |
475 /** |
482 /** |
476 * Add a new newsitem to be shown. |
483 * Add a new newsitem to be shown. |
477 * @param string String to display |
484 * @param string String to display |
483 */ |
490 */ |
484 void AddNewsItem(StringID string, NewsSubtype subtype, uint data_a, uint data_b) |
491 void AddNewsItem(StringID string, NewsSubtype subtype, uint data_a, uint data_b) |
485 { |
492 { |
486 if (_game_mode == GM_MENU) return; |
493 if (_game_mode == GM_MENU) return; |
487 |
494 |
488 /* check the rare case that the oldest (to be overwritten) news item is open */ |
495 /* Create new news item node */ |
489 if (_total_news == _max_news_items && (_oldest_news == _current_news || _oldest_news == _forced_news)) { |
496 NewsItem *ni = new NewsItem; |
490 MoveToNextItem(); |
|
491 } |
|
492 |
|
493 if (_total_news < _max_news_items) _total_news++; |
|
494 |
|
495 /* Increase _latest_news. If we have no news yet, use _oldest news as an |
|
496 * index. We cannot use 0 as _oldest_news can jump around due to |
|
497 * DeleteVehicleNews */ |
|
498 NewsID l_news = _latest_news; |
|
499 _latest_news = (_latest_news == INVALID_NEWS) ? _oldest_news : IncreaseIndex(_latest_news); |
|
500 |
|
501 /* If the fifo-buffer is full, overwrite the oldest entry */ |
|
502 if (l_news != INVALID_NEWS && _latest_news == _oldest_news) { |
|
503 assert(_total_news == _max_news_items); |
|
504 _oldest_news = IncreaseIndex(_oldest_news); |
|
505 } |
|
506 |
|
507 /*DEBUG(misc, 0, "+cur %3d, old %2d, lat %3d, for %3d, tot %2d", |
|
508 _current_news, _oldest_news, _latest_news, _forced_news, _total_news);*/ |
|
509 |
|
510 /* Add news to _latest_news */ |
|
511 NewsItem *ni = &_news_items[_latest_news]; |
|
512 memset(ni, 0, sizeof(*ni)); |
|
513 |
497 |
514 ni->string_id = string; |
498 ni->string_id = string; |
515 ni->subtype = subtype; |
499 ni->subtype = subtype; |
516 ni->flags = _news_subtype_data[subtype].flags; |
500 ni->flags = _news_subtype_data[subtype].flags; |
517 |
501 |
521 ni->data_a = data_a; |
505 ni->data_a = data_a; |
522 ni->data_b = data_b; |
506 ni->data_b = data_b; |
523 ni->date = _date; |
507 ni->date = _date; |
524 CopyOutDParam(ni->params, 0, lengthof(ni->params)); |
508 CopyOutDParam(ni->params, 0, lengthof(ni->params)); |
525 |
509 |
526 Window *w = FindWindowById(WC_MESSAGE_HISTORY, 0); |
510 if (_total_news++ == 0) { |
527 if (w == NULL) return; |
511 assert(_oldest_news == NULL); |
528 w->SetDirty(); |
512 _oldest_news = ni; |
529 w->vscroll.count = _total_news; |
513 ni->prev = NULL; |
|
514 } else { |
|
515 assert(_latest_news->next == NULL); |
|
516 _latest_news->next = ni; |
|
517 ni->prev = _latest_news; |
|
518 } |
|
519 |
|
520 ni->next = NULL; |
|
521 _latest_news = ni; |
|
522 |
|
523 InvalidateWindow(WC_MESSAGE_HISTORY, 0); |
|
524 } |
|
525 |
|
526 /** Delete a news item from the queue */ |
|
527 static void DeleteNewsItem(NewsItem *ni) |
|
528 { |
|
529 if (_forced_news == ni) { |
|
530 /* about to remove the currently forced item; skip to next */ |
|
531 MoveToNextItem(); |
|
532 } |
|
533 |
|
534 if ((_current_news == ni) && (FindWindowById(WC_MESSAGE_HISTORY, 0) != NULL)) { |
|
535 /* about to remove the currently displayed item; also skip */ |
|
536 MoveToNextItem(); |
|
537 } |
|
538 |
|
539 /* delete item */ |
|
540 |
|
541 if (ni->prev != NULL) { |
|
542 ni->prev->next = ni->next; |
|
543 } else { |
|
544 assert(_oldest_news == ni); |
|
545 _oldest_news = ni->next; |
|
546 } |
|
547 |
|
548 if (ni->next != NULL) { |
|
549 ni->next->prev = ni->prev; |
|
550 } else { |
|
551 assert(_latest_news == ni); |
|
552 _latest_news = ni->prev; |
|
553 } |
|
554 |
|
555 if (_current_news == ni) _current_news = ni->prev; |
|
556 _total_news--; |
|
557 delete ni; |
|
558 |
|
559 InvalidateWindow(WC_MESSAGE_HISTORY, 0); |
530 } |
560 } |
531 |
561 |
532 void DeleteVehicleNews(VehicleID vid, StringID news) |
562 void DeleteVehicleNews(VehicleID vid, StringID news) |
533 { |
563 { |
534 for (NewsID n = _oldest_news; _latest_news != INVALID_NEWS; n = IncreaseIndex(n)) { |
564 NewsItem *ni = _oldest_news; |
535 const NewsItem *ni = &_news_items[n]; |
565 |
536 |
566 while (ni != NULL) { |
537 if (ni->flags & NF_VEHICLE && |
567 if (ni->flags & NF_VEHICLE && |
538 ni->data_a == vid && |
568 ni->data_a == vid && |
539 (news == INVALID_STRING_ID || ni->string_id == news)) { |
569 (news == INVALID_STRING_ID || ni->string_id == news)) { |
540 /* If we delete a forced news and it is just before the current news |
570 /* grab a pointer to the next item before ni is freed */ |
541 * then we need to advance to the next news (if any) */ |
571 NewsItem *p = ni->next; |
542 if (_forced_news == n) MoveToNextItem(); |
572 DeleteNewsItem(ni); |
543 if (_forced_news == INVALID_NEWS && _current_news == n) MoveToNextItem(); |
573 ni = p; |
544 _total_news--; |
574 } else { |
545 |
575 ni = ni->next; |
546 /* If this is the last news item, invalidate _latest_news */ |
576 } |
547 if (_total_news == 0) { |
577 } |
548 assert(_latest_news == _oldest_news); |
578 } |
549 _latest_news = INVALID_NEWS; |
579 |
550 _current_news = INVALID_NEWS; |
580 void RemoveOldNewsItems() |
551 } |
581 { |
552 |
582 NewsItem *next; |
553 /* Since we only imitate a FIFO removing an arbitrary element does need |
583 for (NewsItem *cur = _oldest_news; _total_news > MIN_NEWS_AMOUNT && cur != NULL; cur = next) { |
554 * some magic. Remove the item by shifting head towards the tail. eg |
584 next = cur->next; |
555 * oldest remove last |
585 if (_date - _news_type_data[_news_subtype_data[cur->subtype].type].age * _settings.gui.news_message_timeout > cur->date) DeleteNewsItem(cur); |
556 * | | | |
|
557 * [------O--------n-----L--] |
|
558 * will become (change dramatized to make clear) |
|
559 * [---------O-----------L--] |
|
560 * We also need an update of the current, forced and visible (open window) |
|
561 * news's as this shifting could change the items they were pointing to */ |
|
562 if (_total_news != 0) { |
|
563 NewsWindow *w = dynamic_cast<NewsWindow*>(FindWindowById(WC_NEWS_WINDOW, 0)); |
|
564 NewsID visible_news = (w != NULL) ? (NewsID)(w->ni - _news_items) : INVALID_NEWS; |
|
565 |
|
566 for (NewsID i = n;; i = DecreaseIndex(i)) { |
|
567 _news_items[i] = _news_items[DecreaseIndex(i)]; |
|
568 |
|
569 if (i != _latest_news) { |
|
570 if (i == _current_news) _current_news = IncreaseIndex(_current_news); |
|
571 if (i == _forced_news) _forced_news = IncreaseIndex(_forced_news); |
|
572 if (i == visible_news) w->ni = &_news_items[IncreaseIndex(visible_news)]; |
|
573 } |
|
574 |
|
575 if (i == _oldest_news) break; |
|
576 } |
|
577 _oldest_news = IncreaseIndex(_oldest_news); |
|
578 } |
|
579 |
|
580 /*DEBUG(misc, 0, "-cur %3d, old %2d, lat %3d, for %3d, tot %2d", |
|
581 _current_news, _oldest_news, _latest_news, _forced_news, _total_news);*/ |
|
582 |
|
583 Window *w = FindWindowById(WC_MESSAGE_HISTORY, 0); |
|
584 if (w != NULL) { |
|
585 w->SetDirty(); |
|
586 w->vscroll.count = _total_news; |
|
587 } |
|
588 } |
|
589 |
|
590 if (n == _latest_news) break; |
|
591 } |
|
592 } |
|
593 |
|
594 /** |
|
595 * Are we ready to show another news item? |
|
596 * Only if nothing is in the newsticker and no newspaper is displayed |
|
597 */ |
|
598 static bool ReadyForNextItem() |
|
599 { |
|
600 NewsID item = (_forced_news == INVALID_NEWS) ? _current_news : _forced_news; |
|
601 |
|
602 if (item >= _max_news_items) return true; |
|
603 NewsItem *ni = &_news_items[item]; |
|
604 |
|
605 /* Ticker message |
|
606 * Check if the status bar message is still being displayed? */ |
|
607 if (IsNewsTickerShown()) return false; |
|
608 |
|
609 /* Newspaper message, decrement duration counter */ |
|
610 if (ni->duration != 0) ni->duration--; |
|
611 |
|
612 /* neither newsticker nor newspaper are running */ |
|
613 return (ni->duration == 0 || FindWindowById(WC_NEWS_WINDOW, 0) == NULL); |
|
614 } |
|
615 |
|
616 /** Move to the next news item */ |
|
617 static void MoveToNextItem() |
|
618 { |
|
619 DeleteWindowById(WC_NEWS_WINDOW, 0); |
|
620 _forced_news = INVALID_NEWS; |
|
621 |
|
622 /* if we're not at the last item, then move on */ |
|
623 if (_current_news != _latest_news) { |
|
624 _current_news = (_current_news == INVALID_NEWS) ? _oldest_news : IncreaseIndex(_current_news); |
|
625 NewsItem *ni = &_news_items[_current_news]; |
|
626 const NewsType type = _news_subtype_data[ni->subtype].type; |
|
627 |
|
628 /* check the date, don't show too old items */ |
|
629 if (_date - _news_type_data[type].age > ni->date) return; |
|
630 |
|
631 switch (_news_type_data[type].display) { |
|
632 default: NOT_REACHED(); |
|
633 case ND_OFF: // Off - show nothing only a small reminder in the status bar |
|
634 InvalidateWindowData(WC_STATUS_BAR, 0, SBI_SHOW_REMINDER); |
|
635 break; |
|
636 |
|
637 case ND_SUMMARY: // Summary - show ticker, but if forced big, cascade to full |
|
638 if (!(ni->flags & NF_FORCE_BIG)) { |
|
639 ShowTicker(ni); |
|
640 break; |
|
641 } |
|
642 /* Fallthrough */ |
|
643 |
|
644 case ND_FULL: // Full - show newspaper |
|
645 ShowNewspaper(ni); |
|
646 break; |
|
647 } |
|
648 } |
586 } |
649 } |
587 } |
650 |
588 |
651 void NewsLoop() |
589 void NewsLoop() |
652 { |
590 { |
653 /* no news item yet */ |
591 /* no news item yet */ |
654 if (_total_news == 0) return; |
592 if (_total_news == 0) return; |
655 |
593 |
|
594 static byte _last_clean_month = 0; |
|
595 |
|
596 if (_last_clean_month != _cur_month) { |
|
597 RemoveOldNewsItems(); |
|
598 _last_clean_month = _cur_month; |
|
599 } |
|
600 |
656 if (ReadyForNextItem()) MoveToNextItem(); |
601 if (ReadyForNextItem()) MoveToNextItem(); |
657 } |
602 } |
658 |
603 |
659 /** Do a forced show of a specific message */ |
604 /** Do a forced show of a specific message */ |
660 static void ShowNewsMessage(NewsID i) |
605 static void ShowNewsMessage(NewsItem *ni) |
661 { |
606 { |
662 if (_total_news == 0) return; |
607 assert(_total_news != 0); |
663 |
608 |
664 /* Delete the news window */ |
609 /* Delete the news window */ |
665 DeleteWindowById(WC_NEWS_WINDOW, 0); |
610 DeleteWindowById(WC_NEWS_WINDOW, 0); |
666 |
611 |
667 /* setup forced news item */ |
612 /* setup forced news item */ |
668 _forced_news = i; |
613 _forced_news = ni; |
669 |
614 |
670 if (_forced_news != INVALID_NEWS) { |
615 if (_forced_news != NULL) { |
671 NewsItem *ni = &_news_items[_forced_news]; |
|
672 ni->duration = 555; |
616 ni->duration = 555; |
673 ni->flags |= NF_FORCE_BIG; |
617 ni->flags |= NF_FORCE_BIG; |
674 DeleteWindowById(WC_NEWS_WINDOW, 0); |
618 DeleteWindowById(WC_NEWS_WINDOW, 0); |
675 ShowNewspaper(ni); |
619 ShowNewspaper(ni); |
676 } |
620 } |
677 } |
621 } |
678 |
622 |
679 /** Show previous news item */ |
623 /** Show previous news item */ |
680 void ShowLastNewsMessage() |
624 void ShowLastNewsMessage() |
681 { |
625 { |
682 if (_forced_news == INVALID_NEWS) { |
626 if (_total_news == 0) { |
|
627 return; |
|
628 } else if (_forced_news == NULL) { |
683 /* Not forced any news yet, show the current one, unless a news window is |
629 /* Not forced any news yet, show the current one, unless a news window is |
684 * open (which can only be the current one), then show the previous item */ |
630 * open (which can only be the current one), then show the previous item */ |
685 const Window *w = FindWindowById(WC_NEWS_WINDOW, 0); |
631 const Window *w = FindWindowById(WC_NEWS_WINDOW, 0); |
686 ShowNewsMessage((w == NULL || (_current_news == _oldest_news)) ? _current_news : DecreaseIndex(_current_news)); |
632 ShowNewsMessage((w == NULL || (_current_news == _oldest_news)) ? _current_news : _current_news->prev); |
687 } else if (_forced_news == _oldest_news) { |
633 } else if (_forced_news == _oldest_news) { |
688 /* We have reached the oldest news, start anew with the latest */ |
634 /* We have reached the oldest news, start anew with the latest */ |
689 ShowNewsMessage(_latest_news); |
635 ShowNewsMessage(_latest_news); |
690 } else { |
636 } else { |
691 /* 'Scrolling' through news history show each one in turn */ |
637 /* 'Scrolling' through news history show each one in turn */ |
692 ShowNewsMessage(DecreaseIndex(_forced_news)); |
638 ShowNewsMessage(_forced_news->prev); |
693 } |
639 } |
694 } |
640 } |
695 |
641 |
696 |
|
697 /* return news by number, with 0 being the most |
|
698 * recent news. Returns INVALID_NEWS if end of queue reached. */ |
|
699 static NewsID getNews(NewsID i) |
|
700 { |
|
701 if (i >= _total_news) return INVALID_NEWS; |
|
702 |
|
703 if (_latest_news < i) { |
|
704 i = _latest_news + _max_news_items - i; |
|
705 } else { |
|
706 i = _latest_news - i; |
|
707 } |
|
708 |
|
709 i %= _max_news_items; |
|
710 return i; |
|
711 } |
|
712 |
642 |
713 /** |
643 /** |
714 * Draw an unformatted news message truncated to a maximum length. If |
644 * Draw an unformatted news message truncated to a maximum length. If |
715 * length exceeds maximum length it will be postfixed by '...' |
645 * length exceeds maximum length it will be postfixed by '...' |
716 * @param x,y position of the string |
646 * @param x,y position of the string |