/*****************************************************************************
 * Copyright (c) 2014-2026 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.
 *****************************************************************************/

#include "Vehicle.h"

#include "../Diagnostic.h"
#include "../GameState.h"
#include "../audio/Audio.h"
#include "../core/Speed.hpp"
#include "../entity/EntityRegistry.h"
#include "../entity/EntityTweener.h"
#include "../scenario/Scenario.h"
#include "../world/Map.h"
#include "../world/MapAnimation.h"
#include "../world/tile_element/TileElement.h"
#include "../world/tile_element/TrackElement.h"
#include "Ride.h"
#include "RideData.h"
#include "Track.h"
#include "TrackData.h"
#include "TrackIteration.h"
#include "VehicleGeometry.h"
#include "ted/TrackElementDescriptor.h"

namespace OpenRCT2
{
    using namespace OpenRCT2::Audio;
    using namespace OpenRCT2::TrackMetadata;
    using namespace OpenRCT2::RideVehicle;

    // External globals from Vehicle.cpp
    extern Vehicle* gCurrentVehicle;
    extern Breakdown _vehicleBreakdown;
    extern StationIndex _vehicleStationIndex;
    extern uint32_t _vehicleMotionTrackFlags;
    extern int32_t _vehicleVelocity;
    extern int32_t _vehicleRemainingDistance;
    extern int32_t _vehicleSubpositionsMoved;
    extern uint8_t _vehicleBrakeSoundTimeout;
    extern Vehicle* _vehicleFrontVehicle;
    extern CoordsXYZ _vehicleCurPosition;

    // Constants
    constexpr uint8_t kTrackSpeedShiftAmount = 16;
    constexpr uint8_t kBoosterAccelerationShiftAmount = 16;
    constexpr int16_t kVehicleMaxSpinSpeedWaterRide = 512;
    constexpr int16_t kVehicleMinSpinSpeedWaterRide = -kVehicleMaxSpinSpeedWaterRide;

    /**
     *
     *  rct2: 0x006DAB90
     */
    void Vehicle::upstopCheck() const
    {
        const auto* carEntry = Entry();
        if (carEntry == nullptr)
        {
            return;
        }

        // No up stops (coaster types)
        if (carEntry->flags.has(CarEntryFlag::hasNoUpstopWheels))
        {
            if (!IsOnCoveredTrack())
            {
                auto gForces = GetGForces();
                gForces.lateralG = std::abs(gForces.lateralG);
                if (gForces.lateralG <= 150)
                {
                    if (Geometry::getAccelerationFromPitch(pitch) < 0)
                    {
                        if (gForces.verticalG > -40)
                        {
                            return;
                        }
                    }
                    else if (gForces.verticalG > -80)
                    {
                        return;
                    }
                }

                if (pitch != VehiclePitch::down60)
                {
                    _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_DERAILED;
                }
            }
        }
        else if (carEntry->flags.has(CarEntryFlag::hasNoUpstopWheelsBobsleigh))
        {
            // No up stops bobsleigh type
            if (!IsOnCoveredTrack())
            {
                auto gForces = GetGForces();

                if (Geometry::getAccelerationFromPitch(pitch) < 0)
                {
                    if (gForces.verticalG > -45)
                    {
                        return;
                    }
                }
                else
                {
                    if (gForces.verticalG > -80)
                    {
                        return;
                    }
                }

                if (pitch != VehiclePitch::down60 && pitch != VehiclePitch::down50)
                {
                    _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_DERAILED;
                }
            }
        }
    }

    /**
     * Modifies the train's velocity to match the block-brake fixed velocity.
     * This function must be called when the car is running through a non-stopping
     * state block-brake (precondition), which means that the block brake is acting
     * merely as a velocity regulator, in a closed state. When the brake is open, it
     * boosts the train to the speed limit
     */
    void Vehicle::applyNonstopBlockBrake()
    {
        if (velocity >= 0)
        {
            // If the vehicle is below the speed limit
            if (velocity <= kBlockBrakeBaseSpeed)
            {
                // Boost it to the fixed block brake speed
                velocity = kBlockBrakeBaseSpeed;
                acceleration = 0;
            }
            else if (velocity > (brake_speed << kTrackSpeedShiftAmount) + kBlockBrakeSpeedOffset)
            {
                velocity -= velocity >> 4;
                acceleration = 0;
            }
        }
    }

    /**
     *
     * Modifies the train's velocity influenced by a block brake
     */
    void Vehicle::applyStopBlockBrake()
    {
        // Slow it down till completely stop the car
        _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_AT_BLOCK_BRAKE;
        acceleration = 0;
        // If the this is slow enough, stop it. If not, slow it down
        if (velocity <= 2.0_mph)
        {
            velocity = 0;
        }
        else
        {
            velocity -= velocity >> 3;
        }
    }

    void Vehicle::applyCableLiftBlockBrake(bool brakeClosed)
    {
        // If we are already on the cable lift, ignore the brake
        if (status == Status::travellingCableLift)
            return;

        // Slow down if travelling faster than 4mph
        if (velocity > kBlockBrakeBaseSpeed)
        {
            velocity -= velocity >> 3;
            acceleration = 0;
        }
        // Once moving slow enough, keep moving forward until lined up with the catch car
        else if (track_progress <= 18)
        {
            velocity = kBlockBrakeBaseSpeed;
            acceleration = 0;
        }
        // Once in position, stop, and wait for the catch car
        if (velocity > 0 && track_progress >= 18)
        {
            velocity = 0;
            acceleration = 0;
            if (!brakeClosed)
                SetState(Status::waitingForCableLift, sub_state);
            else
                _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_AT_BLOCK_BRAKE;
        }
    }

