Skip to content

Commit 2df06ce

Browse files
authored
Feature: Vehicle spin controller (#123)
* Fix breaking changes for new openrct2.d.ts * Add seat spin widget to control vehicle spin value * spin control review fixes
1 parent 3ca540b commit 2df06ce

File tree

8 files changed

+64
-23
lines changed

8 files changed

+64
-23
lines changed

src/objects/rideVehicle.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { CAR_ENTRY_FLAG_SPINNING } from "../utilities/game";
12
import * as Log from "../utilities/logger";
23
import { isNumber } from "../utilities/type";
34
import { getRideObject } from "./rideType";
@@ -112,4 +113,13 @@ export class RideVehicle
112113
const type = this._type();
113114
return !type || isPowered(type);
114115
}
115-
}
116+
117+
/**
118+
* Returns true if this car is considered a "spinning" ride type.
119+
*/
120+
_isSpinning(): boolean
121+
{
122+
const type = this._type();
123+
return !!type && ((type.flags & CAR_ENTRY_FLAG_SPINNING) !== 0);
124+
}
125+
}

src/services/spacingEditor.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export function getDistanceFromProgress(car: Car, trackProgress: number): number
4444
? new ForwardIterator(trackProgress, currentProgress)
4545
: new BackwardIterator(abs(trackProgress), currentProgress);
4646

47-
let trackPosition = currentTrackLocation;
47+
let trackPosition: CoordsXYZD = currentTrackLocation;
4848
let trackDistances = getTrackDistances(iteratorSegment, subposition, trackPosition.direction);
4949
subpositionIterator._setInitialDistanceFromCarRemainingDistance(car.remainingDistance);
5050

@@ -371,4 +371,4 @@ class BackwardIterator extends SubpositionIterator
371371
nextTile.z + nextTrack._endZ
372372
);
373373
}
374-
}
374+
}

src/services/vehicleEditor.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const execute = register<UpdateVehicleSettingArgs>("rve-update-car", updateVehic
1212
type VehicleUpdateKeys
1313
= "rideObject" | "vehicleObject" | "isReversed" | "trackProgress" | "spacing"
1414
| "numSeats" | "mass" | "poweredAcceleration" | "poweredMaxSpeed" | "x" | "y" | "z"
15-
| "body" | "trim" | "tertiary";
15+
| "spin" | "body" | "trim" | "tertiary";
1616

1717
const
1818
rideTypeKey = "rideObject",
@@ -27,6 +27,7 @@ const
2727
xPosition = "x",
2828
yPosition = "y",
2929
zPosition = "z",
30+
spinKey = "spin",
3031
primaryColour = "body",
3132
secondaryColour = "trim",
3233
tertiaryColour = "tertiary";
@@ -153,6 +154,14 @@ export function setPositionZ(vehicles: VehicleSpan[], z: number): void
153154
updateValue(vehicles, zPosition, z);
154155
}
155156

157+
/**
158+
* Sets the z position for this vehicle.
159+
*/
160+
export function setSpin(vehicles: VehicleSpan[], spin: number): void
161+
{
162+
updateValue(vehicles, spinKey, spin);
163+
}
164+
156165

