diff --git a/distribution/changelog.txt b/distribution/changelog.txt index 28c8dcecb7..ec841642bd 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -15,6 +15,7 @@ - Feature: [#16097] The Looping Roller Coaster can now draw all elements from the LIM Launched Roller Coaster. - Feature: [#16132, #16389] The Corkscrew, Twister and Vertical Drop Roller Coasters can now draw inline twists. - Feature: [#16144] [Plugin] Add ImageManager to API. +- Feature: [#16731] [Plugin] New API for fetching and manipulating a staff member's patrol area. - Improved: [#3517] Cheats are now saved with the park. - Improved: [#10150] Ride stations are now properly checked if they’re sheltered. - Improved: [#10664, #16072] Visibility status can be modified directly in the Tile Inspector's list. diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 954121a3d0..5dd80aad1c 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -1511,10 +1511,47 @@ declare global { * The enabled jobs the staff can do, e.g. sweep litter, water plants, inspect rides etc. */ orders: number; + + /** + * Gets the patrol area for the staff member. + */ + readonly patrolArea: PatrolArea; } type StaffType = "handyman" | "mechanic" | "security" | "entertainer"; + interface PatrolArea { + /** + * Gets or sets the map coodinates for all individual tiles in the staff member's patrol area. + * + * Note: fetching all the staff member's patrol area tiles can degrade performance. + */ + tiles: CoordsXY[]; + + /** + * Clears all tiles from the staff member's patrol area. + */ + clear(): void; + + /** + * Adds the given array of coordinates or a map range to the staff member's patrol area. + * @param coords An array of map coordinates, or a map range. + */ + add(coords: CoordsXY[] | MapRange): void; + + /** + * Removes the given array of coordinates or a map range from the staff member's patrol area. + * @param coords An array of map coordinates, or a map range. + */ + remove(coords: CoordsXY[] | MapRange): void; + + /** + * Checks whether a single coordinate is within the staff member's patrol area. + * @param coords An map coordinate. + */ + contains(coord: CoordsXY): boolean; + } + /** * Represents litter entity. */ diff --git a/src/openrct2/entity/Staff.h b/src/openrct2/entity/Staff.h index d373e99be7..9b852a0c44 100644 --- a/src/openrct2/entity/Staff.h +++ b/src/openrct2/entity/Staff.h @@ -65,7 +65,6 @@ public: void SetPatrolArea(const CoordsXY& coords, bool value); void SetPatrolArea(const MapRange& range, bool value); bool HasPatrolArea() const; - std::vector GetPatrolArea(); void SetPatrolArea(const std::vector& area); private: diff --git a/src/openrct2/scripting/Duktape.hpp b/src/openrct2/scripting/Duktape.hpp index afe96f18e2..f60e12b971 100644 --- a/src/openrct2/scripting/Duktape.hpp +++ b/src/openrct2/scripting/Duktape.hpp @@ -334,6 +334,14 @@ namespace OpenRCT2::Scripting return result; } + template<> MapRange inline FromDuk(const DukValue& d) + { + MapRange range; + range.Point1 = FromDuk(d["leftTop"]); + range.Point2 = FromDuk(d["rightBottom"]); + return range.Normalise(); + } + template<> DukValue inline ToDuk(duk_context* ctx, const CoordsXY& coords) { DukObject dukCoords(ctx); diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp index c79c711bb5..123dd2b89d 100644 --- a/src/openrct2/scripting/ScriptEngine.cpp +++ b/src/openrct2/scripting/ScriptEngine.cpp @@ -421,6 +421,7 @@ void ScriptEngine::Initialise() # endif ScScenario::Register(ctx); ScScenarioObjective::Register(ctx); + ScPatrolArea::Register(ctx); ScStaff::Register(ctx); dukglue_register_global(ctx, std::make_shared(), "cheats"); diff --git a/src/openrct2/scripting/bindings/entity/ScStaff.cpp b/src/openrct2/scripting/bindings/entity/ScStaff.cpp index 4b57f53120..2596083d6a 100644 --- a/src/openrct2/scripting/bindings/entity/ScStaff.cpp +++ b/src/openrct2/scripting/bindings/entity/ScStaff.cpp @@ -11,6 +11,7 @@ # include "ScStaff.hpp" +# include "../../../entity/PatrolArea.h" # include "../../../entity/Staff.h" namespace OpenRCT2::Scripting @@ -26,6 +27,7 @@ namespace OpenRCT2::Scripting dukglue_register_property(ctx, &ScStaff::staffType_get, &ScStaff::staffType_set, "staffType"); dukglue_register_property(ctx, &ScStaff::colour_get, &ScStaff::colour_set, "colour"); dukglue_register_property(ctx, &ScStaff::costume_get, &ScStaff::costume_set, "costume"); + dukglue_register_property(ctx, &ScStaff::patrolArea_get, nullptr, "patrolArea"); dukglue_register_property(ctx, &ScStaff::orders_get, &ScStaff::orders_set, "orders"); } @@ -122,6 +124,11 @@ namespace OpenRCT2::Scripting } } + std::shared_ptr ScStaff::patrolArea_get() const + { + return std::make_shared(_id); + } + uint8_t ScStaff::orders_get() const { auto peep = GetStaff(); @@ -138,6 +145,131 @@ namespace OpenRCT2::Scripting } } + ScPatrolArea::ScPatrolArea(EntityId id) + : _staffId(id) + { + } + + void ScPatrolArea::Register(duk_context* ctx) + { + dukglue_register_property(ctx, &ScPatrolArea::tiles_get, &ScPatrolArea::tiles_set, "tiles"); + dukglue_register_method(ctx, &ScPatrolArea::clear, "clear"); + dukglue_register_method(ctx, &ScPatrolArea::add, "add"); + dukglue_register_method(ctx, &ScPatrolArea::remove, "remove"); + dukglue_register_method(ctx, &ScPatrolArea::contains, "contains"); + } + + Staff* ScPatrolArea::GetStaff() const + { + return GetEntity(_staffId); + } + + void ScPatrolArea::ModifyArea(const DukValue& coordsOrRange, bool value) const + { + auto staff = GetStaff(); + if (staff != nullptr) + { + if (coordsOrRange.is_array()) + { + auto dukCoords = coordsOrRange.as_array(); + for (const auto& dukCoord : dukCoords) + { + auto coord = FromDuk(dukCoord); + staff->SetPatrolArea(coord, value); + map_invalidate_tile_full(coord); + } + } + else + { + auto mapRange = FromDuk(coordsOrRange); + for (int32_t y = mapRange.GetTop(); y <= mapRange.GetBottom(); y += COORDS_XY_STEP) + { + for (int32_t x = mapRange.GetLeft(); x <= mapRange.GetRight(); x += COORDS_XY_STEP) + { + CoordsXY coord(x, y); + staff->SetPatrolArea(coord, value); + map_invalidate_tile_full(coord); + } + } + } + UpdateConsolidatedPatrolAreas(); + } + } + + DukValue ScPatrolArea::tiles_get() const + { + auto ctx = GetContext()->GetScriptEngine().GetContext(); + + duk_push_array(ctx); + + auto staff = GetStaff(); + if (staff != nullptr && staff->PatrolInfo != nullptr) + { + auto tiles = staff->PatrolInfo->ToVector(); + + duk_uarridx_t index = 0; + for (const auto& tile : tiles) + { + auto dukCoord = ToDuk(ctx, tile.ToCoordsXY()); + dukCoord.push(); + duk_put_prop_index(ctx, -2, index); + index++; + } + } + + return DukValue::take_from_stack(ctx, -1); + } + + void ScPatrolArea::tiles_set(const DukValue& value) + { + ThrowIfGameStateNotMutable(); + + auto staff = GetStaff(); + if (staff != nullptr) + { + staff->ClearPatrolArea(); + if (value.is_array()) + { + ModifyArea(value, true); + } + } + } + + void ScPatrolArea::clear() + { + ThrowIfGameStateNotMutable(); + + auto staff = GetStaff(); + if (staff != nullptr) + { + staff->ClearPatrolArea(); + UpdateConsolidatedPatrolAreas(); + } + } + + void ScPatrolArea::add(const DukValue& coordsOrRange) + { + ThrowIfGameStateNotMutable(); + ModifyArea(coordsOrRange, true); + } + + void ScPatrolArea::remove(const DukValue& coordsOrRange) + { + ThrowIfGameStateNotMutable(); + ModifyArea(coordsOrRange, false); + } + + bool ScPatrolArea::contains(const DukValue& coord) const + { + auto staff = GetStaff(); + if (staff != nullptr) + { + auto pos = FromDuk(coord); + return staff->IsLocationInPatrol(pos); + } + return false; + } + } // namespace OpenRCT2::Scripting #endif diff --git a/src/openrct2/scripting/bindings/entity/ScStaff.hpp b/src/openrct2/scripting/bindings/entity/ScStaff.hpp index d1c322cd8d..f86e373572 100644 --- a/src/openrct2/scripting/bindings/entity/ScStaff.hpp +++ b/src/openrct2/scripting/bindings/entity/ScStaff.hpp @@ -15,6 +15,29 @@ namespace OpenRCT2::Scripting { + class ScPatrolArea + { + private: + EntityId _staffId; + + public: + ScPatrolArea(EntityId id); + + static void Register(duk_context* ctx); + + private: + Staff* GetStaff() const; + void ModifyArea(const DukValue& coordsOrRange, bool value) const; + + DukValue tiles_get() const; + void tiles_set(const DukValue& value); + + void clear(); + void add(const DukValue& coordsOrRange); + void remove(const DukValue& coordsOrRange); + bool contains(const DukValue& coord) const; + }; + class ScStaff : public ScPeep { public: @@ -34,6 +57,8 @@ namespace OpenRCT2::Scripting uint8_t costume_get() const; void costume_set(uint8_t value); + std::shared_ptr patrolArea_get() const; + uint8_t orders_get() const; void orders_set(uint8_t value); };