Skip to content

Commit

Permalink
Feature: Vehicle spin controller (#123)
Browse files Browse the repository at this point in the history
* Fix breaking changes for new openrct2.d.ts

* Add seat spin widget to control vehicle spin value

* spin control review fixes
  • Loading branch information
guysv authored Dec 7, 2024
1 parent 3ca540b commit 2df06ce
Show file tree
Hide file tree
Showing 8 changed files with 64 additions and 23 deletions.
12 changes: 11 additions & 1 deletion src/objects/rideVehicle.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CAR_ENTRY_FLAG_SPINNING } from "../utilities/game";
import * as Log from "../utilities/logger";
import { isNumber } from "../utilities/type";
import { getRideObject } from "./rideType";
Expand Down Expand Up @@ -112,4 +113,13 @@ export class RideVehicle
const type = this._type();
return !type || isPowered(type);
}
}

/**
* Returns true if this car is considered a "spinning" ride type.
*/
_isSpinning(): boolean
{
const type = this._type();
return !!type && ((type.flags & CAR_ENTRY_FLAG_SPINNING) !== 0);
}
}
4 changes: 2 additions & 2 deletions src/services/spacingEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export function getDistanceFromProgress(car: Car, trackProgress: number): number
? new ForwardIterator(trackProgress, currentProgress)
: new BackwardIterator(abs(trackProgress), currentProgress);

let trackPosition = currentTrackLocation;
let trackPosition: CoordsXYZD = currentTrackLocation;
let trackDistances = getTrackDistances(iteratorSegment, subposition, trackPosition.direction);
subpositionIterator._setInitialDistanceFromCarRemainingDistance(car.remainingDistance);

Expand Down Expand Up @@ -371,4 +371,4 @@ class BackwardIterator extends SubpositionIterator
nextTile.z + nextTrack._endZ
);
}
}
}
14 changes: 12 additions & 2 deletions src/services/vehicleEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const execute = register<UpdateVehicleSettingArgs>("rve-update-car", updateVehic
type VehicleUpdateKeys
= "rideObject" | "vehicleObject" | "isReversed" | "trackProgress" | "spacing"
| "numSeats" | "mass" | "poweredAcceleration" | "poweredMaxSpeed" | "x" | "y" | "z"
| "body" | "trim" | "tertiary";
| "spin" | "body" | "trim" | "tertiary";

const
rideTypeKey = "rideObject",
Expand All @@ -27,6 +27,7 @@ const
xPosition = "x",
yPosition = "y",
zPosition = "z",
spinKey = "spin",
primaryColour = "body",
secondaryColour = "trim",
tertiaryColour = "tertiary";
Expand Down Expand Up @@ -153,6 +154,14 @@ export function setPositionZ(vehicles: VehicleSpan[], z: number): void
updateValue(vehicles, zPosition, z);
}

/**
* Sets the z position for this vehicle.
*/
export function setSpin(vehicles: VehicleSpan[], spin: number): void
{
updateValue(vehicles, spinKey, spin);
}