    /**
     *
     *  rct2: 0x006DAC43
     */
    void Vehicle::handleBlockBrake()
    {
        auto curRide = GetRide();
        if (curRide == nullptr)
            return;

        auto carEntry = Entry();
        if (carEntry == nullptr)
            return;

        // Is chair lift type
        if (carEntry->flags.has(CarEntryFlag::isChairlift))
        {
            velocity = _vehicleBreakdown == Breakdown::safetyCutOut ? 0 : curRide->speed << 16;
            acceleration = 0;
        }

        auto trackType = GetTrackType();

        TileElement* trackElement = MapGetTrackElementAtOfType(TrackLocation, trackType);

        if (trackElement == nullptr)
        {
            return;
        }

        switch (trackType)
        {
            case TrackElemType::blockBrakes:
                // Check if this brake is the start of a cable lift
                if (curRide->flags.has(RideFlag::cableLift))
                {
                    CoordsXYE track;
                    int32_t zUnused;
                    int32_t direction;
                    if (trackBlockGetNextFromZero(
                            TrackLocation, *curRide, GetTrackDirection(), &track, &zUnused, &direction, false)
                        && track.element != nullptr && track.element->asTrack()->hasCableLift())
                    {
                        applyCableLiftBlockBrake(curRide->isBlockSectioned() && trackElement->asTrack()->isBrakeClosed());
                        break;
                    }
                }
                [[fallthrough]];
            case TrackElemType::diagBlockBrakes:
                if (curRide->isBlockSectioned() && trackElement->asTrack()->isBrakeClosed())
                    applyStopBlockBrake();
                else
                    applyNonstopBlockBrake();

                break;
            case TrackElemType::endStation:
                if (trackElement->asTrack()->isBrakeClosed())
                    _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_AT_BLOCK_BRAKE;

                break;
            case TrackElemType::up25ToFlat:
            case TrackElemType::up60ToFlat:
            case TrackElemType::cableLiftHill:
            case TrackElemType::diagUp25ToFlat:
            case TrackElemType::diagUp60ToFlat:
                if (curRide->isBlockSectioned())
                {
                    if (trackType == TrackElemType::cableLiftHill || trackElement->asTrack()->hasChain())
                    {
                        if (trackElement->asTrack()->isBrakeClosed())
                        {
                            applyStopBlockBrake();
                        }
                    }
                }
                break;
            default:
                break;
        }
    }

    /**
     *
     *  rct2: 0x006DADAE
     */
    void Vehicle::updateVelocity()
    {
        int32_t nextVelocity = acceleration + velocity;
        if (flags.has(VehicleFlag::stoppedBySafetyCutout))
        {
            nextVelocity = 0;
        }
        if (flags.has(VehicleFlag::stoppedOnHoldingBrake))
        {
            if (vertical_drop_countdown > 0)
            {
                nextVelocity = 0;
                acceleration = 0;
                vertical_drop_countdown--;
            }
        }
        velocity = nextVelocity;

        _vehicleVelocity = nextVelocity;
        _vehicleRemainingDistance = (nextVelocity >> 10) * 42;
    }

    static void blockBrakesOpenPreviousSection(
        const Ride& ride, const CoordsXYZ& vehicleTrackLocation, TileElement* tileElement)
    {
        CoordsXYZ location = vehicleTrackLocation;
        TrackElement* trackElement = trackGetPreviousBlock(location, tileElement);
        if (trackElement == nullptr)
            return;

        SetBrakeClosedMultiTile(*trackElement, location, false);
        MapInvalidateElement(location, reinterpret_cast<TileElement*>(trackElement));

        auto trackType = trackElement->getTrackType();
        if (trackType == TrackElemType::endStation)
        {
            Play3D(SoundId::blockBrakeClose, location);
        }
        else if (trackTypeIsBlockBrakes(trackType))
        {
            Play3D(SoundId::blockBrakeClose, location);
            BlockBrakeSetLinkedBrakesClosed(location, *trackElement, false);
        }
    }

    void Vehicle::goKartAttemptLaneSwitch()
    {
        uint16_t probability = 0x8000;
        if (flags.has(VehicleFlag::currentlyColliding))
        {
            flags.unset(VehicleFlag::currentlyColliding);
        }
        else
        {
            probability = 0x0A3D;
        }
        if ((ScenarioRand() & 0xFFFF) <= probability)
        {
            // This changes "riding left" to "moving to right lane" and "riding right" to "moving to left lane".
            TrackSubposition = VehicleTrackSubposition{ static_cast<uint8_t>(static_cast<uint8_t>(TrackSubposition) + 2u) };
        }
    }

    static void playSplashSound()
    {
        if (_vehicleVelocity <= kBlockBrakeBaseSpeed)
        {
            return;
        }

        Play3D(SoundId::waterSplash, { _vehicleCurPosition.x, _vehicleCurPosition.y, _vehicleCurPosition.z });
    }

    /**
     *
     *  rct2: 0x006DB59E
     */
    void Vehicle::handleWaterSplash() const
    {
        const auto* rideEntry = GetRideEntry();
        auto trackType = GetTrackType();

        if (!rideEntry->flags.has(RideEntryFlag::playSplashSound))
        {
            if (rideEntry->flags.has(RideEntryFlag::coveredTrackIsWaterChannel))
            {
                if (IsHead())
                {
                    if (IsOnCoveredTrack())
                    {
                        Vehicle* nextVehicle = getGameState().entities.getEntity<Vehicle>(next_vehicle_on_ride);
                        if (nextVehicle == nullptr)
                            return;

                        Vehicle* nextNextVehicle = getGameState().entities.getEntity<Vehicle>(
                            nextVehicle->next_vehicle_on_ride);
                        if (nextNextVehicle == nullptr)
                            return;
                        if (!nextNextVehicle->IsOnCoveredTrack())
                        {
                            if (track_progress == 4)
                            {
                                playSplashSound();
                            }
                        }
                    }
                }
            }
        }
        else
        {
            if (trackType == TrackElemType::down25ToFlat)
            {
                if (track_progress == 12)
                {
                    playSplashSound();
                }
            }
        }
        if (IsHead())
        {
            if (trackType == TrackElemType::waterSplash)
            {
                if (track_progress == 48)
                {
                    playSplashSound();
                }
            }
        }
    }

