diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 5f2561f04c..7f84063323 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -896,7 +896,34 @@ declare global { "attraction" | "peep_on_attraction" | "peep" | "money" | "blank" | "research" | "guests" | "award" | "chart"; interface ParkMessage { + /** + * Whether the message has been shown and archived. + */ + readonly isArchived: boolean; + + /** + * The date this message was posted in total elapsed months. + */ + month: number; + + /** + * The day of the month this message was posted. + */ + day: number; + + /** + * How old the message is in number of ticks. + */ + tickCount: number; + + /** + * The format of the message such as the icon and whether location is enabled. + */ type: ParkMessageType; + + /** + * The actual message content. + */ text: string; /** @@ -905,6 +932,17 @@ declare global { * Researched item for research. */ subject?: number; + + /** + * Removes the message. + */ + remove(): void; + } + + interface ParkMessageDesc { + type: ParkMessageType; + text: string; + subject?: number; } interface Park { @@ -912,9 +950,10 @@ declare global { rating: number; bankLoan: number; maxBankLoan: number; + messages: ParkMessage[]; postMessage(message: string): void; - postMessage(message: ParkMessage): void; + postMessage(message: ParkMessageDesc): void; } interface Cheats { diff --git a/src/openrct2/management/NewsItem.cpp b/src/openrct2/management/NewsItem.cpp index 7d4b5ed1ba..17ad8d09e6 100644 --- a/src/openrct2/management/NewsItem.cpp +++ b/src/openrct2/management/NewsItem.cpp @@ -81,7 +81,7 @@ bool news_item_is_queue_empty() void news_item_init_queue() { news_item_get(0)->Type = NEWS_ITEM_NULL; - news_item_get(11)->Type = NEWS_ITEM_NULL; + news_item_get(NEWS_ITEM_HISTORY_START)->Type = NEWS_ITEM_NULL; // Throttles for warning types (PEEP_*_WARNING) for (auto& warningThrottle : gPeepWarningThrottle) @@ -164,11 +164,11 @@ void news_item_close_current() window_invalidate_by_class(WC_RECENT_NEWS); // Dequeue the current news item, shift news up - for (i = 0; i < 10; i++) + for (i = 0; i < NEWS_ITEM_HISTORY_START - 1; i++) { newsItems[i] = newsItems[i + 1]; } - newsItems[10].Type = NEWS_ITEM_NULL; + newsItems[NEWS_ITEM_HISTORY_START - 1].Type = NEWS_ITEM_NULL; // Invalidate current news item bar auto intent = Intent(INTENT_ACTION_INVALIDATE_TICKER_NEWS); @@ -177,7 +177,7 @@ void news_item_close_current() static void news_item_shift_history_up() { - const int32_t history_idx = 11; + 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); @@ -190,7 +190,7 @@ static void news_item_shift_history_up() static int32_t news_item_get_new_history_slot() { // Find an available history news item slot - for (int32_t i = 11; i < MAX_NEWS_ITEMS; i++) + for (int32_t i = NEWS_ITEM_HISTORY_START; i < MAX_NEWS_ITEMS; i++) { if (news_item_is_empty(i)) return i; @@ -301,7 +301,7 @@ static NewsItem* news_item_first_open_queue_slot() while (newsItem->Type != NEWS_ITEM_NULL) { - if (newsItem + 1 >= &gNewsItems[10]) + if (newsItem + 1 >= &gNewsItems[NEWS_ITEM_HISTORY_START - 1]) news_item_close_current(); else newsItem++; @@ -438,7 +438,7 @@ 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 < 11; i++) + for (int32_t i = 0; i < NEWS_ITEM_HISTORY_START; i++) { if (!news_item_is_empty(i)) { @@ -459,7 +459,7 @@ void news_item_disable_news(uint8_t type, uint32_t assoc) } } - for (int32_t i = 11; i < MAX_NEWS_ITEMS; i++) + for (int32_t i = NEWS_ITEM_HISTORY_START; i < MAX_NEWS_ITEMS; i++) { if (!news_item_is_empty(i)) { @@ -484,3 +484,20 @@ void news_item_add_to_queue_custom(NewsItem* newNewsItem) newsItem++; newsItem->Type = NEWS_ITEM_NULL; } + +void news_item_remove(int32_t index) +{ + if (index < 0 || index >= MAX_NEWS_ITEMS) + return; + + // News item is already null, no need to remove it + if (gNewsItems[index].Type == NEWS_ITEM_NULL) + return; + + size_t newsBoundary = index < NEWS_ITEM_HISTORY_START ? NEWS_ITEM_HISTORY_START : MAX_NEWS_ITEMS; + for (size_t i = index; i < newsBoundary - 1; i++) + { + gNewsItems[i] = gNewsItems[i + 1]; + } + gNewsItems[newsBoundary - 1].Type = NEWS_ITEM_NULL; +} diff --git a/src/openrct2/management/NewsItem.h b/src/openrct2/management/NewsItem.h index 695d69631c..c1acfa40d2 100644 --- a/src/openrct2/management/NewsItem.h +++ b/src/openrct2/management/NewsItem.h @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) 2014-2019 OpenRCT2 developers + * Copyright (c) 2014-2020 OpenRCT2 developers * * For a complete list of all authors, please refer to contributors.md * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 @@ -54,7 +54,8 @@ struct NewsItem utf8 Text[256]; }; -#define MAX_NEWS_ITEMS 61 +constexpr int32_t NEWS_ITEM_HISTORY_START = 11; +constexpr int32_t MAX_NEWS_ITEMS = 61; extern const uint8_t news_type_properties[10]; @@ -82,3 +83,4 @@ bool news_item_is_queue_empty(); bool news_item_is_valid_idx(int32_t index); void news_item_add_to_queue_custom(NewsItem* newNewsItem); +void news_item_remove(int32_t index); diff --git a/src/openrct2/scripting/ScPark.hpp b/src/openrct2/scripting/ScPark.hpp index d9eb4548e9..12988b3e42 100644 --- a/src/openrct2/scripting/ScPark.hpp +++ b/src/openrct2/scripting/ScPark.hpp @@ -13,6 +13,7 @@ # include "../Context.h" # include "../common.h" +# include "../core/String.hpp" # include "../management/Finance.h" # include "../management/NewsItem.h" # include "../windows/Intent.h" @@ -24,6 +25,204 @@ namespace OpenRCT2::Scripting { + static constexpr const char* ParkMessageTypeStrings[] = { + "attraction", "peep_on_attraction", "peep", "money", "blank", "research", "guests", "award", "chart", + }; + + inline uint8_t GetParkMessageType(const std::string& key) + { + auto it = std::find(std::begin(ParkMessageTypeStrings), std::end(ParkMessageTypeStrings), key); + return it != std::end(ParkMessageTypeStrings) + ? static_cast(NEWS_ITEM_RIDE + std::distance(std::begin(ParkMessageTypeStrings), it)) + : static_cast(NEWS_ITEM_BLANK); + } + + inline std::string GetParkMessageType(uint8_t type) + { + // Decrement 1 as ParkMessageTypeStrings doesn't contain the null type + type--; + if (type < std::size(ParkMessageTypeStrings)) + { + return ParkMessageTypeStrings[type]; + } + return {}; + } + + template<> inline NewsItem FromDuk(const DukValue& value) + { + NewsItem result{}; + result.Type = GetParkMessageType(value["type"].as_string()); + result.Assoc = value["subject"].as_int(); + result.Ticks = value["tickCount"].as_int(); + result.MonthYear = value["month"].as_int(); + result.Day = value["day"].as_int(); + + auto text = language_convert_string(value["text"].as_string()); + String::Set(result.Text, sizeof(result.Text), text.c_str()); + return result; + } + + class ScParkMessage + { + private: + size_t _index{}; + + public: + ScParkMessage(size_t index) + : _index(index) + { + } + + static void Register(duk_context* ctx) + { + dukglue_register_property(ctx, &ScParkMessage::isArchived_get, nullptr, "isArchived"); + dukglue_register_property(ctx, &ScParkMessage::month_get, &ScParkMessage::month_set, "month"); + dukglue_register_property(ctx, &ScParkMessage::day_get, &ScParkMessage::day_set, "day"); + dukglue_register_property(ctx, &ScParkMessage::tickCount_get, &ScParkMessage::tickCount_set, "tickCount"); + dukglue_register_property(ctx, &ScParkMessage::type_get, &ScParkMessage::type_set, "type"); + dukglue_register_property(ctx, &ScParkMessage::subject_get, &ScParkMessage::subject_set, "subject"); + dukglue_register_property(ctx, &ScParkMessage::text_get, &ScParkMessage::text_set, "text"); + dukglue_register_method(ctx, &ScParkMessage::remove, "remove"); + } + + private: + NewsItem* GetMessage() const + { + return &gNewsItems[_index]; + } + + bool isArchived_get() const + { + return _index >= NEWS_ITEM_HISTORY_START; + } + + uint16_t month_get() const + { + auto msg = GetMessage(); + if (msg != nullptr) + { + return msg->MonthYear; + } + return 0; + } + + void month_set(uint16_t value) + { + ThrowIfGameStateNotMutable(); + auto msg = GetMessage(); + if (msg != nullptr) + { + msg->MonthYear = value; + } + } + + uint8_t day_get() const + { + auto msg = GetMessage(); + if (msg != nullptr) + { + return msg->Day; + } + return 0; + } + + void day_set(uint8_t value) + { + ThrowIfGameStateNotMutable(); + auto msg = GetMessage(); + if (msg != nullptr) + { + msg->Day = value; + } + } + + uint16_t tickCount_get() const + { + auto msg = GetMessage(); + if (msg != nullptr) + { + return msg->Ticks; + } + return 0; + } + + void tickCount_set(uint16_t value) + { + ThrowIfGameStateNotMutable(); + auto msg = GetMessage(); + if (msg != nullptr) + { + msg->Ticks = value; + } + } + + std::string type_get() const + { + auto msg = GetMessage(); + if (msg != nullptr) + { + return GetParkMessageType(msg->Type); + } + return {}; + } + + void type_set(const std::string& value) + { + ThrowIfGameStateNotMutable(); + auto msg = GetMessage(); + if (msg != nullptr) + { + msg->Type = GetParkMessageType(value); + } + } + + uint32_t subject_get() const + { + auto msg = GetMessage(); + if (msg != nullptr) + { + return msg->Assoc; + } + return 0; + } + + void subject_set(uint32_t value) + { + ThrowIfGameStateNotMutable(); + auto msg = GetMessage(); + if (msg != nullptr) + { + msg->Assoc = value; + } + } + + std::string text_get() const + { + auto msg = GetMessage(); + if (msg != nullptr) + { + return language_convert_string_to_tokens(msg->Text); + } + return 0; + } + + void text_set(const std::string& value) + { + ThrowIfGameStateNotMutable(); + auto msg = GetMessage(); + if (msg != nullptr) + { + auto text = language_convert_string(value); + String::Set(msg->Text, sizeof(msg->Text), text.c_str()); + } + } + + void remove() + { + news_item_remove(static_cast(_index)); + } + }; + class ScPark { public: @@ -75,6 +274,61 @@ namespace OpenRCT2::Scripting context_broadcast_intent(&intent); } + std::vector> messages_get() const + { + std::vector> result; + for (int32_t i = 0; i < NEWS_ITEM_HISTORY_START; i++) + { + if (news_item_is_empty(i)) + break; + result.push_back(std::make_shared(i)); + } + for (int32_t i = NEWS_ITEM_HISTORY_START; i < MAX_NEWS_ITEMS; i++) + { + if (news_item_is_empty(i)) + break; + result.push_back(std::make_shared(i)); + } + return result; + } + + void messages_set(const std::vector& value) + { + int32_t index = 0; + int32_t archiveIndex = NEWS_ITEM_HISTORY_START; + for (const auto& item : value) + { + auto isArchived = item["isArchived"].as_bool(); + auto newsItem = FromDuk(item); + if (isArchived) + { + if (archiveIndex < MAX_NEWS_ITEMS) + { + gNewsItems[archiveIndex] = newsItem; + archiveIndex++; + } + } + else + { + if (index < NEWS_ITEM_HISTORY_START) + { + gNewsItems[index] = newsItem; + index++; + } + } + } + + // End the lists by setting next item to null + if (index < NEWS_ITEM_HISTORY_START) + { + gNewsItems[index].Type = NEWS_ITEM_NULL; + } + if (archiveIndex < MAX_NEWS_ITEMS) + { + gNewsItems[archiveIndex].Type = NEWS_ITEM_NULL; + } + } + void postMessage(DukValue message) { ThrowIfGameStateNotMutable(); @@ -116,25 +370,9 @@ namespace OpenRCT2::Scripting dukglue_register_property(ctx, &ScPark::rating_get, &ScPark::rating_set, "rating"); dukglue_register_property(ctx, &ScPark::bankLoan_get, &ScPark::bankLoan_set, "bankLoan"); dukglue_register_property(ctx, &ScPark::maxBankLoan_get, &ScPark::maxBankLoan_set, "maxBankLoan"); + dukglue_register_property(ctx, &ScPark::messages_get, &ScPark::messages_set, "messages"); dukglue_register_method(ctx, &ScPark::postMessage, "postMessage"); } - - private: - static uint8_t GetParkMessageType(const std::string& key) - { - static auto keys = { "attraction", "peep_on_attraction", "peep", "money", "blank", "research", "guests", "award", - "chart" }; - uint8_t i = 0; - for (const auto& k : keys) - { - if (k == key) - { - return NEWS_ITEM_RIDE + i; - } - i++; - } - return NEWS_ITEM_BLANK; - } }; } // namespace OpenRCT2::Scripting diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp index cecf644e19..b09790e878 100644 --- a/src/openrct2/scripting/ScriptEngine.cpp +++ b/src/openrct2/scripting/ScriptEngine.cpp @@ -380,6 +380,7 @@ void ScriptEngine::Initialise() ScObject::Register(ctx); ScSmallSceneryObject::Register(ctx); ScPark::Register(ctx); + ScParkMessage::Register(ctx); ScPlayer::Register(ctx); ScPlayerGroup::Register(ctx); ScRide::Register(ctx);