diff --git a/src/objects/rideVehicle.ts b/src/objects/rideVehicle.ts index de8a044..32a9cf0 100644 --- a/src/objects/rideVehicle.ts +++ b/src/objects/rideVehicle.ts @@ -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"; @@ -112,4 +113,13 @@ export class RideVehicle const type = this._type(); return !type || isPowered(type); } -} \ No newline at end of file + + /** + * 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); + } +} diff --git a/src/services/spacingEditor.ts b/src/services/spacingEditor.ts index 6af5232..149954b 100644 --- a/src/services/spacingEditor.ts +++ b/src/services/spacingEditor.ts @@ -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); @@ -371,4 +371,4 @@ class BackwardIterator extends SubpositionIterator nextTile.z + nextTrack._endZ ); } -} \ No newline at end of file +} diff --git a/src/services/vehicleEditor.ts b/src/services/vehicleEditor.ts index cfb1902..8309fcd 100644 --- a/src/services/vehicleEditor.ts +++ b/src/services/vehicleEditor.ts @@ -12,7 +12,7 @@ const execute = register("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", @@ -27,6 +27,7 @@ const xPosition = "x", yPosition = "y", zPosition = "z", + spinKey = "spin", primaryColour = "body", secondaryColour = "trim", tertiaryColour = "tertiary"; @@ -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. @@ -223,6 +232,7 @@ function updateVehicleSetting(args: UpdateVehicleSettingArgs): void case massKey: case poweredAccelerationKey: case poweredMaxSpeedKey: + case spinKey: { callback = (car): void => { @@ -278,4 +288,4 @@ function updateVehicleSetting(args: UpdateVehicleSettingArgs): void } forEachVehicle(targets, callback); -} \ No newline at end of file +} diff --git a/src/services/vehiclePicker.ts b/src/services/vehiclePicker.ts index b32fb5a..a7f5562 100644 --- a/src/services/vehiclePicker.ts +++ b/src/services/vehiclePicker.ts @@ -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); diff --git a/src/ui/mainWindow.ts b/src/ui/mainWindow.ts index b627467..1efe705 100644 --- a/src/ui/mainWindow.ts +++ b/src/ui/mainWindow.ts @@ -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"; @@ -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(), @@ -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) }) ] }), diff --git a/src/utilities/game.ts b/src/utilities/game.ts new file mode 100644 index 0000000..1fd280c --- /dev/null +++ b/src/utilities/game.ts @@ -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; diff --git a/src/viewmodels/vehicleViewModel.ts b/src/viewmodels/vehicleViewModel.ts index 85417fb..283e39b 100644 --- a/src/viewmodels/vehicleViewModel.ts +++ b/src/viewmodels/vehicleViewModel.ts @@ -41,6 +41,7 @@ export class VehicleViewModel readonly _x = store(0); readonly _y = store(0); readonly _z = store(0); + readonly _spin = store(0); readonly _primaryColour = store(0); readonly _secondaryColour = store(0); @@ -51,6 +52,7 @@ export class VehicleViewModel readonly _isPicking = store(false); readonly _isDragging = store(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(0); @@ -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) diff --git a/tests/services/spacingEditor.tests.ts b/tests/services/spacingEditor.tests.ts index 17c6913..c25cec4 100644 --- a/tests/services/spacingEditor.tests.ts +++ b/tests/services/spacingEditor.tests.ts @@ -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, @@ -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 }); @@ -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); @@ -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); @@ -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); @@ -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); @@ -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(); @@ -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(); @@ -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();