157166
/**
158167
* Arguments for updating a single key in a vehicle object.
@@ -223,6 +232,7 @@ function updateVehicleSetting(args: UpdateVehicleSettingArgs): void
223232
case massKey:
224233
case poweredAccelerationKey:
225234
case poweredMaxSpeedKey:
235+
case spinKey:
226236
{
227237
callback = (car): void =>
228238
{
@@ -278,4 +288,4 @@ function updateVehicleSetting(args: UpdateVehicleSettingArgs): void
278288
}
279289

280290
forEachVehicle(targets, callback);
281-
}
291+
}

src/services/vehiclePicker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export function toggleVehiclePicker(isPressed: boolean, onPick: (car: Car) => vo
7373
/**
7474
* Finds a car within a certain range of the selected tile element.
7575
*/
76-
function findCarNearbyTileElement(coords: CoordsXYZ, elementIdx: number): Car | undefined
76+
function findCarNearbyTileElement(coords: CoordsXY, elementIdx: number): Car | undefined
7777
{
7878
const element = getTileElement(coords.x, coords.y, elementIdx);
7979
const entitiesOnTile = map.getAllEntitiesOnTile("car", coords);

src/ui/mainWindow.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { RideVehicleVariant, VehicleVisibility } from "../objects/rideVehicleVar
44
import { invoke, refreshRide } from "../services/events";
55
import { applyToTargets, CopyFilter, getTargets, getVehicleSettings } from "../services/vehicleCopier";
66
import { dragToolId, toggleVehicleDragger } from "../services/vehicleDragger";
7-
import { changeSpacing, changeTrackProgress, setMass, setPositionX, setPositionY, setPositionZ, setPoweredAcceleration, setPoweredMaximumSpeed, setPrimaryColour, setReversed, setRideType, setSeatCount, setSecondaryColour, setTertiaryColour, setVariant } from "../services/vehicleEditor";
7+
import { changeSpacing, changeTrackProgress, setMass, setPositionX, setPositionY, setPositionZ, setPoweredAcceleration, setPoweredMaximumSpeed, setPrimaryColour, setReversed, setRideType, setSeatCount, setSecondaryColour, setSpin, setTertiaryColour, setVariant } from "../services/vehicleEditor";
88
import { locate } from "../services/vehicleLocater";
99
import { pickerToolId, toggleVehiclePicker } from "../services/vehiclePicker";
1010
import { cancelTools } from "../utilities/tools";
@@ -37,7 +37,7 @@ model._selectedRide.subscribe(r =>
3737

3838
export const mainWindow = window({
3939
title,
40-
width: { value: 500, min: 465, max: 560 },
40+
width: { value: 515, min: 515, max: 560 },
4141
height: 407,
4242
spacing: 5,
4343
onOpen: () => model._open(),
@@ -375,6 +375,16 @@ export const mainWindow = window({
375375
value: model._z,
376376
format: model._formatPosition,
377377
onChange: (_, incr) => model._modifyVehicle(setPositionZ, incr)
378+
}),
379+
labelSpinner({
380+
_label: { text: "Seat spin:" },
381+
minimum: 0,
382+
maximum: 255,
383+
disabled: model._isSpinDisabled,
384+
step: model._multiplier,
385+
value: model._spin,
386+
wrapMode: "clampThenWrap",
387+
onChange: value => model._modifyVehicle(setSpin, value)
378388
})
379389
]
380390
}),

src/utilities/game.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Game const for vehicle flag to determine spin ability.
2+
// see src/openrct2/ride/CarEntry.h:CAR_ENTRY_FLAG_SPINNING
3+
export const CAR_ENTRY_FLAG_SPINNING = 1 << 18;

src/viewmodels/vehicleViewModel.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export class VehicleViewModel
4141
readonly _x = store<number>(0);
4242
readonly _y = store<number>(0);
4343
readonly _z = store<number>(0);
44+
readonly _spin = store<number>(0);
4445

4546
readonly _primaryColour = store<Colour>(0);
4647
readonly _secondaryColour = store<Colour>(0);
@@ -51,6 +52,7 @@ export class VehicleViewModel
5152
readonly _isPicking = store<boolean>(false);
5253
readonly _isDragging = store<boolean>(false);
5354
readonly _isEditDisabled = compute(this._selectedVehicle, v => !v);
55+
readonly _isSpinDisabled = compute(this._selectedVehicle, v => !v || !v[0]._isSpinning());
5456
readonly _isPositionDisabled = compute(this._isMoving, this._isEditDisabled, (m, e) => m || e);
5557
readonly _formatPosition = (pos: number): string => (this._isEditDisabled.get() ? "Not available" : pos.toString());
5658
readonly _multiplierIndex = store<number>(0);
@@ -277,6 +279,7 @@ export class VehicleViewModel
277279
this._x.set(car.x);
278280
this._y.set(car.y);
279281
this._z.set(car.z);
282+
this._spin.set(car.spin);
280283

281284
const train = this._selectedTrain.get();
282285
if (train)

tests/services/spacingEditor.tests.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ class TrackPiece
2020
{
2121
return new TrackPiece({ x, y, z, direction }, this.type, this.subpositions);
2222
}
23+
24+
toLocation(): CarTrackLocation
25+
{
26+
return { ...this.position, trackType: this.type };
27+
}
2328
}
2429

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

@@ -405,8 +410,8 @@ test("Flat track: get spacing to preceding vehicle 1 step away", t =>
405410
{
406411
const mapMock = setupTrackIterator([ flatTrackPiece ]);
407412
const train = createTrain(mapMock, [
408-
{ trackProgress: 17, trackLocation: flatTrackPiece.position }, // front car
409-
{ trackProgress: 16, trackLocation: flatTrackPiece.position }
413+
{ trackProgress: 17, trackLocation: flatTrackPiece.toLocation() }, // front car
414+
{ trackProgress: 16, trackLocation: flatTrackPiece.toLocation() }
410415
]);
411416

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

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

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

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

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

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

0 commit comments

Comments
 (0)