    /**
     *
     *  rct2: 0x006DBF3E
     */
    void Vehicle::findStationStopPoint()
    {
        const auto* carEntry = Entry();

        acceleration /= _vehicleSubpositionsMoved;
        if (TrackSubposition == VehicleTrackSubposition::chairliftGoingBack)
        {
            return;
        }

        auto trackType = GetTrackType();
        const auto& ted = GetTrackElementDescriptor(trackType);
        if (!ted.sequenceData.sequences[0].flags.has(SequenceFlag::trackOrigin))
        {
            return;
        }

        _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_3;

        TileElement* tileElement = nullptr;
        if (MapIsLocationValid(TrackLocation))
        {
            tileElement = MapGetTrackElementAtOfTypeSeq(TrackLocation, trackType, 0);
        }

        if (tileElement == nullptr)
        {
            return;
        }

        if (_vehicleStationIndex.IsNull())
        {
            _vehicleStationIndex = tileElement->asTrack()->getStationIndex();
        }

        if (trackType == TrackElemType::towerBase && this == gCurrentVehicle)
        {
            if (track_progress > 3 && !flags.has(VehicleFlag::poweredCarInReverse))
            {
                CoordsXYE output;
                int32_t outputZ, outputDirection;

                CoordsXYE input = { TrackLocation, tileElement };
                if (!trackBlockGetNext(&input, &output, &outputZ, &outputDirection))
                {
                    _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_12;
                }
            }

            if (track_progress <= 3)
            {
                _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_AT_STATION;
            }
        }

        if (trackType != TrackElemType::endStation || this != gCurrentVehicle)
        {
            return;
        }

        uint16_t ax = track_progress;
        if (_vehicleVelocity < 0)
        {
            if (ax <= 22)
            {
                _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_AT_STATION;
            }
        }
        else
        {
            uint16_t cx = 17;
            if (carEntry->flags.has(CarEntryFlag::isChairlift))
            {
                cx = 6;
            }
            if (carEntry->flags.has(CarEntryFlag::isGoKart))
            {
                // Determine the stop positions for the karts. If in left lane it's further along the track than the right lane.
                // Since it's not possible to overtake when the race has ended, this does not check for overtake states (7 and
                // 8).
                cx = TrackSubposition == VehicleTrackSubposition::goKartsRightLane ? 18 : 20;
            }

            if (ax > cx)
            {
                _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_AT_STATION;
            }
        }
    }

    /**
     * Determine whether to use block brake speed or brake speed. If block brake is closed or no block brake present, use the
     * brake's speed; if block brake is open, use maximum of brake speed or block brake speed.
     */
    uint8_t Vehicle::chooseBrakeSpeed() const
    {
        if (!trackTypeIsBrakes(GetTrackType()))
            return brake_speed;
        auto trackElement = MapGetTrackElementAtOfTypeSeq(TrackLocation, GetTrackType(), 0);
        if (trackElement != nullptr)
        {
            if (trackElement->asTrack()->isBrakeClosed())
                return brake_speed;
            else
                return std::max<uint8_t>(brake_speed, BlockBrakeSpeed);
        }
        return brake_speed;
    }

    /**
     * Populate the vehicle's brake_speed and BlockBrakeSpeed values.
     */
    void Vehicle::populateBrakeSpeed(const CoordsXYZ& vehicleTrackLocation, TrackElement& brake)
    {
        auto trackSpeed = brake.getBrakeBoosterSpeed();
        brake_speed = trackSpeed;
        if (!trackTypeIsBrakes(brake.getTrackType()))
        {
            BlockBrakeSpeed = trackSpeed;
            return;
        }
        // As soon as feasible, encode block brake speed into track element so the lookforward can be skipped here.

        CoordsXYE output = CoordsXYE(vehicleTrackLocation.x, vehicleTrackLocation.y, reinterpret_cast<TileElement*>(&brake));
        int32_t outputZ = vehicleTrackLocation.z;
        uint16_t timeoutCount = 256;
        do
        {
            if (trackTypeIsBlockBrakes(output.element->asTrack()->getTrackType()))
            {
                BlockBrakeSpeed = output.element->asTrack()->getBrakeBoosterSpeed();
                return;
            }
            if (!trackTypeIsBrakes(output.element->asTrack()->getTrackType()))
            {
                break;
            }
            timeoutCount--;
        } while (trackBlockGetNext(&output, &output, &outputZ, nullptr) && timeoutCount);

        // If block brake is not found, use the track's speed
        BlockBrakeSpeed = trackSpeed;
    }

