Add plugin API for editing park messages (#11755)

This commit is contained in:
Ted John 2020-05-19 02:59:35 +01:00 committed by GitHub
parent 310ff84b23
commit 03b1974912
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 325 additions and 28 deletions

View file

@ -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 {

View file

@ -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;
}

View file

@ -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);

View file

@ -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<uint8_t>(NEWS_ITEM_RIDE + std::distance(std::begin(ParkMessageTypeStrings), it))
: static_cast<uint8_t>(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<int32_t>(_index));
}
};
class ScPark
{
public:
@ -75,6 +274,61 @@ namespace OpenRCT2::Scripting
context_broadcast_intent(&intent);
}
std::vector<std::shared_ptr<ScParkMessage>> messages_get() const
{
std::vector<std::shared_ptr<ScParkMessage>> 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<ScParkMessage>(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<ScParkMessage>(i));
}
return result;
}
void messages_set(const std::vector<DukValue>& 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<NewsItem>(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

View file

@ -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);