diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts
index e945771bfc..d60ede0b80 100644
--- a/distribution/openrct2.d.ts
+++ b/distribution/openrct2.d.ts
@@ -224,6 +224,10 @@ declare global {
getAllObjects(type: ObjectType): LoadedObject[];
getAllObjects(type: "ride"): RideObject[];
+ /**
+ * Gets the {@link TrackSegment} for the given type.
+ * @param type The track segment type.
+ */
getTrackSegment(type: number): TrackSegment | undefined;
/**
@@ -631,6 +635,14 @@ declare global {
getAllEntitiesOnTile(type: "car", tilePos: CoordsXY): Car[];
getAllEntitiesOnTile(type: "litter", tilePos: CoordsXY): Litter[];
createEntity(type: EntityType, initializer: object): Entity;
+
+ /**
+ * Gets a {@link TrackIterator} for the given track element. This can be used to
+ * iterate through a ride's circuit, segment by segment.
+ * @param location The tile coordinates.
+ * @param elementIndex The index of the track element on the tile.
+ */
+ getTrackIterator(location: CoordsXY, elementIndex: number): TrackIterator | undefined;
}
type TileElementType =
@@ -711,6 +723,7 @@ declare global {
hasChainLift: boolean;
isInverted: boolean;
hasCableLift: boolean;
+ isHighlighted: boolean;
}
interface SmallSceneryElement extends BaseTileElement {
@@ -1144,13 +1157,34 @@ declare global {
/**
* Gets a list of the elements that make up the track segment.
*/
- readonly elements: TrackSegmentElement;
+ readonly elements: TrackSegmentElement[];
}
- interface TrackSegmentElement {
- x: number;
- y: number;
- z: number;
+ interface TrackSegmentElement implements CoordsXYZ {
+ }
+
+ interface TrackIterator {
+ /**
+ * The position and direction of the current track segment, from the first element.
+ */
+ readonly position: CoordsXYZD;
+
+ /**
+ * The current track segment or undefined if at the beginning or end of a disconnected circuit.
+ */
+ readonly segment: TrackSegment | undefined;
+
+ /**
+ * Moves the iterator to the previous track segment.
+ * @returns true if there is a previous segment, otherwise false.
+ */
+ previous(): boolean;
+
+ /**
+ * Moves the iterator to the next track segment.
+ * @returns true if there is a next segment, otherwise false.
+ */
+ next(): boolean;
}
type EntityType =
diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj
index f0be46cdfe..a7b3f28571 100644
--- a/src/openrct2/libopenrct2.vcxproj
+++ b/src/openrct2/libopenrct2.vcxproj
@@ -468,6 +468,7 @@
+
@@ -926,6 +927,7 @@
+
diff --git a/src/openrct2/ride/Track.cpp b/src/openrct2/ride/Track.cpp
index 97478cf8e3..64caf56b7c 100644
--- a/src/openrct2/ride/Track.cpp
+++ b/src/openrct2/ride/Track.cpp
@@ -664,6 +664,25 @@ bool TrackTypeHasSpeedSetting(track_type_t trackType)
return trackType == TrackElemType::Brakes || trackType == TrackElemType::Booster;
}
+std::optional GetTrackSegmentOrigin(const CoordsXYE& posEl)
+{
+ auto trackEl = posEl.element->AsTrack();
+ if (trackEl == nullptr)
+ return {};
+
+ const auto& ted = GetTrackElementDescriptor(trackEl->GetTrackType());
+ auto direction = trackEl->GetDirection();
+ auto coords = CoordsXYZ(posEl.x, posEl.y, trackEl->GetBaseZ());
+
+ // Subtract the current sequence's offset
+ const auto* trackBlock = &ted.Block[trackEl->GetSequenceIndex()];
+ CoordsXY trackBlockOffset = { trackBlock->x, trackBlock->y };
+ coords += trackBlockOffset.Rotate(direction_reverse(direction));
+ coords.z -= trackBlock->z;
+
+ return CoordsXYZD(coords, direction);
+}
+
uint8_t TrackElement::GetSeatRotation() const
{
const auto* ride = get_ride(GetRideIndex());
diff --git a/src/openrct2/ride/Track.h b/src/openrct2/ride/Track.h
index 9edb2a0b4d..2df04c482c 100644
--- a/src/openrct2/ride/Track.h
+++ b/src/openrct2/ride/Track.h
@@ -14,6 +14,8 @@
#include "../world/Map.h"
#include "../world/TileElement.h"
+#include
+
constexpr const uint32_t RideConstructionSpecialPieceSelected = 0x10000;
constexpr const int32_t BLOCK_BRAKE_BASE_SPEED = 0x20364;
@@ -581,3 +583,4 @@ bool track_remove_station_element(const CoordsXYZD& loc, RideId rideIndex, int32
money32 maze_set_track(const CoordsXYZD& coords, uint8_t flags, bool initialPlacement, RideId rideIndex, uint8_t mode);
bool TrackTypeHasSpeedSetting(track_type_t trackType);
+std::optional GetTrackSegmentOrigin(const CoordsXYE& posEl);
diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp
index dc7251e8b4..4dcd5d53d6 100644
--- a/src/openrct2/scripting/ScriptEngine.cpp
+++ b/src/openrct2/scripting/ScriptEngine.cpp
@@ -413,6 +413,7 @@ void ScriptEngine::Initialise()
ScRideObjectVehicle::Register(ctx);
ScTile::Register(ctx);
ScTileElement::Register(ctx);
+ ScTrackIterator::Register(ctx);
ScTrackSegment::Register(ctx);
ScEntity::Register(ctx);
ScLitter::Register(ctx);
diff --git a/src/openrct2/scripting/bindings/ride/ScTrackIterator.cpp b/src/openrct2/scripting/bindings/ride/ScTrackIterator.cpp
new file mode 100644
index 0000000000..14371ef7eb
--- /dev/null
+++ b/src/openrct2/scripting/bindings/ride/ScTrackIterator.cpp
@@ -0,0 +1,122 @@
+/*****************************************************************************
+ * Copyright (c) 2022 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 "ScTrackIterator.h"
+
+# include "../../../Context.h"
+# include "../../../ride/Ride.h"
+# include "../../../ride/TrackData.h"
+# include "../../ScriptEngine.h"
+# include "ScTrackSegment.h"
+
+using namespace OpenRCT2::Scripting;
+using namespace OpenRCT2::TrackMetaData;
+
+std::shared_ptr ScTrackIterator::FromElement(const CoordsXY& position, int32_t elementIndex)
+{
+ auto el = map_get_nth_element_at(position, elementIndex);
+ auto origin = GetTrackSegmentOrigin(CoordsXYE(position, el));
+ if (!origin)
+ return nullptr;
+
+ auto trackEl = el->AsTrack();
+ return std::make_shared(*origin, trackEl->GetTrackType(), trackEl->GetRideIndex());
+}
+
+ScTrackIterator::ScTrackIterator(const CoordsXYZD& position, track_type_t type, RideId ride)
+ : _position(position)
+ , _type(type)
+ , _ride(ride)
+{
+}
+
+void ScTrackIterator::Register(duk_context* ctx)
+{
+ dukglue_register_property(ctx, &ScTrackIterator::position_get, nullptr, "position");
+ dukglue_register_property(ctx, &ScTrackIterator::segment_get, nullptr, "segment");
+ dukglue_register_method(ctx, &ScTrackIterator::previous, "previous");
+ dukglue_register_method(ctx, &ScTrackIterator::next, "next");
+}
+
+DukValue ScTrackIterator::position_get() const
+{
+ auto& scriptEngine = GetContext()->GetScriptEngine();
+ auto ctx = scriptEngine.GetContext();
+ return ToDuk(ctx, _position);
+}
+
+DukValue ScTrackIterator::segment_get() const
+{
+ auto& scriptEngine = GetContext()->GetScriptEngine();
+ auto ctx = scriptEngine.GetContext();
+
+ if (_type >= TrackElemType::Count)
+ return ToDuk(ctx, undefined);
+
+ return GetObjectAsDukValue(ctx, std::make_shared(_type));
+}
+
+bool ScTrackIterator::previous()
+{
+ auto& ted = GetTrackElementDescriptor(_type);
+ auto& seq0 = ted.Block;
+ auto pos = _position + CoordsXYZ(seq0->x, seq0->y, seq0->z);
+
+ auto el = map_get_track_element_at(pos);
+ if (el == nullptr)
+ return false;
+
+ auto posEl = CoordsXYE(pos.x, pos.y, reinterpret_cast(el));
+ track_begin_end tbe{};
+ auto value = track_block_get_previous(posEl, &tbe);
+ if (tbe.begin_element != nullptr)
+ {
+ auto prev = CoordsXYE(tbe.begin_x, tbe.begin_y, tbe.begin_element);
+ auto origin = GetTrackSegmentOrigin(prev);
+ if (origin)
+ {
+ _position = *origin;
+ _type = prev.element->AsTrack()->GetTrackType();
+ return value;
+ }
+ }
+ return false;
+}
+
+bool ScTrackIterator::next()
+{
+ auto& ted = GetTrackElementDescriptor(_type);
+ auto& seq0 = ted.Block;
+ auto pos = _position + CoordsXYZ(seq0->x, seq0->y, seq0->z);
+
+ auto el = map_get_track_element_at(pos);
+ if (el == nullptr)
+ return false;
+
+ auto posEl = CoordsXYE(_position.x, _position.y, reinterpret_cast(el));
+ CoordsXYE next;
+ int32_t z{};
+ int32_t direction = -1;
+ auto value = track_block_get_next(&posEl, &next, &z, &direction);
+ if (next.element != nullptr)
+ {
+ auto origin = GetTrackSegmentOrigin(next);
+ if (origin)
+ {
+ _position = *origin;
+ _type = next.element->AsTrack()->GetTrackType();
+ return value;
+ }
+ }
+ return false;
+}
+
+#endif
diff --git a/src/openrct2/scripting/bindings/ride/ScTrackIterator.h b/src/openrct2/scripting/bindings/ride/ScTrackIterator.h
new file mode 100644
index 0000000000..75827c7d95
--- /dev/null
+++ b/src/openrct2/scripting/bindings/ride/ScTrackIterator.h
@@ -0,0 +1,44 @@
+/*****************************************************************************
+ * Copyright (c) 2022 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 "../../../Identifiers.h"
+# include "../../../world/TileElement.h"
+# include "../../Duktape.hpp"
+
+# include
+
+namespace OpenRCT2::Scripting
+{
+ class ScTrackIterator
+ {
+ private:
+ CoordsXYZD _position;
+ track_type_t _type;
+ RideId _ride;
+
+ public:
+ static std::shared_ptr FromElement(const CoordsXY& position, int32_t elementIndex);
+ static void Register(duk_context* ctx);
+
+ ScTrackIterator(const CoordsXYZD& position, track_type_t type, RideId ride);
+
+ private:
+ DukValue position_get() const;
+ DukValue segment_get() const;
+ bool previous();
+ bool next();
+ };
+
+} // namespace OpenRCT2::Scripting
+
+#endif
diff --git a/src/openrct2/scripting/bindings/world/ScMap.cpp b/src/openrct2/scripting/bindings/world/ScMap.cpp
index e4bcfd123b..ecfbec1c69 100644
--- a/src/openrct2/scripting/bindings/world/ScMap.cpp
+++ b/src/openrct2/scripting/bindings/world/ScMap.cpp
@@ -31,6 +31,7 @@
# include "../entity/ScStaff.hpp"
# include "../entity/ScVehicle.hpp"
# include "../ride/ScRide.hpp"
+# include "../ride/ScTrackIterator.h"
# include "../world/ScTile.hpp"
namespace OpenRCT2::Scripting
@@ -303,6 +304,16 @@ namespace OpenRCT2::Scripting
return res;
}
+ DukValue ScMap::getTrackIterator(const DukValue& dukPosition, int32_t elementIndex) const
+ {
+ auto position = FromDuk(dukPosition);
+ auto trackIterator = ScTrackIterator::FromElement(position, elementIndex);
+ if (trackIterator == nullptr)
+ return ToDuk(_context, undefined);
+
+ return GetObjectAsDukValue(_context, trackIterator);
+ }
+
void ScMap::Register(duk_context* ctx)
{
dukglue_register_property(ctx, &ScMap::size_get, nullptr, "size");
@@ -315,6 +326,7 @@ namespace OpenRCT2::Scripting
dukglue_register_method(ctx, &ScMap::getAllEntities, "getAllEntities");
dukglue_register_method(ctx, &ScMap::getAllEntitiesOnTile, "getAllEntitiesOnTile");
dukglue_register_method(ctx, &ScMap::createEntity, "createEntity");
+ dukglue_register_method(ctx, &ScMap::getTrackIterator, "getTrackIterator");
}
DukValue ScMap::GetEntityAsDukValue(const EntityBase* sprite) const
diff --git a/src/openrct2/scripting/bindings/world/ScMap.hpp b/src/openrct2/scripting/bindings/world/ScMap.hpp
index 02c1c856e9..f00c863973 100644
--- a/src/openrct2/scripting/bindings/world/ScMap.hpp
+++ b/src/openrct2/scripting/bindings/world/ScMap.hpp
@@ -14,6 +14,7 @@
# include "../../../common.h"
# include "../../Duktape.hpp"
# include "../ride/ScRide.hpp"
+# include "../ride/ScTrackIterator.h"
# include "../world/ScTile.hpp"
namespace OpenRCT2::Scripting
@@ -46,6 +47,8 @@ namespace OpenRCT2::Scripting
DukValue createEntity(const std::string& type, const DukValue& initializer);
+ DukValue getTrackIterator(const DukValue& position, int32_t elementIndex) const;
+
static void Register(duk_context* ctx);
private:
diff --git a/src/openrct2/scripting/bindings/world/ScTileElement.cpp b/src/openrct2/scripting/bindings/world/ScTileElement.cpp
index 37a12f6326..be6d66b2c3 100644
--- a/src/openrct2/scripting/bindings/world/ScTileElement.cpp
+++ b/src/openrct2/scripting/bindings/world/ScTileElement.cpp
@@ -1075,6 +1075,27 @@ namespace OpenRCT2::Scripting
Invalidate();
}
+ DukValue ScTileElement::isHighlighted_get() const
+ {
+ auto ctx = GetContext()->GetScriptEngine().GetContext();
+ auto el = _element->AsTrack();
+ if (el != nullptr)
+ duk_push_boolean(ctx, el->IsHighlighted());
+ else
+ duk_push_null(ctx);
+ return DukValue::take_from_stack(ctx);
+ }
+ void ScTileElement::isHighlighted_set(bool value)
+ {
+ ThrowIfGameStateNotMutable();
+ auto el = _element->AsTrack();
+ if (el != nullptr)
+ {
+ el->SetHighlight(value);
+ Invalidate();
+ }
+ }
+
DukValue ScTileElement::object_get() const
{
auto& scriptEngine = GetContext()->GetScriptEngine();
@@ -2045,6 +2066,7 @@ namespace OpenRCT2::Scripting
dukglue_register_property(ctx, &ScTileElement::hasChainLift_get, &ScTileElement::hasChainLift_set, "hasChainLift");
dukglue_register_property(ctx, &ScTileElement::isInverted_get, &ScTileElement::isInverted_set, "isInverted");
dukglue_register_property(ctx, &ScTileElement::hasCableLift_get, &ScTileElement::hasCableLift_set, "hasCableLift");
+ dukglue_register_property(ctx, &ScTileElement::isHighlighted_get, &ScTileElement::isHighlighted_set, "isHighlighted");
// Small Scenery only
dukglue_register_property(ctx, &ScTileElement::age_get, &ScTileElement::age_set, "age");
diff --git a/src/openrct2/scripting/bindings/world/ScTileElement.hpp b/src/openrct2/scripting/bindings/world/ScTileElement.hpp
index 21893cd2ea..7249557943 100644
--- a/src/openrct2/scripting/bindings/world/ScTileElement.hpp
+++ b/src/openrct2/scripting/bindings/world/ScTileElement.hpp
@@ -114,6 +114,9 @@ namespace OpenRCT2::Scripting
DukValue hasCableLift_get() const;
void hasCableLift_set(bool value);
+ DukValue isHighlighted_get() const;
+ void isHighlighted_set(bool value);
+
DukValue object_get() const;
void object_set(const DukValue& value);