/**
* Arguments for updating a single key in a vehicle object.
Expand Down Expand Up @@ -223,6 +232,7 @@ function updateVehicleSetting(args: UpdateVehicleSettingArgs): void
case massKey:
case poweredAccelerationKey:
case poweredMaxSpeedKey:
case spinKey:
{
callback = (car): void =>
{
Expand Down Expand Up @@ -278,4 +288,4 @@ function updateVehicleSetting(args: UpdateVehicleSettingArgs): void
}

forEachVehicle(targets, callback);
}
}
2 changes: 1 addition & 1 deletion src/services/vehiclePicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export function toggleVehiclePicker(isPressed: boolean, onPick: (car: Car) => vo
/**
* Finds a car within a certain range of the selected tile element.
*/
function findCarNearbyTileElement(coords: CoordsXYZ, elementIdx: number): Car | undefined
function findCarNearbyTileElement(coords: CoordsXY, elementIdx: number): Car | undefined
{
const element = getTileElement(coords.x, coords.y, elementIdx);
const entitiesOnTile = map.getAllEntitiesOnTile("car", coords);
Expand Down
14 changes: 12 additions & 2 deletions src/ui/mainWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { RideVehicleVariant, VehicleVisibility } from "../objects/rideVehicleVar
import { invoke, refreshRide } from "../services/events";
import { applyToTargets, CopyFilter, getTargets, getVehicleSettings } from "../services/vehicleCopier";
import { dragToolId, toggleVehicleDragger } from "../services/vehicleDragger";
import { changeSpacing, changeTrackProgress, setMass, setPositionX, setPositionY, setPositionZ, setPoweredAcceleration, setPoweredMaximumSpeed, setPrimaryColour, setReversed, setRideType, setSeatCount, setSecondaryColour, setTertiaryColour, setVariant } from "../services/vehicleEditor";
import { changeSpacing, changeTrackProgress, setMass, setPositionX, setPositionY, setPositionZ, setPoweredAcceleration, setPoweredMaximumSpeed, setPrimaryColour, setReversed, setRideType, setSeatCount, setSecondaryColour, setSpin, setTertiaryColour, setVariant } from "../services/vehicleEditor";
import { locate } from "../services/vehicleLocater";
import { pickerToolId, toggleVehiclePicker } from "../services/vehiclePicker";
import { cancelTools } from "../utilities/tools";
Expand Down Expand Up @@ -37,7 +37,7 @@ model._selectedRide.subscribe(r =>

export const mainWindow = window({
title,
width: { value: 500, min: 465, max: 560 },
width: { value: 515, min: 515, max: 560 },
height: 407,
spacing: 5,
onOpen: () => model._open(),
Expand Down Expand Up @@ -375,6 +375,16 @@ export const mainWindow = window({
value: model._z,
format: model._formatPosition,
onChange: (_, incr) => model._modifyVehicle(setPositionZ, incr)
}),
labelSpinner({
_label: { text: "Seat spin:" },
minimum: 0,
maximum: 255,
disabled: model._isSpinDisabled,
step: model._multiplier,
value: model._spin,
wrapMode: "clampThenWrap",
onChange: value => model._modifyVehicle(setSpin, value)
})
]
}),
Expand Down
3 changes: 3 additions & 0 deletions src/utilities/game.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Game const for vehicle flag to determine spin ability.
// see src/openrct2/ride/CarEntry.h:CAR_ENTRY_FLAG_SPINNING
export const CAR_ENTRY_FLAG_SPINNING = 1 << 18;
3 changes: 3 additions & 0 deletions src/viewmodels/vehicleViewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export class VehicleViewModel
readonly _x = store<number>(0);
readonly _y = store<number>(0);
readonly _z = store<number>(0);
readonly _spin = store<number>(0);

readonly _primaryColour = store<Colour>(0);
readonly _secondaryColour = store<Colour>(0);
Expand All @@ -51,6 +52,7 @@ export class VehicleViewModel
readonly _isPicking = store<boolean>(false);
readonly _isDragging = store<boolean>(false);
readonly _isEditDisabled = compute(this._selectedVehicle, v => !v);
readonly _isSpinDisabled = compute(this._selectedVehicle, v => !v || !v[0]._isSpinning());
readonly _isPositionDisabled = compute(this._isMoving, this._isEditDisabled, (m, e) => m || e);
readonly _formatPosition = (pos: number): string => (this._isEditDisabled.get() ? "Not available" : pos.toString());
readonly _multiplierIndex = store<number>(0);
Expand Down Expand Up @@ -277,6 +279,7 @@ export class VehicleViewModel
this._x.set(car.x);
this._y.set(car.y);
this._z.set(car.z);
this._spin.set(car.spin);

const train = this._selectedTrain.get();
if (train)
Expand Down
35 changes: 20 additions & 15 deletions tests/services/spacingEditor.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ class TrackPiece
{
return new TrackPiece({ x, y, z, direction }, this.type, this.subpositions);
}

toLocation(): CarTrackLocation
{
return { ...this.position, trackType: this.type };
}
}

