From 146a75400186ff0f3801c316161cd67d35552280 Mon Sep 17 00:00:00 2001 From: Ted John Date: Tue, 8 Sep 2020 01:08:21 +0100 Subject: [PATCH] Add plugin API for scenario and objective --- distribution/changelog.txt | 1 + distribution/openrct2.d.ts | 105 ++++++++ src/openrct2/libopenrct2.vcxproj | 1 + src/openrct2/scripting/ScScenario.hpp | 310 ++++++++++++++++++++++++ src/openrct2/scripting/ScriptEngine.cpp | 4 + 5 files changed, 421 insertions(+) create mode 100644 src/openrct2/scripting/ScScenario.hpp diff --git a/distribution/changelog.txt b/distribution/changelog.txt index b198b4b220..97a4303cdf 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -1,6 +1,7 @@ 0.3.0+ (in development) ------------------------------------------------------------------------ - Feature: [#10807] Add 2x and 4x zoom levels (currently limited to OpenGL). +- Feature: [#12703] Add scenario plugin APIs. - Feature: [#12708] Add plugin-accessible names to all game actions. - Feature: [#12712] Add TCP / socket plugin APIs. - Feature: [#12840] Add Park.entranceFee to the plugin API. diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index ad25517878..88274c024e 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -35,6 +35,8 @@ declare global { var network: Network; /** APIs for the park and management of it. */ var park: Park; + /** APIs for the current scenario. */ + var scenario: Scenario; /** * APIs for controlling the user interface. * These will only be available to servers and clients that are not running headless mode. @@ -1470,6 +1472,109 @@ declare global { postMessage(message: ParkMessageDesc): void; } + type ScenarioObjectiveType = + "none" | + "guestsBy" | + "parkValueBy" | + "haveFun" | + "buildTheBest" | + "10Rollercoasters" | + "guestsAndRating" | + "monthlyRideIncome" | + "10RollercoastersLength" | + "finish5Rollercoasters" | + "replayLoanAndParkValue" | + "monthlyFoodIncome"; + + interface ScenarioObjective { + /** + * The objective type. + */ + type: ScenarioObjective; + + /** + * The required number of guests. + */ + guests: number; + + /** + * The year the objective must be completed by the end of. + */ + year: number; + + /** + * The minimum length required for each rollercoaster. + */ + length: number; + + /** + * The minimum excitement rating required for each rollercoaster. + */ + excitement: number; + + /** + * The minimum park value required. + */ + parkValue: number; + + /** + * The minimum monthly income from rides / food. + */ + monthlyIncome: number; + } + + type ScenarioStatus = "inProgress" | "completed" | "failed"; + + interface Scenario { + /** + * The name of the scenario. This is not necessarily the name of the park. + */ + name: string; + + /** + * The description of the scenario, shown above the scenario objective. + */ + details: string; + + /** + * The entered player name if the scenario is complete. + */ + completedBy: string; + + /** + * The filename of the scenario that is being played. Used to match the + * completion score with the scenario file. + */ + filename: string; + + /** + * The criteria required to complete the scenario. + */ + objective: ScenarioObjective; + + /** + * The number of consecutive days the park rating has been under the threshold for. + * This is reset when the park rating rises above the threshold again. + * Also used to post warning messages. + */ + parkRatingWarningDays: number; + + /** + * The company value when the scenario was completed. + */ + completedCompanyValue?: number; + + /** + * The current status of the scenario. + */ + status: ScenarioStatus; + + /** + * The current highest recorded company value. + */ + companyValueRecord: number; + } + interface Cheats { allowArbitraryRideTypeChanges: boolean; allowTrackPlaceInvalidHeights: boolean; diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj index d079a1dc3f..dd2b045f3a 100644 --- a/src/openrct2/libopenrct2.vcxproj +++ b/src/openrct2/libopenrct2.vcxproj @@ -410,6 +410,7 @@ + diff --git a/src/openrct2/scripting/ScScenario.hpp b/src/openrct2/scripting/ScScenario.hpp new file mode 100644 index 0000000000..33e1998abb --- /dev/null +++ b/src/openrct2/scripting/ScScenario.hpp @@ -0,0 +1,310 @@ +/***************************************************************************** + * 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 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#pragma once + +#ifdef ENABLE_SCRIPTING + +# include "../Context.h" +# include "../GameState.h" +# include "../common.h" +# include "../core/String.hpp" +# include "../scenario/Scenario.h" +# include "../world/Park.h" +# include "Duktape.hpp" +# include "ScriptEngine.h" + +# include + +namespace OpenRCT2::Scripting +{ + static const DukEnumMap ScenarioObjectiveTypeMap({ + { "none", OBJECTIVE_NONE }, + { "guestsBy", OBJECTIVE_GUESTS_BY }, + { "parkValueBy", OBJECTIVE_PARK_VALUE_BY }, + { "haveFun", OBJECTIVE_HAVE_FUN }, + { "buildTheBest", OBJECTIVE_BUILD_THE_BEST }, + { "10Rollercoasters", OBJECTIVE_10_ROLLERCOASTERS }, + { "guestsAndRating", OBJECTIVE_GUESTS_AND_RATING }, + { "monthlyRideIncome", OBJECTIVE_MONTHLY_RIDE_INCOME }, + { "10RollercoastersLength", OBJECTIVE_10_ROLLERCOASTERS_LENGTH }, + { "finish5Rollercoasters", OBJECTIVE_FINISH_5_ROLLERCOASTERS }, + { "replayLoanAndParkValue", OBJECTIVE_REPLAY_LOAN_AND_PARK_VALUE }, + { "monthlyFoodIncome", OBJECTIVE_MONTHLY_FOOD_INCOME }, + }); + + class ScScenarioObjective + { + private: + std::string type_get() + { + return std::string(ScenarioObjectiveTypeMap[gScenarioObjective.Type]); + } + + void type_set(const std::string& value) + { + ThrowIfGameStateNotMutable(); + gScenarioObjective.Type = ScenarioObjectiveTypeMap[value]; + } + + uint16_t guests_get() + { + if (gScenarioObjective.Type == OBJECTIVE_GUESTS_BY || gScenarioObjective.Type == OBJECTIVE_GUESTS_AND_RATING) + { + return gScenarioObjective.NumGuests; + } + return 0; + } + + void guests_set(uint16_t value) + { + ThrowIfGameStateNotMutable(); + if (gScenarioObjective.Type == OBJECTIVE_GUESTS_BY || gScenarioObjective.Type == OBJECTIVE_GUESTS_AND_RATING) + { + gScenarioObjective.NumGuests = value; + } + } + + uint8_t year_get() + { + if (gScenarioObjective.Type == OBJECTIVE_GUESTS_BY || gScenarioObjective.Type == OBJECTIVE_PARK_VALUE_BY) + { + return gScenarioObjective.Year; + } + return 0; + } + + void year_set(uint8_t value) + { + ThrowIfGameStateNotMutable(); + if (gScenarioObjective.Type == OBJECTIVE_GUESTS_BY || gScenarioObjective.Type == OBJECTIVE_PARK_VALUE_BY) + { + gScenarioObjective.Year = value; + } + } + + uint16_t length_get() + { + if (gScenarioObjective.Type == OBJECTIVE_10_ROLLERCOASTERS_LENGTH) + { + return gScenarioObjective.NumGuests; + } + return 0; + } + + void length_set(uint16_t value) + { + ThrowIfGameStateNotMutable(); + if (gScenarioObjective.Type == OBJECTIVE_10_ROLLERCOASTERS_LENGTH) + { + gScenarioObjective.NumGuests = value; + } + } + + money32 excitement_get() + { + if (gScenarioObjective.Type == OBJECTIVE_FINISH_5_ROLLERCOASTERS) + { + return gScenarioObjective.Currency; + } + return 0; + } + + void excitement_set(money32 value) + { + ThrowIfGameStateNotMutable(); + if (gScenarioObjective.Type == OBJECTIVE_FINISH_5_ROLLERCOASTERS) + { + gScenarioObjective.Currency = value; + } + } + + money32 parkValue_get() + { + if (gScenarioObjective.Type == OBJECTIVE_PARK_VALUE_BY + || gScenarioObjective.Type == OBJECTIVE_REPLAY_LOAN_AND_PARK_VALUE) + { + return gScenarioObjective.Currency; + } + return 0; + } + + void parkValue_set(money32 value) + { + ThrowIfGameStateNotMutable(); + if (gScenarioObjective.Type == OBJECTIVE_PARK_VALUE_BY + || gScenarioObjective.Type == OBJECTIVE_REPLAY_LOAN_AND_PARK_VALUE) + { + gScenarioObjective.Currency = value; + } + } + + money32 monthlyIncome_get() + { + if (gScenarioObjective.Type == OBJECTIVE_MONTHLY_RIDE_INCOME + || gScenarioObjective.Type == OBJECTIVE_MONTHLY_FOOD_INCOME) + { + return gScenarioObjective.Currency; + } + return 0; + } + + void monthlyIncome_set(money32 value) + { + ThrowIfGameStateNotMutable(); + if (gScenarioObjective.Type == OBJECTIVE_PARK_VALUE_BY + || gScenarioObjective.Type == OBJECTIVE_REPLAY_LOAN_AND_PARK_VALUE) + { + gScenarioObjective.Currency = value; + } + } + + public: + static void Register(duk_context* ctx) + { + dukglue_register_property(ctx, &ScScenarioObjective::type_get, &ScScenarioObjective::type_set, "type"); + dukglue_register_property(ctx, &ScScenarioObjective::guests_get, &ScScenarioObjective::guests_set, "guests"); + dukglue_register_property(ctx, &ScScenarioObjective::year_get, &ScScenarioObjective::year_set, "year"); + dukglue_register_property( + ctx, &ScScenarioObjective::excitement_get, &ScScenarioObjective::excitement_set, "excitement"); + dukglue_register_property( + ctx, &ScScenarioObjective::monthlyIncome_get, &ScScenarioObjective::monthlyIncome_set, "monthlyIncome"); + dukglue_register_property( + ctx, &ScScenarioObjective::parkValue_get, &ScScenarioObjective::parkValue_set, "parkValue"); + } + }; + + class ScScenario + { + public: + std::string name_get() + { + return gScenarioName; + } + + void name_set(const std::string& value) + { + ThrowIfGameStateNotMutable(); + gScenarioName = value; + } + + std::string details_get() + { + return gScenarioDetails; + } + + void details_set(const std::string& value) + { + ThrowIfGameStateNotMutable(); + gScenarioDetails = value; + } + + std::string completedBy_get() + { + return gScenarioCompletedBy; + } + + void completedBy_set(const std::string& value) + { + ThrowIfGameStateNotMutable(); + gScenarioCompletedBy = value; + } + + std::string filename_get() + { + return gScenarioFileName; + } + + void filename_set(const std::string& value) + { + ThrowIfGameStateNotMutable(); + String::Set(gScenarioFileName, std::size(gScenarioFileName), value.c_str()); + } + + std::shared_ptr objective_get() const + { + return std::make_shared(); + } + + uint16_t parkRatingWarningDays_get() const + { + return gScenarioParkRatingWarningDays; + } + + void parkRatingWarningDays_set(uint16_t value) + { + ThrowIfGameStateNotMutable(); + gScenarioParkRatingWarningDays = value; + } + + DukValue completedCompanyValue_get() const + { + auto ctx = GetContext()->GetScriptEngine().GetContext(); + if (gScenarioCompletedCompanyValue == MONEY32_UNDEFINED + || gScenarioCompletedCompanyValue == COMPANY_VALUE_ON_FAILED_OBJECTIVE) + { + return ToDuk(ctx, nullptr); + } + return ToDuk(ctx, gScenarioCompletedCompanyValue); + } + void completedCompanyValue_set(int32_t value) + { + ThrowIfGameStateNotMutable(); + gScenarioCompletedCompanyValue = value; + } + + std::string status_get() const + { + if (gScenarioCompletedCompanyValue == MONEY32_UNDEFINED) + return "inProgress"; + else if (gScenarioCompletedCompanyValue == COMPANY_VALUE_ON_FAILED_OBJECTIVE) + return "failed"; + return "completed"; + } + void status_set(const std::string& value) + { + ThrowIfGameStateNotMutable(); + if (value == "inProgress") + gScenarioCompletedCompanyValue = MONEY32_UNDEFINED; + else if (value == "failed") + gScenarioCompletedCompanyValue = COMPANY_VALUE_ON_FAILED_OBJECTIVE; + else if (value == "completed") + gScenarioCompletedCompanyValue = gCompanyValue; + } + + money32 companyValueRecord_get() const + { + return gScenarioCompanyValueRecord; + } + void companyValueRecord_set(money32 value) + { + ThrowIfGameStateNotMutable(); + gScenarioCompanyValueRecord = value; + } + + public: + static void Register(duk_context* ctx) + { + dukglue_register_property(ctx, &ScScenario::name_get, &ScScenario::name_set, "name"); + dukglue_register_property(ctx, &ScScenario::details_get, &ScScenario::details_set, "details"); + dukglue_register_property(ctx, &ScScenario::completedBy_get, &ScScenario::completedBy_set, "completedBy"); + dukglue_register_property(ctx, &ScScenario::filename_get, &ScScenario::filename_set, "filename"); + dukglue_register_property( + ctx, &ScScenario::parkRatingWarningDays_get, &ScScenario::parkRatingWarningDays_set, "parkRatingWarningDays"); + dukglue_register_property(ctx, &ScScenario::objective_get, nullptr, "objective"); + dukglue_register_property(ctx, &ScScenario::status_get, &ScScenario::status_set, "status"); + dukglue_register_property( + ctx, &ScScenario::completedCompanyValue_get, &ScScenario::completedCompanyValue_set, "completedCompanyValue"); + dukglue_register_property( + ctx, &ScScenario::companyValueRecord_get, &ScScenario::companyValueRecord_set, "companyValueRecord"); + } + }; +} // namespace OpenRCT2::Scripting + +#endif diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp index b7cd51a4d6..0a67294fb6 100644 --- a/src/openrct2/scripting/ScriptEngine.cpp +++ b/src/openrct2/scripting/ScriptEngine.cpp @@ -33,6 +33,7 @@ # include "ScObject.hpp" # include "ScPark.hpp" # include "ScRide.hpp" +# include "ScScenario.hpp" # include "ScSocket.hpp" # include "ScTile.hpp" @@ -398,6 +399,8 @@ void ScriptEngine::Initialise() ScSocket::Register(ctx); ScListener::Register(ctx); # endif + ScScenario::Register(ctx); + ScScenarioObjective::Register(ctx); ScStaff::Register(ctx); dukglue_register_global(ctx, std::make_shared(), "cheats"); @@ -407,6 +410,7 @@ void ScriptEngine::Initialise() dukglue_register_global(ctx, std::make_shared(ctx), "map"); dukglue_register_global(ctx, std::make_shared(ctx), "network"); dukglue_register_global(ctx, std::make_shared(), "park"); + dukglue_register_global(ctx, std::make_shared(), "scenario"); _initialised = true; _pluginsLoaded = false;