From e5107141e915551b4bdc89aeae16ef5221947804 Mon Sep 17 00:00:00 2001 From: Ted John Date: Tue, 8 Sep 2020 00:08:07 +0100 Subject: [PATCH 1/3] Add plugin API for park flags --- distribution/openrct2.d.ts | 28 +++++++++++++++++++ src/openrct2/scripting/ScPark.hpp | 36 +++++++++++++++++++++++++ src/openrct2/scripting/ScriptEngine.cpp | 2 +- 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index c37757a5f7..ad25517878 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -1424,6 +1424,21 @@ declare global { subject?: number; } + type ParkFlags = + "difficultGuestGeneration" | + "difficultParkRating" | + "forbidHighConstruction" | + "forbidLandscapeChanges" | + "forbidMarketingCampaigns" | + "forbidTreeRemoval" | + "freeParkEntry" | + "noMoney" | + "open" | + "preferLessIntenseRides" | + "preferMoreIntenseRides" | + "scenarioCompleteNameInput" | + "unlockAllPrices"; + interface Park { cash: number; rating: number; @@ -1438,6 +1453,19 @@ declare global { name: string; messages: ParkMessage[]; + /** + * Gets whether a given flag is set or not. + * @param key The flag to test. + */ + getFlag(flag: ParkFlags): boolean; + + /** + * Sets the given flag to the given value. + * @param key The flag to set. + * @param value Whether to set or clear the flag. + */ + setFlag(flag: ParkFlags, value: boolean): void; + postMessage(message: string): void; postMessage(message: ParkMessageDesc): void; } diff --git a/src/openrct2/scripting/ScPark.hpp b/src/openrct2/scripting/ScPark.hpp index 78e646c9b3..fd122e9b30 100644 --- a/src/openrct2/scripting/ScPark.hpp +++ b/src/openrct2/scripting/ScPark.hpp @@ -229,6 +229,23 @@ namespace OpenRCT2::Scripting } }; + static const DukEnumMap ParkFlagMap({ + { "open", PARK_FLAGS_PARK_OPEN }, + { "scenarioCompleteNameInput", PARK_FLAGS_SCENARIO_COMPLETE_NAME_INPUT }, + { "forbidLandscapeChanges", PARK_FLAGS_FORBID_LANDSCAPE_CHANGES }, + { "forbidTreeRemoval", PARK_FLAGS_FORBID_TREE_REMOVAL }, + { "forbidHighConstruction", PARK_FLAGS_FORBID_HIGH_CONSTRUCTION }, + { "preferLessIntenseRides", PARK_FLAGS_PREF_LESS_INTENSE_RIDES }, + { "forbidMarketingCampaigns", PARK_FLAGS_FORBID_MARKETING_CAMPAIGN }, + { "preferMoreIntenseRides", PARK_FLAGS_PREF_MORE_INTENSE_RIDES }, + { "noMoney", PARK_FLAGS_NO_MONEY }, + { "difficultGuestGeneration", PARK_FLAGS_DIFFICULT_GUEST_GENERATION }, + { "freeParkEntry", PARK_FLAGS_PARK_FREE_ENTRY }, + { "difficultParkRating", PARK_FLAGS_DIFFICULT_PARK_RATING }, + { "noMoney", PARK_FLAGS_NO_MONEY_SCENARIO }, + { "unlockAllPrices", PARK_FLAGS_UNLOCK_ALL_PRICES }, + }); + class ScPark { public: @@ -300,6 +317,23 @@ namespace OpenRCT2::Scripting GetContext()->GetGameState()->GetPark().Name = value; } + bool getFlag(const std::string& key) const + { + auto mask = ParkFlagMap[key]; + return (gParkFlags & mask) != 0; + } + + void setFlag(const std::string& key, bool value) + { + ThrowIfGameStateNotMutable(); + auto mask = ParkFlagMap[key]; + if (value) + gParkFlags |= mask; + else + gParkFlags &= ~mask; + gfx_invalidate_screen(); + } + std::vector> messages_get() const { std::vector> result; @@ -395,6 +429,8 @@ namespace OpenRCT2::Scripting dukglue_register_property(ctx, &ScPark::entranceFee_get, &ScPark::entranceFee_set, "entranceFee"); dukglue_register_property(ctx, &ScPark::name_get, &ScPark::name_set, "name"); dukglue_register_property(ctx, &ScPark::messages_get, &ScPark::messages_set, "messages"); + dukglue_register_method(ctx, &ScPark::getFlag, "getFlag"); + dukglue_register_method(ctx, &ScPark::setFlag, "setFlag"); dukglue_register_method(ctx, &ScPark::postMessage, "postMessage"); } }; diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp index 89c0195c33..b7cd51a4d6 100644 --- a/src/openrct2/scripting/ScriptEngine.cpp +++ b/src/openrct2/scripting/ScriptEngine.cpp @@ -42,7 +42,7 @@ using namespace OpenRCT2; using namespace OpenRCT2::Scripting; -static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 6; +static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 7; struct ExpressionStringifier final { From 146a75400186ff0f3801c316161cd67d35552280 Mon Sep 17 00:00:00 2001 From: Ted John Date: Tue, 8 Sep 2020 01:08:21 +0100 Subject: [PATCH 2/3] 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; From e79d4be5a5f04c79d43dc620f1e2d3aa7b68d352 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 13 Sep 2020 14:55:54 +0100 Subject: [PATCH 3/3] Add quadrant and occupiedQuadrants to plugin API --- distribution/changelog.txt | 2 ++ distribution/openrct2.d.ts | 2 ++ src/openrct2/scripting/ScTile.hpp | 32 +++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/distribution/changelog.txt b/distribution/changelog.txt index 97a4303cdf..2502fe62c2 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -5,6 +5,8 @@ - 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. +- Feature: [#12884] Add BaseTileElement.occupiedQuadrants to the plugin API. +- Feature: [#12885] Add SmallSceneryElement.quadrant to the plugin API. - Feature: [#12886] Make all scenery placement and remove actions available to the plugin API. - Fix: [#400] Unable to place some saved tracks flush to the ground (original bug). - Fix: [#5753] Entertainers make themselves happy instead of the guests. diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 88274c024e..9f20c293e8 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -506,6 +506,7 @@ declare global { type: TileElementType; baseHeight: number; clearanceHeight: number; + occupiedQuadrants: number; isHidden: boolean; /** Take caution when changing this field, it may invalidate TileElements you have stored in your script. */ } @@ -554,6 +555,7 @@ declare global { primaryColour: number; secondaryColour: number; direction: Direction; + quadrant: number; } interface EntranceElement extends BaseTileElement { diff --git a/src/openrct2/scripting/ScTile.hpp b/src/openrct2/scripting/ScTile.hpp index e29f05ea3d..2ee2bed8e1 100644 --- a/src/openrct2/scripting/ScTile.hpp +++ b/src/openrct2/scripting/ScTile.hpp @@ -615,6 +615,35 @@ namespace OpenRCT2::Scripting } } + uint8_t quadrant_get() const + { + auto el = _element->AsSmallScenery(); + if (el != nullptr) + return el->GetSceneryQuadrant(); + return 0; + } + void quadrant_set(uint8_t value) + { + ThrowIfGameStateNotMutable(); + auto el = _element->AsSmallScenery(); + if (el != nullptr) + { + el->SetSceneryQuadrant(value); + Invalidate(); + } + } + + uint8_t occupiedQuadrants_get() const + { + return _element->GetOccupiedQuadrants(); + } + void occupiedQuadrants_set(uint8_t value) + { + ThrowIfGameStateNotMutable(); + _element->SetOccupiedQuadrants(value); + Invalidate(); + } + uint8_t primaryColour_get() const { switch (_element->GetType()) @@ -975,6 +1004,8 @@ namespace OpenRCT2::Scripting dukglue_register_property( ctx, &ScTileElement::clearanceHeight_get, &ScTileElement::clearanceHeight_set, "clearanceHeight"); dukglue_register_property(ctx, &ScTileElement::direction_get, &ScTileElement::direction_set, "direction"); + dukglue_register_property( + ctx, &ScTileElement::occupiedQuadrants_get, &ScTileElement::occupiedQuadrants_set, "occupiedQuadrants"); // Some dukglue_register_property(ctx, &ScTileElement::object_get, &ScTileElement::object_set, "object"); @@ -1010,6 +1041,7 @@ namespace OpenRCT2::Scripting // Small Scenery only dukglue_register_property(ctx, &ScTileElement::age_get, &ScTileElement::age_set, "age"); + dukglue_register_property(ctx, &ScTileElement::quadrant_get, &ScTileElement::quadrant_set, "quadrant"); // Footpath only dukglue_register_property(ctx, &ScTileElement::railings_get, &ScTileElement::railings_set, "railings");