    /**
     *
     *  rct2: 0x006DB08C
     */
    bool Vehicle::trackMotionForwardsGetNewTrack(TrackElemType trackType, const Ride& curRide, const RideObjectEntry& rideEntry)
    {
        CoordsXYZD location = {};

        auto pitchAndRollEnd = TrackPitchAndRollEnd(trackType);
        TileElement* tileElement = MapGetTrackElementAtOfTypeSeq(TrackLocation, trackType, 0);

        if (tileElement == nullptr)
        {
            return false;
        }

        if (trackType == TrackElemType::cableLiftHill && this == gCurrentVehicle)
        {
            _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_11;
        }

        if (tileElement->asTrack()->isBlockStart())
        {
            if (next_vehicle_on_train.IsNull())
            {
                SetBrakeClosedMultiTile(*tileElement->asTrack(), TrackLocation, true);
                if (trackTypeIsBlockBrakes(trackType) || trackType == TrackElemType::endStation)
                {
                    if (!rideEntry.Cars[0].flags.has(CarEntryFlag::isPowered))
                    {
                        Play3D(SoundId::blockBrakeRelease, TrackLocation);
                    }
                }
                MapInvalidateElement(TrackLocation, tileElement);
                blockBrakesOpenPreviousSection(curRide, TrackLocation, tileElement);
                if (trackTypeIsBlockBrakes(trackType))
                {
                    BlockBrakeSetLinkedBrakesClosed(TrackLocation, *tileElement->asTrack(), true);
                }
            }
        }

        // Change from original: this used to check if the vehicle allowed doors.
        UpdateSceneryDoor();

        bool isGoingBack = false;
        switch (TrackSubposition)
        {
            case VehicleTrackSubposition::chairliftGoingBack:
            case VehicleTrackSubposition::chairliftEndBullwheel:
                TrackSubposition = VehicleTrackSubposition::chairliftGoingBack;
                isGoingBack = true;
                break;
            case VehicleTrackSubposition::chairliftStartBullwheel:
                TrackSubposition = VehicleTrackSubposition::chairliftGoingOut;
                break;
            case VehicleTrackSubposition::goKartsMovingToRightLane:
                TrackSubposition = VehicleTrackSubposition::goKartsRightLane;
                break;
            case VehicleTrackSubposition::goKartsMovingToLeftLane:
                TrackSubposition = VehicleTrackSubposition::goKartsLeftLane;
                break;
            default:
                break;
        }

        if (isGoingBack)
        {
            TrackBeginEnd trackBeginEnd;
            if (!trackBlockGetPrevious({ TrackLocation, tileElement }, &trackBeginEnd))
            {
                return false;
            }
            location.x = trackBeginEnd.begin_x;
            location.y = trackBeginEnd.begin_y;
            location.z = trackBeginEnd.begin_z;
            location.direction = trackBeginEnd.begin_direction;
            tileElement = trackBeginEnd.begin_element;
        }
        else
        {
            {
                int32_t curZ, direction;
                CoordsXYE xyElement = { TrackLocation, tileElement };
                if (!trackBlockGetNext(&xyElement, &xyElement, &curZ, &direction))
                {
                    return false;
                }
                tileElement = xyElement.element;
                location = { xyElement, curZ, static_cast<Direction>(direction) };
            }
            if (tileElement->asTrack()->getTrackType() == TrackElemType::leftReverser
                || tileElement->asTrack()->getTrackType() == TrackElemType::rightReverser)
            {
                if (IsHead() && velocity <= 3.0_mph)
                {
                    velocity = 0;
                }
            }

            if (PitchAndRollStart(flags.has(VehicleFlag::carIsInverted), tileElement) != pitchAndRollEnd)
            {
                return false;
            }

            // Update VehicleFlags::CarIsInverted flag
            const auto previousCarIsInverted = flags.has(VehicleFlag::carIsInverted);
            flags.unset(VehicleFlag::carIsInverted);
            {
                auto rideType = OpenRCT2::GetRide(tileElement->asTrack()->getRideIndex())->type;
                if (GetRideTypeDescriptor(rideType).flags.has(RtdFlag::hasInvertedVariant))
                {
                    if (tileElement->asTrack()->isInverted())
                    {
                        flags.set(VehicleFlag::carIsInverted);
                    }
                }
                if (previousCarIsInverted != flags.has(VehicleFlag::carIsInverted))
                {
                    EntityTweener::get().removeEntity(this);
                }
            }
        }

        const int32_t previousTrackHeight = TrackLocation.z;
        TrackLocation = location;

        // TODO check if getting the vehicle entry again is necessary
        const auto* carEntry = Entry();
        if (carEntry == nullptr)
        {
            return false;
        }
        if (carEntry->flags.has(CarEntryFlag::isGoKart) && TrackSubposition < VehicleTrackSubposition::goKartsMovingToRightLane)
        {
            trackType = tileElement->asTrack()->getTrackType();
            if (trackType == TrackElemType::flat || trackType == TrackElemType::leftQuarterTurn3Tiles
                || trackType == TrackElemType::rightQuarterTurn3Tiles || trackType == TrackElemType::leftQuarterTurn5Tiles
                || trackType == TrackElemType::rightQuarterTurn5Tiles || trackType == TrackElemType::leftEighthToDiag
                || trackType == TrackElemType::rightEighthToDiag || trackType == TrackElemType::leftEighthToOrthogonal
                || trackType == TrackElemType::rightEighthToOrthogonal || trackType == TrackElemType::diagFlat
                || trackType == TrackElemType::sBendLeft || trackType == TrackElemType::sBendRight
                || (curRide.flags.has(RideFlag::passStationNoStopping) && tileElement->asTrack()->isStation()))
            {
                goKartAttemptLaneSwitch();
            }
        }

        if (TrackSubposition >= VehicleTrackSubposition::chairliftGoingOut
            && TrackSubposition <= VehicleTrackSubposition::chairliftStartBullwheel)
        {
            TileCoordsXYZ curLocation{ TrackLocation };

            if (curLocation == curRide.chairliftBullwheelLocation[1])
            {
                TrackSubposition = VehicleTrackSubposition::chairliftEndBullwheel;
            }
            else if (curLocation == curRide.chairliftBullwheelLocation[0])
            {
                TrackSubposition = VehicleTrackSubposition::chairliftStartBullwheel;
            }
        }

        // Loc6DB500
        // Update VehicleFlags::OnLiftHill
        flags.unset(VehicleFlag::onLiftHill);
        if (tileElement->asTrack()->hasChain())
        {
            flags.set(VehicleFlag::onLiftHill);
        }

        trackType = tileElement->asTrack()->getTrackType();
        if (trackType != TrackElemType::brakes)
        {
            target_seat_rotation = tileElement->asTrack()->getSeatRotation();
        }
        SetTrackDirection(location.direction);
        SetTrackType(trackType);
        populateBrakeSpeed(TrackLocation, *tileElement->asTrack());
        if (flags.has(VehicleFlag::stoppedOnHoldingBrake) && vertical_drop_countdown <= 0)
        {
            flags.unset(VehicleFlag::stoppedOnHoldingBrake);
        }
        if (trackType == TrackElemType::onRidePhoto)
        {
            tileElement->asTrack()->setPhotoTimeout();
            MapAnimations::CreateTemporary(TrackLocation, MapAnimations::TemporaryType::onRidePhoto);
        }
        if (trackType == TrackElemType::rotationControlToggle)
        {
            flags.flip(VehicleFlag::spinningIsLocked);
        }
        // Change from original: this used to check if the vehicle allowed doors.
        UpdateSceneryDoorBackwards();
        UpdateLandscapeDoors(previousTrackHeight);

        return true;
    }

