diff --git a/src/openrct2-ui/windows/News.cpp b/src/openrct2-ui/windows/News.cpp index f43e01bf65..0de33f3f4f 100644 --- a/src/openrct2-ui/windows/News.cpp +++ b/src/openrct2-ui/windows/News.cpp @@ -150,7 +150,7 @@ static void window_news_update(rct_window* w) j = w->news.var_480; w->news.var_480 = -1; - for (i = 11; i < 61; i++) + for (i = NEWS_ITEM_HISTORY_START; i < MAX_NEWS_ITEMS; i++) { if (news_item_is_empty(i)) return; @@ -188,7 +188,7 @@ static void window_news_scrollgetsize(rct_window* w, int32_t scrollIndex, int32_ int32_t itemHeight = window_news_get_item_height(); *height = 0; - for (int32_t i = 11; i < 61; i++) + for (int32_t i = NEWS_ITEM_HISTORY_START; i < MAX_NEWS_ITEMS; i++) { if (news_item_is_empty(i)) break; @@ -208,7 +208,7 @@ static void window_news_scrollmousedown(rct_window* w, int32_t scrollIndex, cons buttonIndex = 0; auto mutableScreenCoords = screenCoords; - for (i = 11; i < 61; i++) + for (i = NEWS_ITEM_HISTORY_START; i < MAX_NEWS_ITEMS; i++) { if (news_item_is_empty(i)) break; @@ -238,7 +238,7 @@ static void window_news_scrollmousedown(rct_window* w, int32_t scrollIndex, cons if (buttonIndex != 0) { - w->news.var_480 = i - 11; + w->news.var_480 = i - NEWS_ITEM_HISTORY_START; w->news.var_482 = buttonIndex; w->news.var_484 = 4; w->Invalidate(); @@ -266,7 +266,7 @@ static void window_news_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32 int32_t i, x, y, yy, press; y = 0; - for (i = 11; i < 61; i++) + for (i = NEWS_ITEM_HISTORY_START; i < MAX_NEWS_ITEMS; i++) { NewsItem* const newsItem = news_item_get(i); if (news_item_is_empty(i)) @@ -301,7 +301,7 @@ static void window_news_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32 press = 0; if (w->news.var_480 != -1) { - const uint8_t idx = 11 + w->news.var_480; + const uint8_t idx = NEWS_ITEM_HISTORY_START + w->news.var_480; news_item_is_valid_idx(idx); if (i == idx && w->news.var_482 == 1) press = INSET_RECT_FLAG_BORDER_INSET; @@ -378,7 +378,7 @@ static void window_news_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32 press = 0; if (w->news.var_480 != -1) { - const uint8_t idx = 11 + w->news.var_480; + const uint8_t idx = NEWS_ITEM_HISTORY_START + w->news.var_480; news_item_is_valid_idx(idx); if (i == idx && w->news.var_482 == 2) press = 0x20; diff --git a/src/openrct2/management/NewsItem.cpp b/src/openrct2/management/NewsItem.cpp index 17ad8d09e6..cd264c428c 100644 --- a/src/openrct2/management/NewsItem.cpp +++ b/src/openrct2/management/NewsItem.cpp @@ -23,7 +23,7 @@ #include "../windows/Intent.h" #include "../world/Sprite.h" -NewsItem gNewsItems[MAX_NEWS_ITEMS]; +NewsItemQueue gNewsItems; /** rct2: 0x0097BE7C */ const uint8_t news_type_properties[] = { @@ -39,7 +39,25 @@ const uint8_t news_type_properties[] = { NEWS_TYPE_HAS_SUBJECT, // NEWS_ITEM_GRAPH }; -static int32_t news_item_get_new_history_slot(); +NewsItem& NewsItemQueue::Current() +{ + return Recent[0]; +} + +const NewsItem& NewsItemQueue::Current() const +{ + return Recent[0]; +} + +NewsItem& NewsItemQueue::Oldest() +{ + return Archived[0]; +} + +const NewsItem& NewsItemQueue::Oldest() const +{ + return Archived[0]; +} bool news_item_is_valid_idx(int32_t index) { @@ -52,10 +70,33 @@ bool news_item_is_valid_idx(int32_t index) } NewsItem* news_item_get(int32_t index) +{ + return gNewsItems.At(index); +} + +NewsItem& NewsItemQueue::operator[](size_t index) +{ + return const_cast(const_cast(*this)[index]); +} + +const NewsItem& NewsItemQueue::operator[](size_t index) const +{ + if (index < NEWS_ITEM_HISTORY_START) + return Recent[index]; + else + return Archived[index - NEWS_ITEM_HISTORY_START]; +} + +NewsItem* NewsItemQueue::At(int32_t index) +{ + return const_cast(const_cast(*this).At(index)); +} + +const NewsItem* NewsItemQueue::At(int32_t index) const { if (news_item_is_valid_idx(index)) { - return &gNewsItems[index]; + return &(*this)[index]; } else { @@ -65,23 +106,33 @@ NewsItem* news_item_get(int32_t index) bool news_item_is_empty(int32_t index) { - NewsItem* news = news_item_get(index); - return news != nullptr && news->Type == NEWS_ITEM_NULL; + NewsItem* news = gNewsItems.At(index); + return news != nullptr && news->IsEmpty(); } bool news_item_is_queue_empty() { - return news_item_is_empty(0); + return gNewsItems.IsEmpty(); +} + +bool NewsItemQueue::IsEmpty() const +{ + return Current().IsEmpty(); } /** * * rct2: 0x0066DF32 */ +void NewsItemQueue::Init() +{ + Current().Type = NEWS_ITEM_NULL; + Oldest().Type = NEWS_ITEM_NULL; +} + void news_item_init_queue() { - news_item_get(0)->Type = NEWS_ITEM_NULL; - news_item_get(NEWS_ITEM_HISTORY_START)->Type = NEWS_ITEM_NULL; + gNewsItems.Init(); // Throttles for warning types (PEEP_*_WARNING) for (auto& warningThrottle : gPeepWarningThrottle) @@ -93,10 +144,14 @@ void news_item_init_queue() context_broadcast_intent(&intent); } +uint16_t NewsItemQueue::IncrementTicks() +{ + return ++Current().Ticks; +} + static void news_item_tick_current() { - int32_t ticks; - ticks = ++news_item_get(0)->Ticks; + int32_t ticks = gNewsItems.IncrementTicks(); // Only play news item sound when in normal playing mode if (ticks == 1 && (gScreenFlags == SCREEN_FLAGS_PLAYING)) { @@ -105,15 +160,18 @@ static void news_item_tick_current() } } -static bool news_item_is_current_old() +int32_t NewsItemQueue::RemoveTime() const { - int32_t remove_time = 320; - if (!news_item_is_empty(5) && !news_item_is_empty(4) && !news_item_is_empty(3) && !news_item_is_empty(2)) + if (!Recent[5].IsEmpty() && !Recent[4].IsEmpty() && !Recent[3].IsEmpty() && !Recent[2].IsEmpty()) { - remove_time = 256; + return 256; } + return 320; +} - return news_item_get(0)->Ticks >= remove_time; +bool NewsItemQueue::CurrentShouldBeArchived() const +{ + return Current().Ticks >= RemoveTime(); } /** @@ -123,7 +181,7 @@ static bool news_item_is_current_old() void news_item_update_current() { // Check if there is a current news item - if (news_item_is_queue_empty()) + if (gNewsItems.IsEmpty()) return; auto intent = Intent(INTENT_ACTION_INVALIDATE_TICKER_NEWS); @@ -133,8 +191,8 @@ void news_item_update_current() news_item_tick_current(); // Removal of current news item - if (news_item_is_current_old()) - news_item_close_current(); + if (gNewsItems.CurrentShouldBeArchived()) + gNewsItems.ArchiveCurrent(); } /** @@ -143,62 +201,48 @@ void news_item_update_current() */ void news_item_close_current() { - int32_t i; - NewsItem* newsItems = gNewsItems; + gNewsItems.ArchiveCurrent(); +} +void NewsItemQueue::ArchiveCurrent() +{ // Check if there is a current message - if (news_item_is_queue_empty()) + if (IsEmpty()) return; - // Find an available history news item slot for current message - i = news_item_get_new_history_slot(); - - // Set the history news item slot to the current news item - newsItems[i] = newsItems[0]; - - // Set the end of the end of the history list - if (i < MAX_NEWS_ITEMS - 1) - newsItems[i + 1].Type = NEWS_ITEM_NULL; + AppendToArchive(Current()); // Invalidate the news window window_invalidate_by_class(WC_RECENT_NEWS); // Dequeue the current news item, shift news up - for (i = 0; i < NEWS_ITEM_HISTORY_START - 1; i++) - { - newsItems[i] = newsItems[i + 1]; - } - newsItems[NEWS_ITEM_HISTORY_START - 1].Type = NEWS_ITEM_NULL; + memmove(Recent, Recent + 1, sizeof(NewsItem) * (std::size(Recent) - 1)); + Recent[NEWS_ITEM_HISTORY_START - 1].Type = NEWS_ITEM_NULL; // Invalidate current news item bar auto intent = Intent(INTENT_ACTION_INVALIDATE_TICKER_NEWS); context_broadcast_intent(&intent); } -static void news_item_shift_history_up() -{ - const int32_t history_idx = NEWS_ITEM_HISTORY_START; - NewsItem* history_start = news_item_get(history_idx); - const size_t count = sizeof(NewsItem) * (MAX_NEWS_ITEMS - 1 - history_idx); - memmove(history_start, history_start + 1, count); -} - /** * Finds a spare history slot or replaces an existing one if there are no spare * slots available. */ -static int32_t news_item_get_new_history_slot() +void NewsItemQueue::AppendToArchive(NewsItem& item) { - // Find an available history news item slot - for (int32_t i = NEWS_ITEM_HISTORY_START; i < MAX_NEWS_ITEMS; i++) + auto it = std::find_if(std::begin(Archived), std::end(Archived), [](const auto& newsItem) { return newsItem.IsEmpty(); }); + if (it != std::end(Archived)) { - if (news_item_is_empty(i)) - return i; + *it = item; + ++it; + if (it != std::end(Archived)) + it->Type = NEWS_ITEM_NULL; + return; } // Dequeue the first history news item, shift history up - news_item_shift_history_up(); - return MAX_NEWS_ITEMS - 1; + memmove(Archived, Archived + 1, sizeof(NewsItem) * (std::size(Archived) - 1)); + Archived[MAX_NEWS_ITEMS_ARCHIVE - 1] = item; } /** @@ -295,18 +339,17 @@ std::optional news_item_get_subject_location(int32_t type, int32_t su return subjectLoc; } -static NewsItem* news_item_first_open_queue_slot() +NewsItem* NewsItemQueue::FirstOpenOrNewSlot() { - NewsItem* newsItem = gNewsItems; - - while (newsItem->Type != NEWS_ITEM_NULL) + auto it = std::begin(Recent); + for (; !it->IsEmpty();) { - if (newsItem + 1 >= &gNewsItems[NEWS_ITEM_HISTORY_START - 1]) - news_item_close_current(); + if (it + 2 >= std::end(Recent)) + ArchiveCurrent(); else - newsItem++; + it++; } - return newsItem; + return &*it; } /** @@ -325,7 +368,7 @@ NewsItem* news_item_add_to_queue(uint8_t type, rct_string_id string_id, uint32_t NewsItem* news_item_add_to_queue_raw(uint8_t type, const utf8* text, uint32_t assoc) { - NewsItem* newsItem = news_item_first_open_queue_slot(); + NewsItem* newsItem = gNewsItems.FirstOpenOrNewSlot(); newsItem->Type = type; newsItem->Flags = 0; newsItem->Assoc = assoc; @@ -438,48 +481,30 @@ void news_item_open_subject(int32_t type, int32_t subject) void news_item_disable_news(uint8_t type, uint32_t assoc) { // TODO: write test invalidating windows - for (int32_t i = 0; i < NEWS_ITEM_HISTORY_START; i++) - { - if (!news_item_is_empty(i)) + gNewsItems.ForeachRecentNews([type, assoc](auto& newsItem) { + if (type == newsItem.Type && assoc == newsItem.Assoc) { - NewsItem* const newsItem = news_item_get(i); - if (type == newsItem->Type && assoc == newsItem->Assoc) + newsItem.Flags |= NEWS_FLAG_HAS_BUTTON; + if (&newsItem == &gNewsItems.Current()) { - newsItem->Flags |= NEWS_FLAG_HAS_BUTTON; - if (i == 0) - { - auto intent = Intent(INTENT_ACTION_INVALIDATE_TICKER_NEWS); - context_broadcast_intent(&intent); - } + auto intent = Intent(INTENT_ACTION_INVALIDATE_TICKER_NEWS); + context_broadcast_intent(&intent); } } - else - { - break; - } - } + }); - for (int32_t i = NEWS_ITEM_HISTORY_START; i < MAX_NEWS_ITEMS; i++) - { - if (!news_item_is_empty(i)) + gNewsItems.ForeachArchivedNews([type, assoc](auto& newsItem) { + if (type == newsItem.Type && assoc == newsItem.Assoc) { - NewsItem* const newsItem = news_item_get(i); - if (type == newsItem->Type && assoc == newsItem->Assoc) - { - newsItem->Flags |= NEWS_FLAG_HAS_BUTTON; - window_invalidate_by_class(WC_RECENT_NEWS); - } + newsItem.Flags |= NEWS_FLAG_HAS_BUTTON; + window_invalidate_by_class(WC_RECENT_NEWS); } - else - { - break; - } - } + }); } void news_item_add_to_queue_custom(NewsItem* newNewsItem) { - NewsItem* newsItem = news_item_first_open_queue_slot(); + NewsItem* newsItem = gNewsItems.FirstOpenOrNewSlot(); *newsItem = *newNewsItem; newsItem++; newsItem->Type = NEWS_ITEM_NULL; diff --git a/src/openrct2/management/NewsItem.h b/src/openrct2/management/NewsItem.h index c1acfa40d2..3623bac716 100644 --- a/src/openrct2/management/NewsItem.h +++ b/src/openrct2/management/NewsItem.h @@ -52,14 +52,65 @@ struct NewsItem uint16_t MonthYear; uint8_t Day; utf8 Text[256]; + + constexpr bool IsEmpty() const noexcept + { + return Type == NEWS_ITEM_NULL; + } }; constexpr int32_t NEWS_ITEM_HISTORY_START = 11; -constexpr int32_t MAX_NEWS_ITEMS = 61; +constexpr int32_t MAX_NEWS_ITEMS_ARCHIVE = 50; +constexpr int32_t MAX_NEWS_ITEMS = NEWS_ITEM_HISTORY_START + MAX_NEWS_ITEMS_ARCHIVE; extern const uint8_t news_type_properties[10]; -extern NewsItem gNewsItems[MAX_NEWS_ITEMS]; +struct NewsItemQueue +{ + NewsItem& operator[](size_t index); + const NewsItem& operator[](size_t index) const; + NewsItem* At(int32_t index); + const NewsItem* At(int32_t index) const; + bool IsEmpty() const; + void Init(); + uint16_t IncrementTicks(); + NewsItem& Current(); + const NewsItem& Current() const; + NewsItem& Oldest(); + const NewsItem& Oldest() const; + bool CurrentShouldBeArchived() const; + void ArchiveCurrent(); + NewsItem* FirstOpenOrNewSlot(); + + template void ForeachRecentNews(Predicate&& p) + { + for (auto& newsItem : Recent) + { + if (newsItem.IsEmpty()) + break; + p(newsItem); + } + } + + template void ForeachArchivedNews(Predicate&& p) + { + for (auto& newsItem : Archived) + { + if (newsItem.IsEmpty()) + break; + p(newsItem); + } + } + +private: + int32_t RemoveTime() const; + void AppendToArchive(NewsItem& item); + + NewsItem Recent[NEWS_ITEM_HISTORY_START]; + NewsItem Archived[MAX_NEWS_ITEMS_ARCHIVE]; +}; + +extern NewsItemQueue gNewsItems; void news_item_init_queue();