diff --git a/distribution/changelog.txt b/distribution/changelog.txt
index 527a6ca0fb..4b3f544237 100644
--- a/distribution/changelog.txt
+++ b/distribution/changelog.txt
@@ -1,5 +1,6 @@
0.4.25 (in development)
------------------------------------------------------------------------
+- Feature: [#24468] [Plugin] Add awards to plugin API.
- Feature: [#24702] [Plugin] Add bindings for missing cheats (forcedParkRating, ignoreRidePrice, makeAllDestructible).
- Fix: [#24598] Cannot load .park files that use official legacy footpaths by accident.
diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts
index 9c828690ac..892b4cce63 100644
--- a/distribution/openrct2.d.ts
+++ b/distribution/openrct2.d.ts
@@ -4179,6 +4179,53 @@ declare global {
"scenarioCompleteNameInput" |
"unlockAllPrices";
+ type AwardType =
+ "mostUntidy" |
+ "mostTidy" |
+ "bestRollerCoasters" |
+ "bestValue" |
+ "mostBeautiful" |
+ "worstValue" |
+ "safest" |
+ "bestStaff" |
+ "bestFood" |
+ "worstFood" |
+ "bestToilets" |
+ "mostDisappointing" |
+ "bestWaterRides" |
+ "bestCustomDesignedRides" |
+ "mostDazzlingRideColours" |
+ "mostConfusingLayout" |
+ "bestGentleRides";
+
+ interface Award {
+ /**
+ * The type of the award.
+ */
+ readonly type: AwardType;
+
+ /**
+ * The award description.
+ */
+ readonly text: string;
+
+ /**
+ * Number of months this award will remain active.
+ * Starts at 5, expires at 0.
+ */
+ readonly monthsRemaining: number;
+
+ /**
+ * The sprite of the award.
+ */
+ readonly imageId: number;
+
+ /**
+ * Whether this is a positive or negative award.
+ */
+ readonly positive: boolean;
+ }
+
interface Park {
cash: number;
rating: number;
@@ -4326,6 +4373,25 @@ declare global {
* @param type The type of expenditure to get.
*/
getMonthlyExpenditure(type: ExpenditureType): number[]
+
+ /**
+ * The current awards of the park.
+ */
+ readonly awards: Award[]
+
+ /**
+ * Clear all awards.
+ */
+ clearAwards(): void
+
+ /**
+ * Grant the given award type to the park.
+ * Does not check eligibility.
+ * If the park already has an active award of the given type, the old award will be removed.
+ * If the park already has 4 active awards, the oldest award will be removed.
+ * @param type the award type to grant
+ */
+ grantAward(type: AwardType): void
}
interface Research {
diff --git a/src/openrct2-ui/UiStringIds.h b/src/openrct2-ui/UiStringIds.h
index b890df371f..290734a5ae 100644
--- a/src/openrct2-ui/UiStringIds.h
+++ b/src/openrct2-ui/UiStringIds.h
@@ -1290,23 +1290,6 @@ namespace OpenRCT2
// Window: Park
STR_ADMISSION_PRICE = 1756,
STR_ADMISSION_PRICE_PAY_PER_RIDE_TIP = 6014,
- STR_AWARD_MOST_UNTIDY = 2814,
- STR_AWARD_BEST_CUSTOM_DESIGNED_RIDES = STR_AWARD_MOST_UNTIDY + 13,
- STR_AWARD_BEST_FOOD = STR_AWARD_MOST_UNTIDY + 8,
- STR_AWARD_BEST_GENTLE_RIDES = STR_AWARD_MOST_UNTIDY + 16,
- STR_AWARD_BEST_ROLLERCOASTERS = STR_AWARD_MOST_UNTIDY + 2,
- STR_AWARD_BEST_STAFF = STR_AWARD_MOST_UNTIDY + 7,
- STR_AWARD_BEST_TOILETS = STR_AWARD_MOST_UNTIDY + 10,
- STR_AWARD_BEST_VALUE = STR_AWARD_MOST_UNTIDY + 3,
- STR_AWARD_BEST_WATER_RIDES = STR_AWARD_MOST_UNTIDY + 12,
- STR_AWARD_MOST_BEAUTIFUL = STR_AWARD_MOST_UNTIDY + 4,
- STR_AWARD_MOST_CONFUSING_LAYOUT = STR_AWARD_MOST_UNTIDY + 15,
- STR_AWARD_MOST_DAZZLING_RIDE_COLOURS = STR_AWARD_MOST_UNTIDY + 14,
- STR_AWARD_MOST_DISAPPOINTING = STR_AWARD_MOST_UNTIDY + 11,
- STR_AWARD_MOST_TIDY = STR_AWARD_MOST_UNTIDY + 1,
- STR_AWARD_SAFEST = STR_AWARD_MOST_UNTIDY + 6,
- STR_AWARD_WORST_FOOD = STR_AWARD_MOST_UNTIDY + 9,
- STR_AWARD_WORST_VALUE = STR_AWARD_MOST_UNTIDY + 5,
STR_BUY_LAND_AND_CONSTRUCTION_RIGHTS_TIP = 5135,
STR_CLOSE_PARK = 1013,
STR_CLOSE_PARK_TIP = 5296,
diff --git a/src/openrct2-ui/windows/Park.cpp b/src/openrct2-ui/windows/Park.cpp
index 9b8e81d7ec..233d86cbdf 100644
--- a/src/openrct2-ui/windows/Park.cpp
+++ b/src/openrct2-ui/windows/Park.cpp
@@ -164,31 +164,6 @@ namespace OpenRCT2::Ui::Windows
0,
0,
};
-
- struct WindowParkAward {
- StringId text;
- uint32_t sprite;
- };
-
- static constexpr WindowParkAward _parkAwards[] = {
- { STR_AWARD_MOST_UNTIDY, SPR_AWARD_MOST_UNTIDY },
- { STR_AWARD_MOST_TIDY, SPR_AWARD_MOST_TIDY },
- { STR_AWARD_BEST_ROLLERCOASTERS, SPR_AWARD_BEST_ROLLERCOASTERS },
- { STR_AWARD_BEST_VALUE, SPR_AWARD_BEST_VALUE },
- { STR_AWARD_MOST_BEAUTIFUL, SPR_AWARD_MOST_BEAUTIFUL },
- { STR_AWARD_WORST_VALUE, SPR_AWARD_WORST_VALUE },
- { STR_AWARD_SAFEST, SPR_AWARD_SAFEST },
- { STR_AWARD_BEST_STAFF, SPR_AWARD_BEST_STAFF },
- { STR_AWARD_BEST_FOOD, SPR_AWARD_BEST_FOOD },
- { STR_AWARD_WORST_FOOD, SPR_AWARD_WORST_FOOD },
- { STR_AWARD_BEST_TOILETS, SPR_AWARD_BEST_TOILETS },
- { STR_AWARD_MOST_DISAPPOINTING, SPR_AWARD_MOST_DISAPPOINTING },
- { STR_AWARD_BEST_WATER_RIDES, SPR_AWARD_BEST_WATER_RIDES },
- { STR_AWARD_BEST_CUSTOM_DESIGNED_RIDES, SPR_AWARD_BEST_CUSTOM_DESIGNED_RIDES },
- { STR_AWARD_MOST_DAZZLING_RIDE_COLOURS, SPR_AWARD_MOST_DAZZLING_RIDE_COLOURS },
- { STR_AWARD_MOST_CONFUSING_LAYOUT, SPR_AWARD_MOST_CONFUSING_LAYOUT },
- { STR_AWARD_BEST_GENTLE_RIDES, SPR_AWARD_BEST_GENTLE_RIDES },
- };
// clang-format on
class ParkWindow final : public Window
@@ -1164,8 +1139,8 @@ namespace OpenRCT2::Ui::Windows
for (const auto& award : currentAwards)
{
- GfxDrawSprite(rt, ImageId(_parkAwards[EnumValue(award.Type)].sprite), screenCoords);
- DrawTextWrapped(rt, screenCoords + ScreenCoordsXY{ 34, 6 }, 180, _parkAwards[EnumValue(award.Type)].text);
+ GfxDrawSprite(rt, ImageId(AwardGetSprite(award.Type)), screenCoords);
+ DrawTextWrapped(rt, screenCoords + ScreenCoordsXY{ 34, 6 }, 180, AwardGetText(award.Type));
screenCoords.y += 32;
}
diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj
index 51117b3e26..5467efa682 100644
--- a/src/openrct2/libopenrct2.vcxproj
+++ b/src/openrct2/libopenrct2.vcxproj
@@ -595,6 +595,7 @@
+
@@ -1117,6 +1118,7 @@
+
diff --git a/src/openrct2/localisation/StringIds.h b/src/openrct2/localisation/StringIds.h
index ae9a3dd9d2..f8868b66ee 100644
--- a/src/openrct2/localisation/StringIds.h
+++ b/src/openrct2/localisation/StringIds.h
@@ -1096,6 +1096,23 @@ enum : StringId
STR_PEEPS_CANT_FIND_TOILET = 2811,
STR_PEEPS_GETTING_LOST_OR_STUCK = 2812,
STR_ENTRANCE_FEE_TOO_HI = 2813,
+ STR_AWARD_MOST_UNTIDY = 2814,
+ STR_AWARD_BEST_CUSTOM_DESIGNED_RIDES = STR_AWARD_MOST_UNTIDY + 13,
+ STR_AWARD_BEST_FOOD = STR_AWARD_MOST_UNTIDY + 8,
+ STR_AWARD_BEST_GENTLE_RIDES = STR_AWARD_MOST_UNTIDY + 16,
+ STR_AWARD_BEST_ROLLERCOASTERS = STR_AWARD_MOST_UNTIDY + 2,
+ STR_AWARD_BEST_STAFF = STR_AWARD_MOST_UNTIDY + 7,
+ STR_AWARD_BEST_TOILETS = STR_AWARD_MOST_UNTIDY + 10,
+ STR_AWARD_BEST_VALUE = STR_AWARD_MOST_UNTIDY + 3,
+ STR_AWARD_BEST_WATER_RIDES = STR_AWARD_MOST_UNTIDY + 12,
+ STR_AWARD_MOST_BEAUTIFUL = STR_AWARD_MOST_UNTIDY + 4,
+ STR_AWARD_MOST_CONFUSING_LAYOUT = STR_AWARD_MOST_UNTIDY + 15,
+ STR_AWARD_MOST_DAZZLING_RIDE_COLOURS = STR_AWARD_MOST_UNTIDY + 14,
+ STR_AWARD_MOST_DISAPPOINTING = STR_AWARD_MOST_UNTIDY + 11,
+ STR_AWARD_MOST_TIDY = STR_AWARD_MOST_UNTIDY + 1,
+ STR_AWARD_SAFEST = STR_AWARD_MOST_UNTIDY + 6,
+ STR_AWARD_WORST_FOOD = STR_AWARD_MOST_UNTIDY + 9,
+ STR_AWARD_WORST_VALUE = STR_AWARD_MOST_UNTIDY + 5,
STR_NEWS_ITEM_AWARD_MOST_UNTIDY = 2831,
STR_NEWS_ITEM_MOST_TIDY = STR_NEWS_ITEM_AWARD_MOST_UNTIDY + 1,
STR_NEWS_ITEM_BEST_ROLLERCOASTERS = STR_NEWS_ITEM_AWARD_MOST_UNTIDY + 2,
diff --git a/src/openrct2/management/Award.cpp b/src/openrct2/management/Award.cpp
index 8fcc967abd..f08ada6a8d 100644
--- a/src/openrct2/management/Award.cpp
+++ b/src/openrct2/management/Award.cpp
@@ -13,7 +13,6 @@
#include "../config/Config.h"
#include "../entity/EntityList.h"
#include "../entity/Guest.h"
-#include "../localisation/StringIds.h"
#include "../profiling/Profiling.h"
#include "../ride/Ride.h"
#include "../ride/RideData.h"
@@ -29,49 +28,55 @@ enum class AwardEffect : uint8_t
negative,
positive
};
-static constexpr AwardEffect kAwardPositiveMap[] = {
- AwardEffect::negative, // AwardType::MostUntidy
- AwardEffect::positive, // AwardType::MostTidy
- AwardEffect::positive, // AwardType::BestRollerCoasters
- AwardEffect::positive, // AwardType::BestValue
- AwardEffect::positive, // AwardType::MostBeautiful
- AwardEffect::negative, // AwardType::WorstValue
- AwardEffect::positive, // AwardType::Safest
- AwardEffect::positive, // AwardType::BestStaff
- AwardEffect::positive, // AwardType::BestFood
- AwardEffect::negative, // AwardType::WorstFood
- AwardEffect::positive, // AwardType::BestToilets
- AwardEffect::negative, // AwardType::MostDisappointing
- AwardEffect::positive, // AwardType::BestWaterRides
- AwardEffect::positive, // AwardType::BestCustomDesignedRides
- AwardEffect::positive, // AwardType::MostDazzlingRideColours
- AwardEffect::negative, // AwardType::MostConfusingLayout
- AwardEffect::positive, // AwardType::BestGentleRides
+
+struct AwardData_t
+{
+ StringId text;
+ StringId news;
+ ImageIndex sprite;
+ AwardEffect effect;
};
-static constexpr StringId AwardNewsStrings[] = {
- STR_NEWS_ITEM_AWARD_MOST_UNTIDY,
- STR_NEWS_ITEM_MOST_TIDY,
- STR_NEWS_ITEM_BEST_ROLLERCOASTERS,
- STR_NEWS_ITEM_BEST_VALUE,
- STR_NEWS_ITEM_MOST_BEAUTIFUL,
- STR_NEWS_ITEM_WORST_VALUE,
- STR_NEWS_ITEM_SAFEST,
- STR_NEWS_ITEM_BEST_STAFF,
- STR_NEWS_ITEM_BEST_FOOD,
- STR_NEWS_ITEM_WORST_FOOD,
- STR_NEWS_ITEM_BEST_TOILETS,
- STR_NEWS_ITEM_MOST_DISAPPOINTING,
- STR_NEWS_ITEM_BEST_WATER_RIDES,
- STR_NEWS_ITEM_BEST_CUSTOM_DESIGNED_RIDES,
- STR_NEWS_ITEM_MOST_DAZZLING_RIDE_COLOURS,
- STR_NEWS_ITEM_MOST_CONFUSING_LAYOUT,
- STR_NEWS_ITEM_BEST_GENTLE_RIDES,
+// clang-format off
+static constexpr AwardData_t AwardData[] = {
+ { STR_AWARD_MOST_UNTIDY, STR_NEWS_ITEM_AWARD_MOST_UNTIDY, SPR_AWARD_MOST_UNTIDY, AwardEffect::negative },
+ { STR_AWARD_MOST_TIDY, STR_NEWS_ITEM_MOST_TIDY, SPR_AWARD_MOST_TIDY, AwardEffect::positive },
+ { STR_AWARD_BEST_ROLLERCOASTERS, STR_NEWS_ITEM_BEST_ROLLERCOASTERS, SPR_AWARD_BEST_ROLLERCOASTERS, AwardEffect::positive },
+ { STR_AWARD_BEST_VALUE, STR_NEWS_ITEM_BEST_VALUE, SPR_AWARD_BEST_VALUE, AwardEffect::positive },
+ { STR_AWARD_MOST_BEAUTIFUL, STR_NEWS_ITEM_MOST_BEAUTIFUL, SPR_AWARD_MOST_BEAUTIFUL, AwardEffect::positive },
+ { STR_AWARD_WORST_VALUE, STR_NEWS_ITEM_WORST_VALUE, SPR_AWARD_WORST_VALUE, AwardEffect::negative },
+ { STR_AWARD_SAFEST, STR_NEWS_ITEM_SAFEST, SPR_AWARD_SAFEST, AwardEffect::positive },
+ { STR_AWARD_BEST_STAFF, STR_NEWS_ITEM_BEST_STAFF, SPR_AWARD_BEST_STAFF, AwardEffect::positive },
+ { STR_AWARD_BEST_FOOD, STR_NEWS_ITEM_BEST_FOOD, SPR_AWARD_BEST_FOOD, AwardEffect::positive },
+ { STR_AWARD_WORST_FOOD, STR_NEWS_ITEM_WORST_FOOD, SPR_AWARD_WORST_FOOD, AwardEffect::negative },
+ { STR_AWARD_BEST_TOILETS, STR_NEWS_ITEM_BEST_TOILETS, SPR_AWARD_BEST_TOILETS, AwardEffect::positive },
+ { STR_AWARD_MOST_DISAPPOINTING, STR_NEWS_ITEM_MOST_DISAPPOINTING, SPR_AWARD_MOST_DISAPPOINTING, AwardEffect::negative },
+ { STR_AWARD_BEST_WATER_RIDES, STR_NEWS_ITEM_BEST_WATER_RIDES, SPR_AWARD_BEST_WATER_RIDES, AwardEffect::positive },
+ { STR_AWARD_BEST_CUSTOM_DESIGNED_RIDES, STR_NEWS_ITEM_BEST_CUSTOM_DESIGNED_RIDES, SPR_AWARD_BEST_CUSTOM_DESIGNED_RIDES, AwardEffect::positive },
+ { STR_AWARD_MOST_DAZZLING_RIDE_COLOURS, STR_NEWS_ITEM_MOST_DAZZLING_RIDE_COLOURS, SPR_AWARD_MOST_DAZZLING_RIDE_COLOURS, AwardEffect::positive },
+ { STR_AWARD_MOST_CONFUSING_LAYOUT, STR_NEWS_ITEM_MOST_CONFUSING_LAYOUT, SPR_AWARD_MOST_CONFUSING_LAYOUT, AwardEffect::negative },
+ { STR_AWARD_BEST_GENTLE_RIDES, STR_NEWS_ITEM_BEST_GENTLE_RIDES, SPR_AWARD_BEST_GENTLE_RIDES, AwardEffect::positive },
};
+// clang-format on
bool AwardIsPositive(AwardType type)
{
- return kAwardPositiveMap[EnumValue(type)] == AwardEffect::positive;
+ return AwardData[EnumValue(type)].effect == AwardEffect::positive;
+}
+
+ImageIndex AwardGetSprite(AwardType type)
+{
+ return AwardData[EnumValue(type)].sprite;
+}
+
+StringId AwardGetText(AwardType type)
+{
+ return AwardData[EnumValue(type)].text;
+}
+
+StringId AwardGetNews(AwardType type)
+{
+ return AwardData[EnumValue(type)].news;
}
#pragma region Award checks
@@ -598,6 +603,16 @@ void AwardReset()
getGameState().currentAwards.clear();
}
+static void AwardAdd(AwardType type)
+{
+ getGameState().currentAwards.push_back(Award{ 5u, type });
+ if (Config::Get().notifications.ParkAward)
+ {
+ News::AddItemToQueue(News::ItemType::award, AwardGetNews(type), 0, {});
+ }
+ Ui::GetWindowManager()->InvalidateByClass(WindowClass::ParkInformation);
+}
+
/**
*
* rct2: 0x0066A86C
@@ -648,14 +663,24 @@ void AwardUpdateAll()
// Check if award is deserved
if (AwardIsDeserved(awardType, activeAwardTypes))
{
- // Add award
- currentAwards.push_back(Award{ 5u, awardType });
- if (Config::Get().notifications.ParkAward)
- {
- News::AddItemToQueue(News::ItemType::award, AwardNewsStrings[EnumValue(awardType)], 0, {});
- }
- windowMgr->InvalidateByClass(WindowClass::ParkInformation);
+ AwardAdd(awardType);
}
}
}
}
+
+void AwardGrant(AwardType type)
+{
+ auto& currentAwards = getGameState().currentAwards;
+
+ // Remove award type if already granted
+ std::erase_if(currentAwards, [type](const Award& award) { return award.Type == type; });
+
+ // Ensure there is space for the award
+ if (currentAwards.size() >= OpenRCT2::Limits::kMaxAwards)
+ {
+ currentAwards.erase(currentAwards.begin());
+ }
+
+ AwardAdd(type);
+}
diff --git a/src/openrct2/management/Award.h b/src/openrct2/management/Award.h
index 24467da0e3..60790f0954 100644
--- a/src/openrct2/management/Award.h
+++ b/src/openrct2/management/Award.h
@@ -9,6 +9,9 @@
#pragma once
+#include "../SpriteIds.h"
+#include "../localisation/StringIds.h"
+
#include
#include
@@ -42,5 +45,9 @@ struct Award
};
bool AwardIsPositive(AwardType type);
+ImageIndex AwardGetSprite(AwardType type);
+StringId AwardGetText(AwardType type);
+StringId AwardGetNews(AwardType type);
+void AwardGrant(AwardType type);
void AwardReset();
void AwardUpdateAll();
diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp
index 8ddfd71c80..537842edf3 100644
--- a/src/openrct2/scripting/ScriptEngine.cpp
+++ b/src/openrct2/scripting/ScriptEngine.cpp
@@ -51,6 +51,7 @@
#include "bindings/object/ScObjectManager.h"
#include "bindings/ride/ScRide.hpp"
#include "bindings/ride/ScRideStation.hpp"
+ #include "bindings/world/ScAward.hpp"
#include "bindings/world/ScClimate.hpp"
#include "bindings/world/ScDate.hpp"
#include "bindings/world/ScMap.hpp"
@@ -403,6 +404,7 @@ void ScriptEngine::Initialise()
throw std::runtime_error("Script engine already initialised.");
auto ctx = static_cast(_context);
+ ScAward::Register(ctx);
ScCheats::Register(ctx);
ScClimate::Register(ctx);
ScWeatherState::Register(ctx);
diff --git a/src/openrct2/scripting/ScriptEngine.h b/src/openrct2/scripting/ScriptEngine.h
index e54898add0..38919c5cbf 100644
--- a/src/openrct2/scripting/ScriptEngine.h
+++ b/src/openrct2/scripting/ScriptEngine.h
@@ -46,7 +46,7 @@ namespace OpenRCT2
namespace OpenRCT2::Scripting
{
- static constexpr int32_t kPluginApiVersion = 109;
+ static constexpr int32_t kPluginApiVersion = 110;
// Versions marking breaking changes.
static constexpr int32_t kApiVersionPeepDeprecation = 33;
diff --git a/src/openrct2/scripting/bindings/world/ScAward.cpp b/src/openrct2/scripting/bindings/world/ScAward.cpp
new file mode 100644
index 0000000000..f1f29d7d40
--- /dev/null
+++ b/src/openrct2/scripting/bindings/world/ScAward.cpp
@@ -0,0 +1,90 @@
+/*****************************************************************************
+ * Copyright (c) 2014-2025 OpenRCT2 developers
+ *
+ * For a complete list of all authors, please refer to contributors.md
+ * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
+ *
+ * OpenRCT2 is licensed under the GNU General Public License version 3.
+ *****************************************************************************/
+
+#ifdef ENABLE_SCRIPTING
+
+ #include "ScAward.hpp"
+
+ #include "../../../GameState.h"
+ #include "../../../localisation/Formatting.h"
+ #include "../../../management/Award.h"
+ #include "../../../windows/Intent.h"
+ #include "../../Duktape.hpp"
+
+namespace OpenRCT2::Scripting
+{
+ ScAward::ScAward(size_t index)
+ : _index(index)
+ {
+ }
+
+ void ScAward::Register(duk_context* ctx)
+ {
+ dukglue_register_property(ctx, &ScAward::type_get, nullptr, "type");
+ dukglue_register_property(ctx, &ScAward::text_get, nullptr, "text");
+ dukglue_register_property(ctx, &ScAward::positive_get, nullptr, "positive");
+ dukglue_register_property(ctx, &ScAward::imageId_get, nullptr, "imageId");
+ dukglue_register_property(ctx, &ScAward::monthsRemaining_get, nullptr, "monthsRemaining");
+ }
+
+ Award* ScAward::GetAward() const
+ {
+ return &getGameState().currentAwards[_index];
+ }
+
+ std::string ScAward::type_get() const
+ {
+ auto award = GetAward();
+ if (award == nullptr)
+ return {};
+
+ return AwardTypeToString(award->Type).value_or(std::string());
+ }
+
+ std::string ScAward::text_get() const
+ {
+ auto award = GetAward();
+ if (award == nullptr)
+ return {};
+
+ Formatter ft{};
+ ft.Add(AwardGetText(award->Type));
+ return FormatStringIDLegacy(STR_STRINGID, ft.Data());
+ }
+
+ uint16_t ScAward::monthsRemaining_get() const
+ {
+ auto award = GetAward();
+ if (award == nullptr)
+ return {};
+
+ return award->Time;
+ }
+
+ bool ScAward::positive_get() const
+ {
+ auto award = GetAward();
+ if (award == nullptr)
+ return {};
+
+ return AwardIsPositive(award->Type);
+ }
+
+ uint32_t ScAward::imageId_get() const
+ {
+ auto award = GetAward();
+ if (award == nullptr)
+ return {};
+
+ return AwardGetSprite(award->Type);
+ }
+
+} // namespace OpenRCT2::Scripting
+
+#endif
diff --git a/src/openrct2/scripting/bindings/world/ScAward.hpp b/src/openrct2/scripting/bindings/world/ScAward.hpp
new file mode 100644
index 0000000000..f38b1a556f
--- /dev/null
+++ b/src/openrct2/scripting/bindings/world/ScAward.hpp
@@ -0,0 +1,85 @@
+/*****************************************************************************
+ * Copyright (c) 2014-2025 OpenRCT2 developers
+ *
+ * For a complete list of all authors, please refer to contributors.md
+ * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
+ *
+ * OpenRCT2 is licensed under the GNU General Public License version 3.
+ *****************************************************************************/
+
+#pragma once
+
+#ifdef ENABLE_SCRIPTING
+
+ #include "../../../Context.h"
+ #include "../../../management/Award.h"
+ #include "../../Duktape.hpp"
+ #include "../../ScriptEngine.h"
+
+ #include
+
+namespace OpenRCT2::Scripting
+{
+ static constexpr const char* AwardTypes[] = {
+ "mostUntidy",
+ "mostTidy",
+ "bestRollerCoasters",
+ "bestValue",
+ "mostBeautiful",
+ "worstValue",
+ "safest",
+ "bestStaff",
+ "bestFood",
+ "worstFood",
+ "bestToilets",
+ "mostDisappointing",
+ "bestWaterRides",
+ "bestCustomDesignedRides",
+ "mostDazzlingRideColours",
+ "mostConfusingLayout",
+ "bestGentleRides",
+ };
+
+ inline std::optional AwardTypeToString(AwardType awardType)
+ {
+ auto index = static_cast(awardType);
+ if (index < std::size(AwardTypes))
+ {
+ return AwardTypes[index];
+ }
+ return std::nullopt;
+ }
+
+ inline std::optional StringToAwardType(std::string_view awardType)
+ {
+ auto it = std::find(std::begin(AwardTypes), std::end(AwardTypes), awardType);
+ if (it != std::end(AwardTypes))
+ {
+ return std::optional(static_cast(std::distance(std::begin(AwardTypes), it)));
+ }
+ return std::nullopt;
+ }
+
+ class ScAward
+ {
+ private:
+ size_t _index{};
+
+ public:
+ ScAward(size_t index);
+
+ static void Register(duk_context* ctx);
+
+ private:
+ Award* GetAward() const;
+
+ std::string type_get() const;
+ std::string text_get() const;
+ uint16_t monthsRemaining_get() const;
+ bool positive_get() const;
+ uint32_t imageId_get() const;
+ };
+
+} // namespace OpenRCT2::Scripting
+
+#endif
diff --git a/src/openrct2/scripting/bindings/world/ScPark.cpp b/src/openrct2/scripting/bindings/world/ScPark.cpp
index 555bcff057..5dabf66f1c 100644
--- a/src/openrct2/scripting/bindings/world/ScPark.cpp
+++ b/src/openrct2/scripting/bindings/world/ScPark.cpp
@@ -430,6 +430,35 @@ namespace OpenRCT2::Scripting
return result;
}
+ std::vector> ScPark::awards_get() const
+ {
+ std::vector> result;
+
+ auto& gameState = getGameState();
+ for (size_t i = 0; i < gameState.currentAwards.size(); i++)
+ {
+ result.push_back(std::make_shared(i));
+ }
+
+ return result;
+ }
+
+ void ScPark::clearAwards() const
+ {
+ ThrowIfGameStateNotMutable();
+ AwardReset();
+ }
+
+ void ScPark::grantAward(const std::string& awardType) const
+ {
+ ThrowIfGameStateNotMutable();
+ auto optType = StringToAwardType(awardType);
+ if (optType.has_value())
+ {
+ AwardGrant(optType.value());
+ }
+ }
+
void ScPark::Register(duk_context* ctx)
{
dukglue_register_property(ctx, &ScPark::cash_get, &ScPark::cash_set, "cash");
@@ -463,6 +492,9 @@ namespace OpenRCT2::Scripting
dukglue_register_method(ctx, &ScPark::setFlag, "setFlag");
dukglue_register_method(ctx, &ScPark::postMessage, "postMessage");
dukglue_register_method(ctx, &ScPark::getMonthlyExpenditure, "getMonthlyExpenditure");
+ dukglue_register_property(ctx, &ScPark::awards_get, nullptr, "awards");
+ dukglue_register_method(ctx, &ScPark::clearAwards, "clearAwards");
+ dukglue_register_method(ctx, &ScPark::grantAward, "grantAward");
}
} // namespace OpenRCT2::Scripting
diff --git a/src/openrct2/scripting/bindings/world/ScPark.hpp b/src/openrct2/scripting/bindings/world/ScPark.hpp
index 42668dbe9c..dcef4d4377 100644
--- a/src/openrct2/scripting/bindings/world/ScPark.hpp
+++ b/src/openrct2/scripting/bindings/world/ScPark.hpp
@@ -13,6 +13,7 @@
#include "../../../Context.h"
#include "../../Duktape.hpp"
+ #include "ScAward.hpp"
#include "ScParkMessage.hpp"
#include "ScResearch.hpp"
@@ -101,6 +102,12 @@ namespace OpenRCT2::Scripting
std::vector getMonthlyExpenditure(const std::string& expenditureType) const;
+ std::vector> awards_get() const;
+
+ void clearAwards() const;
+
+ void grantAward(const std::string& awardType) const;
+
static void Register(duk_context* ctx);
};
} // namespace OpenRCT2::Scripting