    /**
     *
     *  rct2: 0x006DAEB9
     */
    bool Vehicle::trackMotionForwards(const CarEntry* carEntry, const Ride& curRide, const RideObjectEntry& rideEntry)
    {
        EntityId otherVehicleIndex = EntityId::GetNull();
        while (true)
        {
            auto trackType = GetTrackType();
            if (trackType == TrackElemType::heartLineTransferUp || trackType == TrackElemType::heartLineTransferDown)
            {
                if (track_progress == 80)
                {
                    vehicle_type ^= 1;
                    carEntry = Entry();
                }
                if (_vehicleVelocity >= 0x40000)
                {
                    acceleration = -_vehicleVelocity * 8;
                }
                else if (_vehicleVelocity < 0x20000)
                {
                    acceleration = 0x50000;
                }
            }
            else if (trackTypeIsBrakes(trackType))
            {
                if (!curRide.hasFailingBrakes())
                {
                    auto brakeSpeed = chooseBrakeSpeed() << kTrackSpeedShiftAmount;

                    if ((brakeSpeed) < _vehicleVelocity)
                    {
                        acceleration = -_vehicleVelocity * 16;
                    }
                    else if (!(getGameState().currentTicks & 0x0F))
                    {
                        if (_vehicleBrakeSoundTimeout == 0)
                        {
                            _vehicleBrakeSoundTimeout++;
                            Play3D(SoundId::brakeRelease, { x, y, z });
                        }
                    }
                }
            }
            else if (trackTypeIsBooster(trackType))
            {
                auto boosterSpeed = GetUnifiedBoosterSpeed(curRide.type, brake_speed) << kTrackSpeedShiftAmount;
                if (boosterSpeed > _vehicleVelocity)
                {
                    acceleration = GetRideTypeDescriptor(curRide.type).LegacyBoosterSettings.BoosterAcceleration
                        << kBoosterAccelerationShiftAmount;
                }
            }
            else if (rideEntry.flags.has(RideEntryFlag::riderControlsSpeed) && num_peeps > 0)
            {
                acceleration += CalculateRiderBraking();
            }

            if ((trackType == TrackElemType::flat && curRide.getRideTypeDescriptor().flags.has(RtdFlag::hasLsmBehaviourOnFlat))
                || (trackType == TrackElemType::poweredLift))
            {
                acceleration = GetRideTypeDescriptor(curRide.type).LegacyBoosterSettings.PoweredLiftAcceleration
                    << kBoosterAccelerationShiftAmount;
            }
            if (trackType == TrackElemType::brakeForDrop)
            {
                if (IsHead())
                {
                    if (!flags.has(VehicleFlag::stoppedOnHoldingBrake))
                    {
                        if (track_progress >= 8)
                        {
                            acceleration = -_vehicleVelocity * 16;
                            if (track_progress >= 24)
                            {
                                flags.set(VehicleFlag::stoppedOnHoldingBrake);
                                vertical_drop_countdown = 90;
                            }
                        }
                    }
                }
            }
            if (trackType == TrackElemType::logFlumeReverser)
            {
                if (track_progress != 16 || velocity < 4.0_mph)
                {
                    if (track_progress == 32)
                    {
                        vehicle_type = carEntry->reversedCarIndex;
                        carEntry = Entry();
                    }
                }
                else
                {
                    track_progress += 17;
                }
            }

            uint16_t newTrackProgress = track_progress + 1;

            // Track Total Progress is in the two bytes before the move info list
            uint16_t trackTotalProgress = GetTrackProgress();
            if (newTrackProgress >= trackTotalProgress)
            {
                UpdateCrossings();

                if (!trackMotionForwardsGetNewTrack(trackType, curRide, rideEntry))
                {
                    _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_5;
                    _vehicleRemainingDistance -= remaining_distance + 1;
                    remaining_distance = -1;
                    return false;
                }
                newTrackProgress = 0;
            }

            track_progress = newTrackProgress;
            handleWaterSplash();

            // Loc6DB706
            const auto moveInfo = GetMoveInfo();
            trackType = GetTrackType();
            {
                auto nextVehiclePosition = TrackLocation
                    + CoordsXYZ{ moveInfo->x, moveInfo->y,
                                 moveInfo->z + GetRideTypeDescriptor(curRide.type).Heights.VehicleZOffset };

                bool useReverserDistance = TrackSubposition == VehicleTrackSubposition::reverserRCFrontBogie
                    && (trackType == TrackElemType::leftReverser || trackType == TrackElemType::rightReverser)
                    && track_progress >= 30 && track_progress <= 66;

                // Loc6DB8A5
                remaining_distance -= Geometry::getTranslationDistance(
                    nextVehiclePosition - _vehicleCurPosition, useReverserDistance);

                if (TrackSubposition == VehicleTrackSubposition::reverserRCRearBogie
                    && (trackType == TrackElemType::leftReverser || trackType == TrackElemType::rightReverser)
                    && track_progress == 96)
                {
                    ReverseReverserCar();

                    const VehicleInfo* moveInfo2 = GetMoveInfo();
                    nextVehiclePosition.x = x + moveInfo2->x;
                    nextVehiclePosition.y = y + moveInfo2->y;
                }

                _vehicleCurPosition = nextVehiclePosition;
                orientation = moveInfo->yaw;
                roll = moveInfo->roll;
                pitch = moveInfo->pitch;

                if (carEntry->flags.has(CarEntryFlag::useWoodenWildMouseSwing) && moveInfo->pitch != VehiclePitch::flat)
                {
                    SwingSprite = 0;
                    SwingPosition = 0;
                    SwingSpeed = 0;
                }

                // this == frontVehicle
                if (this == _vehicleFrontVehicle)
                {
                    if (_vehicleVelocity >= 0)
                    {
                        otherVehicleIndex = prev_vehicle_on_ride;
                        if (UpdateMotionCollisionDetection(nextVehiclePosition, &otherVehicleIndex))
                        {
                            _vehicleRemainingDistance -= remaining_distance + 1;
                            remaining_distance = -1;

                            // Might need to be bp rather than this, but hopefully not
                            auto otherVeh = getGameState().entities.getEntity<Vehicle>(otherVehicleIndex);
                            if (otherVeh == nullptr)
                            {
                                // This can never happen as prev_vehicle_on_ride will always be set to a vehicle
                                LOG_ERROR("Failed to get next vehicle during update!");
                                return true;
                            }
                            auto head = otherVeh->TrainHead();

                            auto velocityDelta = abs(velocity - head->velocity);
                            if (!rideEntry.flags.has(RideEntryFlag::disableCollisionCrashes))
                            {
                                if (velocityDelta > 14.0_mph)
                                {
                                    if (!carEntry->flags.has(CarEntryFlag::useBoatHireCollisionDetection))
                                    {
                                        _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_COLLISION;
                                    }
                                }
                            }

                            if (carEntry->flags.has(CarEntryFlag::isGoKart))
                            {
                                velocity -= velocity >> 2;
                            }
                            else
                            {
                                int32_t newHeadVelocity = velocity >> 1;
                                velocity = head->velocity >> 1;
                                head->velocity = newHeadVelocity;
                            }
                            _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_1;
                            return false;
                        }
                    }
                }
            }

            // Loc6DB928
            if (remaining_distance < 0x368A)
            {
                return true;
            }

            acceleration += Geometry::getAccelerationFromPitch(pitch);
            _vehicleSubpositionsMoved++;
        }
    }

    static PitchAndRoll getPitchAndRollEnd(
        const Ride& curRide, bool useInvertedSprites, TrackElemType trackType, TileElement* tileElement)
    {
        bool isInverted = useInvertedSprites ^ tileElement->asTrack()->isInverted();
        const auto& ted = GetTrackElementDescriptor(trackType);
        return { ted.definition.pitchEnd, TrackGetActualBank2(curRide.type, isInverted, ted.definition.rollEnd) };
    }