const flatTrackPiece = new TrackPiece({ x: 0, y: 0, z: 0, direction: 0 }, 1,
Expand Down Expand Up @@ -326,7 +331,7 @@ const multiTurnTest = test.macro({
exec(t, startTrackPiece: number, startProgress: number, progress: number, expectedResult: number): void
{
const trackPieces = [ rightTurn1TrackPiece, rightTurn2TrackPiece, steepUpTrackPiece, steepTurnTrackPiece ];
const trackLocation: CoordsXYZD = { ...trackPieces[startTrackPiece].position, direction: 0 };
const trackLocation: CarTrackLocation = { ...trackPieces[startTrackPiece].position, direction: 0, trackType: 0 };
setupTrackIterator(trackPieces, startTrackPiece);
const car = Mock.car({ trackProgress: startProgress, trackLocation, remainingDistance: (progress >= 0) ? ForwardRemainingDistance : 0 });

Expand Down Expand Up @@ -405,8 +410,8 @@ test("Flat track: get spacing to preceding vehicle 1 step away", t =>
{
const mapMock = setupTrackIterator([ flatTrackPiece ]);
const train = createTrain(mapMock, [
{ trackProgress: 17, trackLocation: flatTrackPiece.position }, // front car
{ trackProgress: 16, trackLocation: flatTrackPiece.position }
{ trackProgress: 17, trackLocation: flatTrackPiece.toLocation() }, // front car
{ trackProgress: 16, trackLocation: flatTrackPiece.toLocation() }
]);

const spacing = getSpacingToPrecedingVehicle(train, train._at(1)._car(), 1);
Expand All @@ -419,8 +424,8 @@ test("Flat track: get spacing to preceding vehicle 10 step away", t =>
{
const mapMock = setupTrackIterator([ flatTrackPiece ]);
const train = createTrain(mapMock, [
{ trackProgress: 17, trackLocation: flatTrackPiece.position }, // front car
{ trackProgress: 7, trackLocation: flatTrackPiece.position }
{ trackProgress: 17, trackLocation: flatTrackPiece.toLocation() }, // front car
{ trackProgress: 7, trackLocation: flatTrackPiece.toLocation() }
]);

const spacing = getSpacingToPrecedingVehicle(train, train._at(1)._car(), 1);
Expand All @@ -433,8 +438,8 @@ test("Flat track: get spacing to preceding vehicle 31 step away", t =>
{
const mapMock = setupTrackIterator([ flatTrackPiece ]);
const train = createTrain(mapMock, [
{ trackProgress: 31, trackLocation: flatTrackPiece.position }, // front car
{ trackProgress: 0, trackLocation: flatTrackPiece.position } // back car
{ trackProgress: 31, trackLocation: flatTrackPiece.toLocation() }, // front car
{ trackProgress: 0, trackLocation: flatTrackPiece.toLocation() } // back car
]);

const spacing = getSpacingToPrecedingVehicle(train, train._at(1)._car(), 1);
Expand All @@ -447,8 +452,8 @@ test("Flat track: get spacing to preceding vehicle is too far away", t =>
{
const mapMock = setupTrackIterator([ flatTrackPiece ]);
const train = createTrain(mapMock, [
{ trackProgress: 15, trackLocation: { x: 10, y: 10, z: 10, direction: 0 } }, // front car
{ trackProgress: 10, trackLocation: flatTrackPiece.position }
{ trackProgress: 15, trackLocation: { x: 10, y: 10, z: 10, direction: 0, trackType: 0 } }, // front car
{ trackProgress: 10, trackLocation: flatTrackPiece.toLocation()}
]);

const spacing = getSpacingToPrecedingVehicle(train, train._at(1)._car(), 1);
Expand All @@ -462,8 +467,8 @@ test("Two flat tracks: get spacing to next track piece by 1", t =>
const pieces = [ flatTrackPiece.copyTo(32, 64), flatTrackPiece.copyTo(32, 32) ];
const mapMock = setupTrackIterator(pieces);
const train = createTrain(mapMock, [
{ trackProgress: 0, trackLocation: pieces[1].position }, // front car
{ trackProgress: 31, trackLocation: pieces[0].position } // back car
{ trackProgress: 0, trackLocation: pieces[1].toLocation() }, // front car
{ trackProgress: 31, trackLocation: pieces[0].toLocation() } // back car
]);
const car = train._at(1)._car();

Expand All @@ -478,8 +483,8 @@ test("Two flat tracks: get spacing to next track piece by 10", t =>
const pieces = [ flatTrackPiece.copyTo(32, 64), flatTrackPiece.copyTo(32, 32) ];
const mapMock = setupTrackIterator(pieces);
const train = createTrain(mapMock, [
{ trackProgress: 3, trackLocation: pieces[1].position }, // front car
{ trackProgress: 25, trackLocation: pieces[0].position } // back car
{ trackProgress: 3, trackLocation: pieces[1].toLocation() }, // front car
{ trackProgress: 25, trackLocation: pieces[0].toLocation() } // back car
]);
const car = train._at(1)._car();

Expand All @@ -494,8 +499,8 @@ test("Three flat tracks: get spacing to next track piece by 50", t =>
const pieces = [ flatTrackPiece.copyTo(32, 96), flatTrackPiece.copyTo(32, 64), flatTrackPiece.copyTo(32, 32) ];
const mapMock = setupTrackIterator(pieces);
const train = createTrain(mapMock, [
{ trackProgress: 5, trackLocation: pieces[2].position }, // front car
{ trackProgress: 19, trackLocation: pieces[0].position } // back car
{ trackProgress: 5, trackLocation: pieces[2].toLocation() }, // front car
{ trackProgress: 19, trackLocation: pieces[0].toLocation() } // back car
]);
const car = train._at(1)._car();

Expand Down

0 comments on commit 2df06ce

Please sign in to comment.