    /**
     *
     *  rct2: 0x006DBAA6
     */
    bool Vehicle::trackMotionBackwardsGetNewTrack(TrackElemType trackType, const Ride& curRide, uint16_t* progress)
    {
        auto pitchAndRollStart = TrackPitchAndRollStart(trackType);
        TileElement* tileElement = MapGetTrackElementAtOfTypeSeq(TrackLocation, trackType, 0);

        if (tileElement == nullptr)
            return false;

        bool nextTileBackwards = true;
        int32_t direction = 0;
        // Loc6DBB08:;
        auto trackPos = CoordsXYZ{ TrackLocation.x, TrackLocation.y, 0 };

        switch (TrackSubposition)
        {
            case VehicleTrackSubposition::chairliftEndBullwheel:
                TrackSubposition = VehicleTrackSubposition::chairliftGoingOut;
                break;
            case VehicleTrackSubposition::goKartsMovingToRightLane:
                TrackSubposition = VehicleTrackSubposition::goKartsLeftLane;
                break;
            case VehicleTrackSubposition::goKartsMovingToLeftLane:
                TrackSubposition = VehicleTrackSubposition::goKartsRightLane;
                break;
            case VehicleTrackSubposition::chairliftGoingBack:
            case VehicleTrackSubposition::chairliftStartBullwheel:
                TrackSubposition = VehicleTrackSubposition::chairliftGoingBack;
                nextTileBackwards = false;
                break;
            default:
                break;
        }

        if (nextTileBackwards)
        {
            // Loc6DBB7E:;
            TrackBeginEnd trackBeginEnd;
            if (!trackBlockGetPrevious({ trackPos, tileElement }, &trackBeginEnd))
            {
                return false;
            }
            tileElement = trackBeginEnd.begin_element;

            trackType = tileElement->asTrack()->getTrackType();
            if (trackType == TrackElemType::leftReverser || trackType == TrackElemType::rightReverser)
            {
                return false;
            }

            if (getPitchAndRollEnd(curRide, flags.has(VehicleFlag::carIsInverted), trackType, tileElement) != pitchAndRollStart)
            {
                return false;
            }

            // Update VehicleFlags::CarIsInverted
            const auto previousCarIsInverted = flags.has(VehicleFlag::carIsInverted);
            flags.unset(VehicleFlag::carIsInverted);
            if (GetRideTypeDescriptor(curRide.type).flags.has(RtdFlag::hasInvertedVariant))
            {
                if (tileElement->asTrack()->isInverted())
                {
                    flags.set(VehicleFlag::carIsInverted);
                }
                if (previousCarIsInverted != flags.has(VehicleFlag::carIsInverted))
                {
                    EntityTweener::get().removeEntity(this);
                }
            }

            trackPos = { trackBeginEnd.begin_x, trackBeginEnd.begin_y, trackBeginEnd.begin_z };
            direction = trackBeginEnd.begin_direction;
        }
        else
        {
            // Loc6DBB4F:;
            CoordsXYE input;
            CoordsXYE output;
            int32_t outputZ{};

            input.x = trackPos.x;
            input.y = trackPos.y;
            input.element = tileElement;
            if (!trackBlockGetNext(&input, &output, &outputZ, &direction))
            {
                return false;
            }
            tileElement = output.element;
            trackPos = { output, outputZ };
        }

        // Loc6DBC3B:
        TrackLocation = trackPos;

        if (TrackSubposition >= VehicleTrackSubposition::chairliftGoingOut
            && TrackSubposition <= VehicleTrackSubposition::chairliftStartBullwheel)
        {
            TileCoordsXYZ curLocation{ TrackLocation };

            if (curLocation == curRide.chairliftBullwheelLocation[1])
            {
                TrackSubposition = VehicleTrackSubposition::chairliftEndBullwheel;
            }
            else if (curLocation == curRide.chairliftBullwheelLocation[0])
            {
                TrackSubposition = VehicleTrackSubposition::chairliftStartBullwheel;
            }
        }

        if (tileElement->asTrack()->hasChain())
        {
            if (_vehicleVelocity < 0)
            {
                if (next_vehicle_on_train.IsNull())
                {
                    trackType = tileElement->asTrack()->getTrackType();
                    const auto& ted = GetTrackElementDescriptor(trackType);
                    if (!ted.flags.has(TrackElementFlag::down))
                    {
                        _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_9;
                    }
                }
                flags.set(VehicleFlag::onLiftHill);
            }
        }
        else
        {
            if (flags.has(VehicleFlag::onLiftHill))
            {
                flags.unset(VehicleFlag::onLiftHill);
                if (next_vehicle_on_train.IsNull())
                {
                    if (_vehicleVelocity < 0)
                    {
                        _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_8;
                    }
                }
            }
        }

        trackType = tileElement->asTrack()->getTrackType();
        if (trackType != TrackElemType::brakes)
        {
            target_seat_rotation = tileElement->asTrack()->getSeatRotation();
        }
        direction &= 3;
        SetTrackType(trackType);
        SetTrackDirection(direction);
        populateBrakeSpeed(TrackLocation, *tileElement->asTrack());
        if (flags.has(VehicleFlag::stoppedOnHoldingBrake) && vertical_drop_countdown <= 0)
        {
            flags.unset(VehicleFlag::stoppedOnHoldingBrake);
        }
        // There are two bytes before the move info list
        uint16_t trackTotalProgress = GetTrackProgress();
        *progress = trackTotalProgress - 1;
        return true;
    }

    /**
     *
     *  rct2: 0x006DBA33
     */
    bool Vehicle::trackMotionBackwards(const CarEntry* carEntry, const Ride& curRide, const RideObjectEntry& rideEntry)
    {
        EntityId otherVehicleIndex = EntityId::GetNull();

        while (true)
        {
            auto trackType = GetTrackType();
            if (trackType == TrackElemType::flat && curRide.getRideTypeDescriptor().flags.has(RtdFlag::hasLsmBehaviourOnFlat))
            {
                int32_t unkVelocity = _vehicleVelocity;
                if (unkVelocity < -524288)
                {
                    unkVelocity = abs(unkVelocity);
                    acceleration = unkVelocity * 2;
                }
            }

            if (trackTypeIsBrakes(trackType))
            {
                auto brakeSpeed = chooseBrakeSpeed();

                if (-(brakeSpeed << kTrackSpeedShiftAmount) > _vehicleVelocity)
                {
                    acceleration = _vehicleVelocity * -16;
                }
            }

            uint16_t newTrackProgress = track_progress - 1;
            if (newTrackProgress == 0xFFFF)
            {
                UpdateCrossings();

                if (!trackMotionBackwardsGetNewTrack(trackType, curRide, &newTrackProgress))
                {
                    _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_5;
                    _vehicleRemainingDistance -= remaining_distance - 0x368A;
                    remaining_distance = 0x368A;
                    return false;
                }
            }

            // Loc6DBD42
            track_progress = newTrackProgress;
            {
                const VehicleInfo* moveInfo = GetMoveInfo();
                auto nextVehiclePosition = TrackLocation
                    + CoordsXYZ{ moveInfo->x, moveInfo->y,
                                 moveInfo->z + GetRideTypeDescriptor(curRide.type).Heights.VehicleZOffset };

                remaining_distance += Geometry::getTranslationDistance(nextVehiclePosition - _vehicleCurPosition, false);

                _vehicleCurPosition = nextVehiclePosition;
                orientation = moveInfo->yaw;
                roll = moveInfo->roll;
                pitch = moveInfo->pitch;

                if (carEntry->flags.has(CarEntryFlag::useWoodenWildMouseSwing) && pitch != VehiclePitch::flat)
                {
                    SwingSprite = 0;
                    SwingPosition = 0;
                    SwingSpeed = 0;
                }

                if (this == _vehicleFrontVehicle)
                {
                    if (_vehicleVelocity < 0)
                    {
                        otherVehicleIndex = next_vehicle_on_ride;
                        if (UpdateMotionCollisionDetection(nextVehiclePosition, &otherVehicleIndex))
                        {
                            _vehicleRemainingDistance -= remaining_distance - 0x368A;
                            remaining_distance = 0x368A;

                            Vehicle* v3 = getGameState().entities.getEntity<Vehicle>(otherVehicleIndex);
                            Vehicle* v4 = gCurrentVehicle;
                            if (v3 == nullptr)
                            {
                                return false;
                            }

                            if (!rideEntry.flags.has(RideEntryFlag::disableCollisionCrashes))
                            {
                                if (abs(v4->velocity - v3->velocity) > 14.0_mph)
                                {
                                    if (!carEntry->flags.has(CarEntryFlag::useBoatHireCollisionDetection))
                                    {
                                        _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_COLLISION;
                                    }
                                }
                            }

                            if (carEntry->flags.has(CarEntryFlag::isGoKart))
                            {
                                velocity -= velocity >> 2;
                                _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_2;
                            }
                            else
                            {
                                int32_t v3Velocity = v3->velocity;
                                v3->velocity = v4->velocity >> 1;
                                v4->velocity = v3Velocity >> 1;
                                _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_2;
                            }

                            return false;
                        }
                    }
                }
            }

            // Loc6DBE3F
            if (remaining_distance >= 0)
            {
                return true;
            }
            acceleration += Geometry::getAccelerationFromPitch(pitch);
            _vehicleSubpositionsMoved++;
        }
    }

    /**
     *
     *  rct2: 0x006DC1E4
     */
    static uint8_t poweredCarCurveSpeed(TrackElemType trackType, VehicleTrackSubposition trackSubposition, uint8_t speed)
    {
        enum
        {
            FULL_SPEED,
            THREE_QUARTER_SPEED,
            HALF_SPEED
        };

        uint8_t speedModifier = FULL_SPEED;

        if (trackType == TrackElemType::leftQuarterTurn1Tile)
        {
            speedModifier = (trackSubposition == VehicleTrackSubposition::goKartsLeftLane) ? HALF_SPEED : THREE_QUARTER_SPEED;
        }
        else if (trackType == TrackElemType::rightQuarterTurn1Tile)
        {
            speedModifier = (trackSubposition == VehicleTrackSubposition::goKartsRightLane) ? HALF_SPEED : THREE_QUARTER_SPEED;
        }

        switch (speedModifier)
        {
            case HALF_SPEED:
                return speed >> 1;
            case THREE_QUARTER_SPEED:
                return speed - (speed >> 2);
        }
        return speed;
    }

    int32_t Vehicle::getPoweredCarAcceleration(const CarEntry* carEntry, uint32_t totalMass, const int32_t curAcceleration)
    {
        if (carEntry->flags.has(CarEntryFlag::isPoweredRideWithUnrestrictedGravity))
        {
            if (velocity > (speed * 0x4000))
            {
                // Same code as none powered rides
                if (curAcceleration <= 0 && curAcceleration >= -500 && velocity <= 0.5_mph)
                {
                    return curAcceleration + 400;
                }
                return curAcceleration;
            }
        }
        uint8_t modifiedSpeed = poweredCarCurveSpeed(GetTrackType(), TrackSubposition, speed);
        int32_t poweredAcceleration = modifiedSpeed << 14;
        int32_t quarterForce = (modifiedSpeed * totalMass) >> 2;
        if (flags.has(VehicleFlag::poweredCarInReverse))
        {
            poweredAcceleration = -poweredAcceleration;
        }
        poweredAcceleration -= velocity;
        poweredAcceleration *= powered_acceleration << 1;
        if (quarterForce != 0)
        {
            poweredAcceleration /= quarterForce;
        }

        if (carEntry->flags.has(CarEntryFlag::isLift))
        {
            poweredAcceleration *= 4;
        }

        if (carEntry->flags.has(CarEntryFlag::isWaterRide))
        {
            if (poweredAcceleration < 0)
            {
                poweredAcceleration >>= 4;
            }

            if (carEntry->flags.has(CarEntryFlag::hasSpinning))
            {
                spin_speed = std::clamp(spin_speed, kVehicleMinSpinSpeedWaterRide, kVehicleMaxSpinSpeedWaterRide);
            }

            if (pitch != VehiclePitch::flat)
            {
                if (poweredAcceleration < 0)
                {
                    poweredAcceleration = 0;
                }

                if (carEntry->flags.has(CarEntryFlag::hasSpinning))
                {
                    // If the vehicle is on the up slope kill the spin speedModifier
                    if (pitch == VehiclePitch::up25)
                    {
                        spin_speed = 0;
                    }
                }
                return curAcceleration + poweredAcceleration;
            }
        }

        if (std::abs(velocity) <= 1.0_mph)
        {
            return poweredAcceleration;
        }

        return curAcceleration + poweredAcceleration;
    }

    void Vehicle::updateTrackMotionCar(
        Vehicle& car, const Ride& curRide, const RideObjectEntry& rideEntry, const CarEntry* carEntry)
    {
        if (carEntry->flags.has(CarEntryFlag::hasSwinging))
        {
            car.UpdateSwingingCar();
        }
        if (carEntry->flags.has(CarEntryFlag::hasSpinning))
        {
            car.UpdateSpinningCar();
        }
        if (carEntry->flags.hasAny(CarEntryFlag::hasVehicleAnimation, CarEntryFlag::hasRiderAnimation))
        {
            car.UpdateAdditionalAnimation();
        }
        car.acceleration = Geometry::getAccelerationFromPitch(car.pitch);
        _vehicleSubpositionsMoved = 1;

        if (!car.flags.has(VehicleFlag::moveSingleCar))
        {
            car.remaining_distance += _vehicleRemainingDistance;
        }

        car.sound2_flags &= ~VEHICLE_SOUND2_FLAGS_LIFT_HILL;
        _vehicleCurPosition.x = car.x;
        _vehicleCurPosition.y = car.y;
        _vehicleCurPosition.z = car.z;
        car.invalidate();

        while (true)
        {
            if (car.remaining_distance < 0)
            {
                // Backward loop
                if (car.trackMotionBackwards(carEntry, curRide, rideEntry))
                {
                    break;
                }

                if (car.remaining_distance < 0x368A)
                {
                    break;
                }
                car.acceleration += Geometry::getAccelerationFromPitch(car.pitch);
                _vehicleSubpositionsMoved++;
                continue;
            }
            if (car.remaining_distance < 0x368A)
            {
                // Location found
                return;
            }
            if (car.trackMotionForwards(carEntry, curRide, rideEntry))
            {
                break;
            }

            if (car.remaining_distance >= 0)
            {
                break;
            }
            car.acceleration = Geometry::getAccelerationFromPitch(car.pitch);
            _vehicleSubpositionsMoved++;
        }
        // Loc6DBF20
        car.moveTo(_vehicleCurPosition);
    }

    /**
     *
     *  rct2: 0x006DAB4C
     */
    int32_t Vehicle::updateTrackMotionTrain(int32_t* outStation)
    {
        auto curRide = GetRide();
        if (curRide == nullptr)
            return 0;

        const auto* rideEntry = GetRideEntry();
        const auto* carEntry = Entry();

        if (carEntry == nullptr)
        {
            return 0;
        }

        if (carEntry->flags.has(CarEntryFlag::isMiniGolf))
        {
            return UpdateTrackMotionMiniGolf(outStation);
        }

        _vehicleBrakeSoundTimeout = 0;
        gCurrentVehicle = this;
        _vehicleMotionTrackFlags = 0;
        _vehicleStationIndex = StationIndex::GetNull();

        upstopCheck();
        handleBlockBrake();
        updateVelocity();

        Vehicle* vehicle = this;
        if (_vehicleVelocity < 0 && !vehicle->flags.has(VehicleFlag::moveSingleCar))
        {
            vehicle = vehicle->TrainTail();
        }
        // This will be the front vehicle even when traveling
        // backwards.
        _vehicleFrontVehicle = vehicle;

        auto spriteId = vehicle->id;
        while (!spriteId.IsNull())
        {
            Vehicle* car = getGameState().entities.getEntity<Vehicle>(spriteId);
            if (car == nullptr)
            {
                break;
            }
            carEntry = car->Entry();
            if (carEntry != nullptr)
            {
                updateTrackMotionCar(*car, *curRide, *rideEntry, carEntry);
            }

            car->findStationStopPoint();

            // Loc6DC0F7
            if (car->flags.has(VehicleFlag::onLiftHill))
            {
                _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_ON_LIFT_HILL;
            }
            if (car->flags.has(VehicleFlag::moveSingleCar))
            {
                if (outStation != nullptr)
                    *outStation = _vehicleStationIndex.ToUnderlying();
                return _vehicleMotionTrackFlags;
            }
            if (_vehicleVelocity >= 0)
            {
                spriteId = car->next_vehicle_on_train;
            }
            else
            {
                if (car == gCurrentVehicle)
                {
                    break;
                }
                spriteId = car->prev_vehicle_on_ride;
            }
        }
        // Loc6DC144
        vehicle = gCurrentVehicle;

        carEntry = vehicle->Entry();
        // eax
        int32_t totalAcceleration = 0;
        // ebp
        int32_t totalMass = 0;
        // ebx
        int32_t numVehicles = 0;

        for (; vehicle != nullptr; vehicle = getGameState().entities.getEntity<Vehicle>(vehicle->next_vehicle_on_train))
        {
            numVehicles++;
            totalMass += vehicle->mass;
            totalAcceleration += vehicle->acceleration;
        }

        vehicle = gCurrentVehicle;
        int32_t newAcceleration = (totalAcceleration / numVehicles) * 21;
        if (newAcceleration < 0)
        {
            newAcceleration += 511;
        }
        newAcceleration >>= 9;

        int32_t curAcceleration = newAcceleration;
        curAcceleration -= vehicle->velocity / 4096;
        curAcceleration -= GetAccelerationDecrease2(vehicle->velocity, totalMass);

        if (carEntry->flags.has(CarEntryFlag::isPowered))
        {
            curAcceleration = vehicle->getPoweredCarAcceleration(carEntry, totalMass, curAcceleration);
        }
        else if (curAcceleration <= 0 && curAcceleration >= -500)
        {
            // Probably moving slowly on a flat track piece, low rolling resistance and drag.

            if (vehicle->velocity <= 0.5_mph && vehicle->velocity >= 0)
            {
                // Vehicle is creeping forwards very slowly (less than ~2km/h), boost speed a bit.
                curAcceleration += 400;
            }
        }

        if (vehicle->GetTrackType() == TrackElemType::waterSplash)
        {
            if (vehicle->track_progress >= 48 && vehicle->track_progress <= 128)
            {
                curAcceleration -= vehicle->velocity >> 6;
            }
        }

        if (rideEntry->flags.has(RideEntryFlag::coveredTrackIsWaterChannel))
        {
            if (vehicle->IsHead())
            {
                if (vehicle->IsOnCoveredTrack())
                {
                    if (vehicle->velocity > 2.0_mph)
                    {
                        curAcceleration -= vehicle->velocity >> 6;
                    }
                }
            }
        }

        vehicle->acceleration = curAcceleration;

        // hook_setreturnregisters(&regs);
        if (outStation != nullptr)
            *outStation = _vehicleStationIndex.ToUnderlying();
        return _vehicleMotionTrackFlags;
    }
} // namespace OpenRCT2
