ScriptName RYBQuestScript float fQuestDelayTime ; controls script update rate, see: https://cs.uesp.net/wiki/FQuestDelayTime float HighUpdateRate ; script updates per second when boat is moving or rocking (default: 0.0166) float MediumUpdateRate ; script updates per second when boat is not moving or rocking but nearby (default: 0.1) float LowUpdateRate ; script updates per second when far away from the boat (default: 1.0) float ModVersion ; For handling updates ; Quest script states short Initializing ; Boat states short BoatMoving ; 0 = not moving, 1 = about to move this tick, 2 = in motion short Rowing ; 0 = not rowing (timed), 1 = rowing forward (timed), 2 = rowing backward (timed) short AutoRowing ; 0 = not auto rowing, 1 = auto rowing forward, 2 = auto rowing backward short Resetting ; 1 = run positioning code once relative to player, 2 = run positioning code once relative to boat short LockHeading ; 0 = free to turn, 1 = locked to current boat heading short LadderDeployed ; 0 = disabled, 1 = enabled short LampOn ; 0 = not summoning, 1 = waiting for collider to move, 2 = moving boat to collider, ; 3 = moving slightly to fix collision/interaction issues short Summoning short Dragging ; 0 = not dragging, 1 = dragging short Grounded ; 0 = not grounded, 1 = collided with something and stopped short OnLand ; 0 = not on land, 1 = on land (Z position is too high) auto-set if BoatZ > LandZThreshold short BoatPurchased short ChestPurchased short LampPurchased short LadderPurchased ; Triggers ; Sort of like functions that any script can call by setting these to 1 which this script will handle and set back to 0 short TriggerAutoRow ; 1 = auto-row forward, 2 = auto-row backward short TriggerGetOnBoat ; 1 = get on boat via SetPosition, 2 = sit on the seat short TriggerStartDragging ; 1 = will start dragging if Dragging == 0 short TriggerStopDragging ; 1 = will stop dragging if Dragging == 1 short TriggerSummonBoat ; 1 = summon boat, 2 = place boat right here short TriggerRowCast ; player casting the row spell will set this to 1 (forward) or 2 (back) float SecondsPassed ; actual time passed since last frame (GetSecondsPassed) float SmoothedDeltaTime ; SecondsPassed adjusted to the recent average frame rate using exponentially smoothing ; How much to smooth the delta time. ; Tune this between 0.05-0.2 to make adjustments more/less responsive to frame rate changes (default: 0.1) float DeltaSmoothingFactor float TargetDeltaTime ; target delta time to smooth to (default: 0.0166) aka. 60fps ; All "base" speed/rate values below assume a frame rate of 60fps (0.0166 seconds per frame) ; To prevent the boat from moving faster in higher FPS and slower in lower FPS, the speed values are dynamically ; adjusted to the current frame rate (based on the result of GetSecondsPassed exponentially smoothed over many frames). float BaseBoatVelocity ; distance in units to move the boat every frame float AbsoluteBoatVelocity ; absolute value of BaseBoatVelocity (used in calculating the turn rate) float FrameBoatVelocity float BoatMaxVelocity ; maximum distance in units to move the boat every frame (default: 6) float VelocityDecayLnRetentionFactor ; natural log of the retention factor (0.99) per frame at 60fps (default: -0.01005) float FrameVelocityDecay ; how much to multiply the velocity by every frame to decay it. will be calculated float BaseRowForce ; how much to increase/decrease the velocity every frame when rowing (default: 0.05) float FrameRowForce float BaseTurnRate ; how much to turn the boat every frame (default: 0.4) float TurnRateVelocityFactor ; how much to multiply the base turn rate by based on the boat's velocity (calculated) float FrameTurnRate float CurrentTurnRate ; the actual turn rate being applied this frame (decayed or accelerated version of FrameTurnRate) float TurnRateAcceleration ; how quickly turn rate ramps up (default: 0.15) float TurnRateDeceleration ; how quickly turn rate decays when not turning (default: 0.92) float LandZThreshold ; Z position that the boat must be above to be considered on land (default: 10) float WaterLevelZ ; Z position of the water level (default: 0) float RowTimer ; current time left before stopping rowing float RowSpellDuration ; duration to row after spell is cast (default: 1.0) float OverboardDistance ; distance in units from the boat to be considered overboard (default: 300) float PlayerDistance ; current distance from the player to the boat (returned from GetDistance) float SummonDistance ; distance in units to place the boat in front of the player when summoning (default: 350) ; summoning the boat goes haywire unless we give things time to "settle" this delays moving the boat a bit float SummonTimer ; how much time left to wait before executing the next step of summoning (default: 0.5) float SummonTimerDelay ; how long to wait in seconds after summoning to move boat to collider (default: 0.5) float PlayerSin float PlayerCos ; Dragging physics variables float DragRopeLength ; how far the player can get before boat starts moving (default: 100) float DragTautDistanceSquared ; current distance from player to tow point (squared) float DragSlackDistance ; how much slack is in the rope float PlayerMovementDistance float PlayerMovementForce float DragPointX ; point where the rope connects (center of boat) float DragPointY float DragTargetAngle float DragForceX ; force applied to boat float DragForceY float DragBoatVelocityX ; boat's current drag velocity float DragBoatVelocityY float DragVelocitySquared float DragFriction ; how quickly boat stops when not being pulled. 1.0 = no friction (default: 0.85) float DragBaseTurnRate ; how fast boat rotates to match drag direction (default: 0.1) float DragFrameTurnRate ; how much to rotate the boat this frame adjusted for the current frame rate float DragMaxTurnPerFrame ; maximum angle to turn the boat per frame while dragging (default: 0.1) float DragTurnDeadzone ; difference of degrees in player angle to boat angle to ignore before turning (default: 5) float DragCurrentTurnRate ; current turn rate applied this frame while dragging adjusted with accel/decel smoothing float DragTurnRateAcceleration ; how quickly to accelerate the turn rate while dragging (default: 0.15) float DragTurnRateDeceleration ; how quickly to decelerate the turn rate while dragging (default: 0.92) float DragAngleDiff ; difference between boat angle and drag angle float DragPullStrength ; how strong the pull is (default: 0.005) float DragMaxForce ; maximum force to apply to the boat (default: 0.5) float DragMaxVelocity ; maximum velocity of the boat while dragging (default: 4.0) float DragZInterpolationRate ; how quickly to interpolate the Z position of the boat to the player (default: 0.1) float DragPlayerBoatZDiff ; difference between player Z and boat Z float DragFrameBoatZAdjust ; adjustment to the boat Z position this frame based on player Z float DragLandPlayerZOffset ; offset from the player z to place the drag point, to raise the boat up (default: 30) float DragMaxZPerFrame ; maximum Z distance to move the boat per frame while dragging (default: 0.1) float DragMaxPlayerDistance ; maximum distance between player and boat before dragging is canceled (default: 1800) float DragPitchAngle ; current smoothed pitch angle for dragging terrain float DragTargetPitchAngle ; target pitch angle to smooth toward float DragMaxPitchAngle ; maximum (absolute) pitch angle to apply while dragging (default: 45) float DragPitchSmoothingFactor ; how quickly to smooth pitch changes (default: 0.15) float DragPathStartX ; Start position of current path segment float DragPathStartY float DragPathStartZ float DragPathDistance ; Distance traveled in current segment float DragPathSlope ; Calculated slope of boat's path float DragPathMinDistance ; Minimum distance before calculating slope (default: 50) float DragPathSlopeFactor ; Factor to invert and minimize slope player path (default: -0.7) float DragPathSlopeDeadzone ; Angle of player path that will not affect pitch (default: 2) float DragTargetPitchMovingSmoothingFactor ; Additional smoothing factor on pitch applied while moving (default: 0.03) float DragTargetPitchStoppedSmoothingFactor ; Additional smoothing factor on pitch applied while stopped (default: 0.02) float DragUphillZAdjustmentFactor ; How much to adjust Z based on angle when going uphill (default: 6.0) float DragDownhillZAdjustmentFactor ; How much to adjust Z based on angle when going downhill (default: 4.5) ; Boat drag angle calculation variables float dX float dY float dXAbs float dYAbs float a float r float s ; Rocking animation variables ; IMPORTANT: Keep pitch and roll angles small (under 10-15 degrees) to avoid gimbal lock issues ; The boat's primary movement still relies on Z rotation, these are just visual effects float RockingEnabled ; 0 = disabled, 1 = enabled (default: 1) float RockPhase ; Current phase of the primary rocking motion (0-360) float RockPhase2 ; Secondary rocking phase for more complex motion float RockPhase3 ; Tertiary phase for roll motion float RockAmplitudeZ ; How much the boat rocks up/down (in units) (default: 0.8) float RockAmplitudePitch ; How much the boat pitches forward/back (in degrees) (default: 2.0) float RockAmplitudeRoll ; How much the boat rolls side-to-side (in degrees) (default: 3.0) float RockMaxAbsoluteZ ; Maximum BoatZ +/- Z offset value allowed from rocking (in units) (default: 3.0) float RockFrequency ; How fast the boat rocks (degrees per second) (default: 30.0) float RockFrequency2 ; Secondary frequency for complex motion (default: 45.0) float RockFrequency3 ; Frequency for roll motion (default: 25.0) float RockRandomPhase ; Random offset to make rocking look natural float RockRandomTimer ; Timer for changing random values float RockRandomInterval ; How often to change random values (in seconds) (default: 3.0) float RockSpeedFactor ; How much boat speed affects rocking (default: 0.5) float RockWeatherFactor ; Multiplier for bad weather (purely based on GetWindSpeed currently) (default: 0.5) float WindSpeed ; Current wind speed (0-1) from GetWindSpeed float RockDistanceThreshold ; Distance from player to enable rocking (in units) (default: 3000) float TargetRockZOffset ; Target Z offset from rocking float TargetRockPitchOffset ; Target pitch (X angle) offset from rocking float TargetRockRollOffset ; Target roll (Y angle) offset from rocking float RockZOffset ; Current smoothed boat Z offset from rocking float RockPitchOffset ; Current smoothed pitch (X angle) offset from rocking float RockRollOffset ; Current smoothed roll (Y angle) offset from rocking float RockSmoothingFactor ; How quickly rocking starts from nothing and settles when stopped (default: 0.1) float BoatPitchAngle ; Current pitch angle of boat float BoatRollAngle ; Current roll angle of boat ; Sine approximation variables for rocking float rockAng float rockN float rockT2 float rockSin float rockSin2 float rockSin3 float rockCos3 ; Attachment rotation calculation variables float PitchRadians ; Pitch angle in radians for calculations float RollRadians ; Roll angle in radians for calculations float SinPitch ; Sine of pitch angle float SinRoll ; Sine of roll angle float CosPitch ; Cosine of pitch angle float CosRoll ; Cosine of roll angle float OrigX float OrigY float OrigZ float TempX float TempY float TempZ ; Player weight influence variables float PlayerWeightEnabled ; 0 = disabled, 1 = enabled (default: 1) float PlayerLocalX ; Player's position relative to boat center (starboard/port) float PlayerLocalY ; Player's position relative to boat center (bow/stern) float PlayerLocalZ ; Player's position relative to boat center (up/down) float PlayerRelativeX ; World relative position before rotation float PlayerRelativeY ; World relative position before rotation float PlayerDistanceFromCenter ; How far player is from boat center float PlayerWeightInfluence ; 0-1 multiplier based on distance from center float PlayerWeightMaxDistanceForward ; bow to stern max distance (default: 240) float PlayerWeightMaxDistanceSide ; port to starboard max distance (default: 100) float PlayerWeightMaxDistanceVertical ; vertical max distance (default: 60) float PlayerWeightPitchFactor ; How much player position affects pitch (default: 0.15) float PlayerWeightRollFactor ; How much player position affects roll (default: 0.12) float PlayerWeightPitchOffset ; Pitch offset caused by player weight float PlayerWeightRollOffset ; Roll offset caused by player weight float PlayerWeightSmoothingFactor ; How quickly weight effects change (default: 0.2) float TargetPlayerWeightPitchOffset ; Target pitch offset for smoothing float TargetPlayerWeightRollOffset ; Target roll offset for smoothing ref BoatRef float BoatX float BoatY float BoatZ float BoatZWithRock ; BoatZ + RockZOffset float BoatAngle float PlayerX float PlayerY float PlayerZ float PlayerAngle float Diff float Hemi float BoatModelAngle ; boat's in-game model is rotated 90 degrees offset from 0, this stores true model angle float TurnDeadzone ; difference of degrees in player angle to boat angle to ignore before turning (default: 10) ; Math variables for boat movement phyiscs float pi float halfPi float radToDeg float degToRad float ang float n float t2 ; (specific to boat yaw angle) float sin float cos float tan ref BoatMarker float BoatMarkerZ float BoatMarkerZOffset ; offset from the boat to place the map marker (default: 20) ref Seat float SeatX float SeatY float SeatZ float SeatForwardOffset ; pos units from boat center along bow to stern to place the seat float SeatSideOffset ; pos units from boat center along port to starboard to place the seat float SeatZOffset ; pos units from BoatZ upwards to place the seat (default: 19) ref Chest float ChestX float ChestY float ChestZ float ChestAngle float ChestAngleOffset float ChestForwardOffset ; pos units from boat center along bow to stern to place the chest float ChestSideOffset ; pos units from boat center along port to starboard to place the chest float ChestZOffset ; pos units from BoatZ upwards to place the chest (default: 15) ref LampLit ref LampUnlit float LampX float LampY float LampZ float LampForwardOffset ; pos units from boat center along bow to stern to place the lamp float LampSideOffset ; pos units from boat center along port to starboard to place the lamp float LampZOffset ; pos units from BoatZ upwards to place the lamp (default: 87) ref Ladder float LadderX float LadderY float LadderZ float LadderForwardOffset ; pos units from boat center along bow to stern to place the ladder float LadderSideOffset ; pos units from boat center along port to starboard to place the ladder float LadderZOffset ; pos units from BoatZ upwards to place the ladder (default: -28) ref Collider float ColliderX float ColliderY float ColliderZ float ColliderOffset ; pos units from boat center towards boat direction to place the collider float ColliderOffsetReverse ; pos units from boat center towards the back of the boat to place the collider float ColliderMoveFreq ; how often to move the collider in seconds (default: 0.05) float ColliderMoveTimer ; current time left to wait before moving the collider again float CollisionDetectDelay ; how long to wait in seconds at boat startup before checking for collision (default: 2) float CollisionDetectTimer ; current time left to give for moving the boat away at startup before detecting collision short CollisionDetectZThreshold ; Z position that the collider must be above to be considered colliding with something begin GameMode if (Initializing == 0) set Initializing to 1 ; start in low-processing mode and switch to faster later on when boat is moving set fQuestDelayTime to LowUpdateRate ; Initialization is done in stage result scripts to save max script size space SetStage RYB 1 ; init constants SetStage RYB 2 ; init refs SetStage RYB 3 ; init collider SetStage RYB 4 ; init summoning SetStage RYB 5 ; init dragging SetStage RYB 6 ; init rocking SetStage RYB 7 ; init player weight endif set PlayerDistance to BoatRef.GetDistance Player if (PlayerDistance < RockDistanceThreshold) if (RockingEnabled == 1) set fQuestDelayTime to HighUpdateRate ; high processing rate when near the boat for rocking animation else set fQuestDelayTime to MediumUpdateRate ; medium processing rate when nearby but rocking is disabled endif else set fQuestDelayTime to LowUpdateRate ; low processing rate when far away endif if (TriggerAutoRow == 1) set TriggerAutoRow to 0 set BoatMoving to 1 set fQuestDelayTime to HighUpdateRate set AutoRowing to 1 set Grounded to 0 set DragTargetPitchAngle to 0 Seat.SetDestroyed 1 BoatMarker.Disable Ladder.Disable set LadderDeployed to 0 set BaseBoatVelocity to BaseRowForce set CollisionDetectTimer to CollisionDetectDelay elseif (TriggerAutoRow == 2) set TriggerAutoRow to 0 set BoatMoving to 1 set Grounded to 0 set DragTargetPitchAngle to 0 set fQuestDelayTime to HighUpdateRate set AutoRowing to 2 Seat.SetDestroyed 1 BoatMarker.Disable Ladder.Disable set LadderDeployed to 0 set BaseBoatVelocity to -BaseRowForce set CollisionDetectTimer to CollisionDetectDelay endif ; For some unknown reason, this player distance check only works in this script and not in the menu script if (TriggerRowCast > 0 && PlayerDistance > OverboardDistance) set TriggerRowCast to 0 set RYBMenu.TriggerRowCast to 3 ; not near boat messagebox elseif (TriggerRowCast > 0 && Dragging >= 1) set TriggerRowCast to 0 set RYBMenu.TriggerRowCast to 4 ; dragging boat messagebox elseif (TriggerRowCast > 0 && OnLand == 1) set TriggerRowCast to 0 set RYBMenu.TriggerRowCast to 5 ; boat on land messagebox elseif (TriggerRowCast == 1) set TriggerRowCast to 0 set AutoRowing to 0 ; rowing manually cancels auto rowing set Rowing to 1 set RowTimer to RowSpellDuration if (BoatMoving == 0) set BoatMoving to 1 set Grounded to 0 set DragTargetPitchAngle to 0 set fQuestDelayTime to HighUpdateRate Seat.SetDestroyed 1 BoatMarker.Disable Ladder.Disable set LadderDeployed to 0 set BaseBoatVelocity to BaseRowForce set CollisionDetectTimer to CollisionDetectDelay endif elseif (TriggerRowCast == 2) set TriggerRowCast to 0 set AutoRowing to 0 ; rowing manually cancels auto rowing set DragTargetPitchAngle to 0 set Rowing to 2 set RowTimer to RowSpellDuration if (BoatMoving == 0) set BoatMoving to 1 set Grounded to 0 set fQuestDelayTime to HighUpdateRate Seat.SetDestroyed 1 BoatMarker.Disable Ladder.Disable set LadderDeployed to 0 set BaseBoatVelocity to -BaseRowForce set CollisionDetectTimer to CollisionDetectDelay endif endif if (TriggerGetOnBoat == 1) set TriggerGetOnBoat to 0 SetStage RYB 11 ; Update boat position and angle set PlayerZ to BoatZ + 20 ; TODO: this sometimes doesn't put the player on the boat Player.SetPos x BoatX Player.SetPos y BoatY Player.SetPos z PlayerZ Player.SetAngle z BoatAngle Message "You climbed onto the boat." elseif (TriggerGetOnBoat == 2) set TriggerGetOnBoat to 0 Seat.Activate Player 1 endif if (TriggerStartDragging == 1) set TriggerStartDragging to 0 if (Dragging == 0) set Dragging to 1 set DragTargetPitchAngle to 0 ; smooth reset to 0 pitch Player.AddItem RYBBoatToken 1 set fQuestDelayTime to HighUpdateRate ; High update rate while dragging Seat.SetDestroyed 1 BoatMarker.Disable set Grounded to 0 ; stop the boat from moving while summoning set BaseBoatVelocity to 0 set CurrentTurnRate to 0 ; Initialize dragging state set DragBoatVelocityX to 0 set DragBoatVelocityY to 0 SetStage RYB 11 ; Update boat position and angle set DragTargetAngle to BoatAngle ; Initialize path tracking set DragPathStartX to Player.GetPos x set DragPathStartY to Player.GetPos y set DragPathStartZ to Player.GetPos z set DragPathSlope to 0 if (OnLand == 1 && Player.IsSwimming == 0) ; When on land, raise the boat up a bit to indicate dragging has been activated set DragPlayerBoatZDiff to (PlayerZ + DragLandPlayerZOffset) - BoatZ set Dragging to 2 ; special state that allows z change until boat meets this z change endif Message "You are now dragging the boat." endif endif if (TriggerStopDragging == 1) set TriggerStopDragging to 0 if (Dragging >= 1) set Dragging to 0 if (RockingEnabled == 0) set fQuestDelayTime to MediumUpdateRate endif set DragBoatVelocityX to 0 set DragBoatVelocityY to 0 Player.RemoveItem RYBBoatToken 1 Seat.SetDestroyed 0 BoatMarker.Enable Message "You stop dragging the boat." endif endif if (TriggerSummonBoat >= 1) set fQuestDelayTime to HighUpdateRate ; stop the boat from moving while summoning set BoatMoving to 0 set Grounded to 0 Seat.SetDestroyed 0 set BaseBoatVelocity to 0 set CurrentTurnRate to 0 set DragTargetPitchAngle to 0 set PlayerX to Player.GetPos x set PlayerY to Player.GetPos y set PlayerZ to Player.GetPos z if (PlayerZ < 1) set PlayerZ to 1 endif set PlayerAngle to Player.GetAngle z if (PlayerAngle < 0) set PlayerAngle to PlayerAngle + 360 elseif (PlayerAngle >= 360) set PlayerAngle to PlayerAngle - 360 endif set BoatAngle to PlayerAngle set BoatModelAngle to BoatAngle + 90 if (BoatModelAngle < 0) set BoatModelAngle to BoatModelAngle + 360 elseif (BoatModelAngle >= 360) set BoatModelAngle to BoatModelAngle - 360 endif ; This positions the invisible collider SummonDistance units in front of the player. ; The game places actors in a way that avoids terrain and object collisions so this is a hack to prevent ; too much clipping on the boat when it is summoned. set ang to PlayerAngle SetStage RYB 18 ; Calculate sin/cos of the player angle if (TriggerSummonBoat == 1) set TriggerSummonBoat to 0 Message "Summoning boat..." ; Use collider to find a good spot to place the boat in front of the player set ColliderX to PlayerX + (PlayerSin * SummonDistance) set ColliderY to PlayerY + (PlayerCos * SummonDistance) SetStage RYB 20 ; Enable and position collider ; need to wait for collider to move and "settle" before moving the boat to the spot the collider found set SummonTimer to SummonTimerDelay set Summoning to 1 return elseif (TriggerSummonBoat == 2) set TriggerSummonBoat to 0 Message "Placing boat..." set BoatX to PlayerX + (PlayerSin * SummonDistance) set BoatY to PlayerY + (PlayerCos * SummonDistance) set BoatZ to PlayerZ ; Disable all the refs first since they need to be disabled and enabled one frame later to appear correctly BoatRef.Disable BoatMarker.Disable Seat.Disable Chest.Disable LampLit.Disable LampUnlit.Disable Ladder.Disable BoatRef.MoveTo Player BoatRef.SetPos x, BoatX BoatRef.SetPos y, BoatY BoatRef.SetPos z, BoatZ BoatRef.SetAngle z, BoatModelAngle set Summoning to 2 return endif endif if (SummonTimer > 0) set SummonTimer to SummonTimer - SecondsPassed elseif (SummonTimer <= 0 && Summoning == 1) ; time to move the boat to the collider set BoatX to Collider.GetPos x set BoatY to Collider.GetPos y set BoatZ to Collider.GetPos z set BoatZ to BoatZ - RockZOffset ; remove rocking offset from last frame to get "true" Z if (BoatZ < (WaterLevelZ + 1)) set BoatZ to WaterLevelZ + 1 endif BoatRef.Disable BoatMarker.Disable Seat.Disable Chest.Disable LampLit.Disable LampUnlit.Disable Ladder.Disable BoatRef.MoveTo Player BoatRef.SetPos x, BoatX BoatRef.SetPos y, BoatY BoatRef.SetPos z, BoatZ BoatRef.SetAngle z, BoatModelAngle Collider.Disable set Summoning to 2 return ; set SummonTimer to SummonTimerDelay ; this delay maybe not necessary elseif (Summoning == 2) set Summoning to 0 BoatRef.Enable BoatMarker.Enable Seat.Enable if (ChestPurchased == 1) Chest.Enable endif if (LampPurchased == 1) if (LampOn == 1) LampLit.Enable endif LampUnlit.Enable endif if (LadderPurchased == 1 && LadderDeployed == 1) Ladder.Enable endif if (RockingEnabled == 0) set fQuestDelayTime to MediumUpdateRate endif set Resetting to 1 endif if (LampOn == 1 && LampLit.GetDisabled == 1) set Resetting to 1 LampLit.Enable LampUnlit.PlaySound3D SPLFireballFail elseif (LampOn == 0 && LampLit.GetDisabled == 0) set Resetting to 1 LampLit.Disable LampUnlit.PlaySound3D ITMTorchHeldExt endif set SecondsPassed to GetSecondsPassed ; Clamp extreme values if (SecondsPassed < 0.001) set SecondsPassed to 0.001 elseif (SecondsPassed > 0.1) set SecondsPassed to 0.1 endif ; Exponentially smooth the delta time to adjust for frame rate changes set SmoothedDeltaTime to ((1.0 - DeltaSmoothingFactor) * SmoothedDeltaTime) + (DeltaSmoothingFactor * SecondsPassed) ; Speed values assume a frame rate of 60fps so readjust speed values to current frame rate set FrameRowForce to BaseRowForce * (SmoothedDeltaTime / TargetDeltaTime) if (Rowing == 0 && AutoRowing == 0 && BoatMoving == 2) ; Boat is moving, but not rowing. Decay the velocity using exponential decay. ; velocity = velocity * (1 - decay * time) if (BaseBoatVelocity != 0) ; Calculate decay factor based on smoothed delta time ; Convert to time-based decay ; retention^(frames) = retention^(time/frameTime) ; We need to calculate r^(SmoothedDeltaTime/TargetDeltaTime) ; Approximation since we can't do pow(): ; For small time steps, r^t ≈ 1 + t*ln(r) ; ln(0.98) ≈ -0.0202 set FrameVelocityDecay to 1 + (SmoothedDeltaTime / TargetDeltaTime) * (VelocityDecayLnRetentionFactor) set BaseBoatVelocity to BaseBoatVelocity * FrameVelocityDecay ; Stop when very slow if (BaseBoatVelocity > -0.2 && BaseBoatVelocity < 0.2) set BaseBoatVelocity to 0 set BoatMoving to 0 if (RockingEnabled == 0) set fQuestDelayTime to MediumUpdateRate endif Seat.SetDestroyed 0 BoatMarker.Enable Collider.Disable endif endif endif if ((Rowing == 1 || AutoRowing == 1) && BoatMoving == 2 && BaseBoatVelocity < BoatMaxVelocity) ; Don't allow the boat to accelerate forever past max speed set BaseBoatVelocity to BaseBoatVelocity + FrameRowForce if (BaseBoatVelocity >= BoatMaxVelocity) set BaseBoatVelocity to BoatMaxVelocity endif endif if ((Rowing == 2 || AutoRowing == 2) && BoatMoving == 2 && BaseBoatVelocity > -BoatMaxVelocity) ; Don't allow the boat to accelerate backwards forever past max negative speed set BaseBoatVelocity to BaseBoatVelocity - FrameRowForce if (BaseBoatVelocity <= -BoatMaxVelocity) set BaseBoatVelocity to -BoatMaxVelocity endif endif if (RowTimer > 0) set RowTimer to RowTimer - SecondsPassed elseif (RowTimer <= 0 && Rowing > 0) set Rowing to 0 endif if (BoatMoving == 2 && PlayerDistance > OverboardDistance) set Rowing to 0 set CurrentTurnRate to 0 ; immediately stop turning set AutoRowing to 0 set LadderDeployed to 1 Ladder.Enable set Resetting to 1 Message "You've gone overboard!" endif if (BoatMoving == 2 && CollisionDetectTimer > 0) set CollisionDetectTimer to CollisionDetectTimer - SecondsPassed elseif (BoatMoving == 2 && CollisionDetectTimer <= 0 && Collider.GetInSameCell Player && Collider.GetPos z > CollisionDetectZThreshold) if (RockingEnabled == 0) set fQuestDelayTime to MediumUpdateRate endif SetStage RYB 21 ; Collision procedure elseif (BoatMoving == 2 && CollisionDetectTimer <= 0 && Collider.GetDead == 1) ; Moving the collider every frame seems to cause the collider to spontaneously die when near land. However, this ; also seems to have many false positives where it dies in open water. Instead of every frame, the script uses ; ColliderMoveTimer to move it every N seconds instead of every frame. The Collider doesn't seem to ; spontaneously die with this method, but I kept this here just in-case it ever does happen. if (RockingEnabled == 0) set fQuestDelayTime to MediumUpdateRate endif Collider.Resurrect 0 SetStage RYB 21 ; Collision procedure endif if (BoatMoving >= 1 && (Player.GetSitting != 0 && Player.GetSitting != 3)) ; wait for player to sit/standup before continuing to move Message "Pausing the boat until you get up." return endif ; Boat dragging. See visualization here: https://claude.ai/public/artifacts/23380c6b-c9a4-430d-bd86-781ae588739f if (Dragging >= 1) set PlayerX to Player.GetPos x set PlayerY to Player.GetPos y set PlayerZ to Player.GetPos z SetStage RYB 11 ; Update boat position and angle if (PlayerDistance > DragMaxPlayerDistance) set Dragging to 0 Player.RemoveItem RYBBoatToken 1 if (RockingEnabled == 0) set fQuestDelayTime to MediumUpdateRate endif set DragBoatVelocityX to 0 set DragBoatVelocityY to 0 Seat.SetDestroyed 0 BoatMarker.Enable Message "You got too far from the boat and stopped dragging it." endif ; Drag point is at center of boat set DragPointX to BoatX set DragPointY to BoatY ; Vector from rope attach point (center of boat) to player set dX to PlayerX - DragPointX set dY to PlayerY - DragPointY ; Save absolute values of dX and dY if (dX < 0) set dXAbs to -dX else set dXAbs to dX endif if (dY < 0) set dYAbs to -dY else set dYAbs to dY endif if (dXAbs < 0.1 && dYAbs < 0.1) ; Player is very close to the drag point, so no need to calculate angle set DragTargetAngle to BoatAngle else SetStage RYB 10 ; Calculate DragTargetAngle with atan2(dY, dX) endif ; Calculate squared distance from player to drag point set DragTautDistanceSquared to ((PlayerX - DragPointX) * (PlayerX - DragPointX)) + ((PlayerY - DragPointY) * (PlayerY - DragPointY)) ; Only apply force if rope is taut (player far enough away) if (DragTautDistanceSquared > (DragRopeLength * DragRopeLength)) ; Get actual distance using Newton's method for square root (2 iterations is usually enough) ; See: https://cs.uesp.net/wiki/Square_Root set PlayerMovementDistance to DragRopeLength + 50 ; Initial reasonable guess ; Iteration 1: x = (x + n/x) / 2 set PlayerMovementDistance to (PlayerMovementDistance + (DragTautDistanceSquared / PlayerMovementDistance)) / 2 ; Iteration 2: x = (x + n/x) / 2 set PlayerMovementDistance to (PlayerMovementDistance + (DragTautDistanceSquared / PlayerMovementDistance)) / 2 ; Calculate normalized direction from drag point to player if (PlayerMovementDistance > 0) set DragForceX to (PlayerX - DragPointX) / PlayerMovementDistance set DragForceY to (PlayerY - DragPointY) / PlayerMovementDistance ; Apply pulling force based on how far beyond rope length we are set DragSlackDistance to PlayerMovementDistance - DragRopeLength ; Instead of linear, use a dampened response if (DragSlackDistance > 100) ; If very far, use logarithmic-like response to prevent extreme forces set DragSlackDistance to 100 + (DragSlackDistance - 100) * 0.1 endif set PlayerMovementForce to DragSlackDistance * DragPullStrength if (PlayerMovementForce > DragMaxForce) set PlayerMovementForce to DragMaxForce endif ; Simple spring force: the further stretched, the stronger the pull set DragForceX to DragForceX * PlayerMovementForce set DragForceY to DragForceY * PlayerMovementForce ; Apply force to velocity set DragBoatVelocityX to DragBoatVelocityX + (DragForceX * SmoothedDeltaTime / TargetDeltaTime) set DragBoatVelocityY to DragBoatVelocityY + (DragForceY * SmoothedDeltaTime / TargetDeltaTime) ; Limit max velocity set DragVelocitySquared to (DragBoatVelocityX * DragBoatVelocityX) + (DragBoatVelocityY * DragBoatVelocityY) if (DragVelocitySquared > (DragMaxVelocity * DragMaxVelocity)) ; Get actual velocity set PlayerMovementDistance to DragMaxVelocity ; Start with max set PlayerMovementDistance to (PlayerMovementDistance + (DragVelocitySquared / PlayerMovementDistance)) / 2 ; Scale down to max velocity set DragBoatVelocityX to DragBoatVelocityX * (DragMaxVelocity / PlayerMovementDistance) set DragBoatVelocityY to DragBoatVelocityY * (DragMaxVelocity / PlayerMovementDistance) endif ; Calculate angle difference to movement direction set DragAngleDiff to DragTargetAngle - BoatAngle if (DragAngleDiff > 180) set DragAngleDiff to DragAngleDiff - 360 elseif (DragAngleDiff < -180) set DragAngleDiff to DragAngleDiff + 360 endif if (DragAngleDiff < DragTurnDeadzone && DragAngleDiff > -DragTurnDeadzone) ; Within deadzone - decay the turn rate set DragCurrentTurnRate to DragCurrentTurnRate * DragTurnRateDeceleration ; Stop tiny movements if (DragCurrentTurnRate > -0.01 && DragCurrentTurnRate < 0.01) set DragCurrentTurnRate to 0 endif else ; Outside deadzone - calculate target turn rate and ramp up toward it set DragFrameTurnRate to DragAngleDiff * DragBaseTurnRate * (SmoothedDeltaTime / TargetDeltaTime) if (DragFrameTurnRate > DragMaxTurnPerFrame) set DragFrameTurnRate to DragMaxTurnPerFrame elseif (DragFrameTurnRate < -DragMaxTurnPerFrame) set DragFrameTurnRate to -DragMaxTurnPerFrame endif ; Smoothly ramp up to target turn rate set DragCurrentTurnRate to DragCurrentTurnRate + ((DragFrameTurnRate - DragCurrentTurnRate) * DragTurnRateAcceleration * SmoothedDeltaTime / TargetDeltaTime) endif set BoatAngle to BoatAngle + DragCurrentTurnRate endif endif ; Apply friction to velocity (always, even when not pulling) set DragBoatVelocityX to DragBoatVelocityX * DragFriction set DragBoatVelocityY to DragBoatVelocityY * DragFriction ; Update boat position based on velocity set BoatX to BoatX + (DragBoatVelocityX * SmoothedDeltaTime / TargetDeltaTime) set BoatY to BoatY + (DragBoatVelocityY * SmoothedDeltaTime / TargetDeltaTime) ; Only allow changing Z pos and pitch angle if rope is taut so the player can't ride the boat up into the air ; (not a plane mod yet ;D) if (DragTautDistanceSquared > (DragRopeLength * DragRopeLength)) ; Smoothly interpolate boat Z towards target if (Player.IsSwimming == 1) set DragPlayerBoatZDiff to PlayerZ - BoatZ else ; Raise the boat up a bit when on land so it doesn't clip into the ground as much set DragPlayerBoatZDiff to (PlayerZ + DragLandPlayerZOffset) - BoatZ endif ; Calculate distance boat has traveled from start of path segment set dX to PlayerX - DragPathStartX set dY to PlayerY - DragPathStartY set DragPathDistance to (dX * dX) + (dY * dY) ; If boat has moved enough, calculate the slope it traveled if (DragPathDistance > (DragPathMinDistance * DragPathMinDistance)) ; Get actual distance using Newton's method set DragPathDistance to DragPathMinDistance ; Initial guess set DragPathDistance to (DragPathDistance + ((dX * dX + dY * dY) / DragPathDistance)) / 2 set DragPathDistance to (DragPathDistance + ((dX * dX + dY * dY) / DragPathDistance)) / 2 ; Calculate slope from player's actual path set DragPathSlope to ((PlayerZ - DragPathStartZ) / DragPathDistance) * radToDeg * -DragPathSlopeFactor ; Apply deadzone to ignore tiny slopes if (DragPathSlope > -DragPathSlopeDeadzone && DragPathSlope < DragPathSlopeDeadzone) set DragPathSlope to 0 endif ; Clamp calculated slope to reasonable values if (DragPathSlope > DragMaxPitchAngle) set DragPathSlope to DragMaxPitchAngle elseif (DragPathSlope < -DragMaxPitchAngle) set DragPathSlope to -DragMaxPitchAngle endif ; Start new path segment from current position set DragPathStartX to PlayerX set DragPathStartY to PlayerY set DragPathStartZ to PlayerZ endif if (Player.IsSwimming == 1) set DragTargetPitchAngle to 0 ; Reset pitch when swimming else ; Only adjust pitch when boat is moving and player is not swimming if (DragBoatVelocityX != 0 || DragBoatVelocityY != 0) ; Smoothly adjust target pitch toward the path slope set DragTargetPitchAngle to DragTargetPitchAngle + ((DragPathSlope - DragTargetPitchAngle) * DragTargetPitchMovingSmoothingFactor) else ; When stopped, very slowly tend toward path slope set DragTargetPitchAngle to DragTargetPitchAngle + ((DragPathSlope - DragTargetPitchAngle) * DragTargetPitchStoppedSmoothingFactor) endif ; Z adjustment based on pitch angle to prevent clipping going up and down hills if (DragPlayerBoatZDiff >= 0) ; raise boat when player going downhill set DragPlayerBoatZDiff to DragPlayerBoatZDiff + (DragTargetPitchAngle * DragDownhillZAdjustmentFactor) else ; lower boat when player going uphill (DragTargetPitchAngle is negative) set DragPlayerBoatZDiff to DragPlayerBoatZDiff + (DragTargetPitchAngle * DragUphillZAdjustmentFactor) endif endif endif if (Dragging == 2 || DragTautDistanceSquared > (DragRopeLength * DragRopeLength)) set DragFrameBoatZAdjust to (DragPlayerBoatZDiff * DragZInterpolationRate * SmoothedDeltaTime / TargetDeltaTime) if (Dragging == 2) ; This is a one-time adjustment, so remove this frame's adjustment from the total target adjustment set DragPlayerBoatZDiff to DragPlayerBoatZDiff - DragFrameBoatZAdjust if ((DragFrameBoatZAdjust > 0 && DragPlayerBoatZDiff <= 0) || (DragFrameBoatZAdjust < 0 && DragPlayerBoatZDiff > 0) || DragPlayerBoatZDiff == 0) set Dragging to 1 ; target reached, proceed with normal dragging endif endif ; Prevent "boat becomes a missile" issue if (DragFrameBoatZAdjust > DragMaxZPerFrame) set DragFrameBoatZAdjust to DragMaxZPerFrame elseif (DragFrameBoatZAdjust < -DragMaxZPerFrame) set DragFrameBoatZAdjust to -DragMaxZPerFrame endif set BoatZ to BoatZ + DragFrameBoatZAdjust endif ; Prevent boat from going below water level if (BoatZ < (WaterLevelZ + 1)) set BoatZ to WaterLevelZ + 1 endif ; Stop tiny movements if (DragBoatVelocityX > -0.1 && DragBoatVelocityX < 0.1) set DragBoatVelocityX to 0 endif if (DragBoatVelocityY > -0.1 && DragBoatVelocityY < 0.1) set DragBoatVelocityY to 0 endif endif ; Smooth the actual pitch angle toward target (outside the dragging check) if (DragPitchAngle != DragTargetPitchAngle) set DragPitchAngle to DragPitchAngle + ((DragTargetPitchAngle - DragPitchAngle) * DragPitchSmoothingFactor * SmoothedDeltaTime / TargetDeltaTime) ; Stop tiny movements if (DragPitchAngle > -0.05 && DragPitchAngle < 0.05) set DragPitchAngle to 0 endif endif ; TODO: test if it's necessary to set these every update while moving. ; In theory, they only need to be set once at initialization since the boat is immovable beyond this script. ; (I want to see if resetting these fixes get on boat issues) ; Doesn't 100% fix it, but maybe it helps a bit? Not sure. if (Summoning == 0 && Dragging == 0) ; During summoning and dragging these are preset so don't overwrite them SetStage RYB 11 ; Update boat position and angle endif ; Calculate player weight influence on boat pitch and roll if (PlayerWeightEnabled == 1 && Summoning == 0 && Dragging == 0 && PlayerDistance < PlayerWeightMaxDistanceForward) ; Get player position set PlayerX to Player.GetPos x set PlayerY to Player.GetPos y set PlayerZ to Player.GetPos z ; Calculate player position relative to boat center set PlayerRelativeX to PlayerX - BoatX set PlayerRelativeY to PlayerY - BoatY ; Calculate boat's forward and right vectors using BoatAngle directly ; Forward vector (bow direction): sin(BoatAngle), cos(BoatAngle) ; Right vector (starboard direction): cos(BoatAngle), -sin(BoatAngle) ; PlayerLocalY: positive = toward bow, negative = toward stern set PlayerLocalY to PlayerRelativeX * sin + PlayerRelativeY * cos ; PlayerLocalX: positive = toward starboard, negative = toward port set PlayerLocalX to PlayerRelativeX * cos - PlayerRelativeY * sin set PlayerLocalZ to PlayerZ - BoatZWithRock ; Calculate distance from boat center for falloff effect set PlayerDistanceFromCenter to PlayerRelativeX * PlayerRelativeX + PlayerRelativeY * PlayerRelativeY ; Newton's method square root approximation (2 iterations) set PlayerDistanceFromCenter to PlayerWeightMaxDistanceForward ; Initial guess set PlayerDistanceFromCenter to (PlayerDistanceFromCenter + ((PlayerRelativeX * PlayerRelativeX + PlayerRelativeY * PlayerRelativeY) / PlayerDistanceFromCenter)) / 2 set PlayerDistanceFromCenter to (PlayerDistanceFromCenter + ((PlayerRelativeX * PlayerRelativeX + PlayerRelativeY * PlayerRelativeY) / PlayerDistanceFromCenter)) / 2 ; Calculate influence factor (1.0 at center, 0.0 at max distance) ; Limit effect area to a box approximately the area above the boat up 60 units (PlayerWeightMaxDistanceVertical) if (PlayerDistanceFromCenter <= PlayerWeightMaxDistanceForward && PlayerLocalZ < PlayerWeightMaxDistanceVertical && PlayerLocalX < PlayerWeightMaxDistanceSide && PlayerLocalX > -PlayerWeightMaxDistanceSide) set PlayerWeightInfluence to 1.0 - (PlayerDistanceFromCenter / PlayerWeightMaxDistanceForward) else set PlayerWeightInfluence to 0 endif ; Calculate target pitch offset based on player's fore/aft position ; Positive PlayerLocalY = toward bow (front) = boat should pitch forward (bow dips down) ; Negative PlayerLocalY = toward stern (back) = boat should pitch backward (stern dips down) set TargetPlayerWeightPitchOffset to PlayerLocalY * PlayerWeightPitchFactor * PlayerWeightInfluence ; Calculate target roll offset based on player's port/starboard position ; Positive PlayerLocalX = toward starboard (right) = boat should roll starboard (startboard dips down) ; Negative PlayerLocalX = toward port (left) = boat should roll port (port dips down) set TargetPlayerWeightRollOffset to -PlayerLocalX * PlayerWeightRollFactor * PlayerWeightInfluence ; Smooth the transition to prevent jarring movements set PlayerWeightPitchOffset to PlayerWeightPitchOffset + ((TargetPlayerWeightPitchOffset - PlayerWeightPitchOffset) * PlayerWeightSmoothingFactor * SmoothedDeltaTime / TargetDeltaTime) set PlayerWeightRollOffset to PlayerWeightRollOffset + ((TargetPlayerWeightRollOffset - PlayerWeightRollOffset) * PlayerWeightSmoothingFactor * SmoothedDeltaTime / TargetDeltaTime) else ; Player not on boat or system disabled - gradually return to neutral set PlayerWeightPitchOffset to PlayerWeightPitchOffset * (1 - PlayerWeightSmoothingFactor * SmoothedDeltaTime / TargetDeltaTime) set PlayerWeightRollOffset to PlayerWeightRollOffset * (1 - PlayerWeightSmoothingFactor * SmoothedDeltaTime / TargetDeltaTime) ; Stop tiny movements if (PlayerWeightPitchOffset > -0.01 && PlayerWeightPitchOffset < 0.01) set PlayerWeightPitchOffset to 0 endif if (PlayerWeightRollOffset > -0.01 && PlayerWeightRollOffset < 0.01) set PlayerWeightRollOffset to 0 endif endif if (BoatMoving >= 1 || Dragging >= 1 || Resetting >= 1) ; Speed values assume a frame rate of 60fps so readjust speed values to current frame rate set FrameBoatVelocity to BaseBoatVelocity * (SmoothedDeltaTime / TargetDeltaTime) ; Calculate new boat angle. Script originally by Jason1 set PlayerAngle to Player.GetAngle z if (PlayerAngle < 0) set PlayerAngle to PlayerAngle + 360 elseif (PlayerAngle >= 360) set PlayerAngle to PlayerAngle - 360 endif if (BoatMoving >= 1 && Dragging == 0 && Summoning == 0 && LockHeading == 0) set Diff to PlayerAngle - BoatAngle ; Calculate velocity-based turn modifier to make boats turn slower when moving slowly for realism if (BaseBoatVelocity < 0) set AbsoluteBoatVelocity to -BaseBoatVelocity else set AbsoluteBoatVelocity to BaseBoatVelocity endif ; Turn rate scales with velocity (0.3 to 1.0 multiplier) set TurnRateVelocityFactor to 0.3 + (0.7 * (AbsoluteBoatVelocity / BoatMaxVelocity)) if (TurnRateVelocityFactor > 1.0) set TurnRateVelocityFactor to 1.0 endif set FrameTurnRate to BaseTurnRate * TurnRateVelocityFactor * (SmoothedDeltaTime / TargetDeltaTime) if ((Diff > 1 || Diff < -1) && (Diff > TurnDeadzone || Diff < -TurnDeadzone)) ; Player is currently turning, ramp up turn rate set CurrentTurnRate to CurrentTurnRate + ((FrameTurnRate - CurrentTurnRate) * TurnRateAcceleration * SmoothedDeltaTime / TargetDeltaTime) else ; Player not turning, decay the turn rate set CurrentTurnRate to CurrentTurnRate * TurnRateDeceleration ; Stop tiny movements if (CurrentTurnRate > -0.05 && CurrentTurnRate < 0.05) set CurrentTurnRate to 0 endif endif if (CurrentTurnRate != 0 && (Diff > 1 || Diff < -1)) if (PlayerAngle > 180) set Hemi to PlayerAngle - 180 else set Hemi to PlayerAngle + 180 endif if (Hemi < 0) set Hemi to Hemi + 360 elseif (Hemi >= 360) set Hemi to Hemi - 360 endif if (PlayerAngle > Hemi) if (BoatAngle > Hemi && BoatAngle < PlayerAngle) set BoatAngle to BoatAngle + CurrentTurnRate else set BoatAngle to BoatAngle - CurrentTurnRate endif else if (BoatAngle < Hemi && BoatAngle > PlayerAngle) set BoatAngle to BoatAngle - CurrentTurnRate else set BoatAngle to BoatAngle + CurrentTurnRate endif endif endif if (BoatAngle < 0) set BoatAngle to BoatAngle + 360 elseif (BoatAngle >= 360) set BoatAngle to BoatAngle - 360 endif else ; Player not turning - decay the turn rate set CurrentTurnRate to CurrentTurnRate * TurnRateDeceleration ; Stop tiny movements if (CurrentTurnRate > -0.05 && CurrentTurnRate < 0.05) set CurrentTurnRate to 0 endif endif ; Adjust for model alignment (boat model is rotated 90 degrees) set BoatModelAngle to BoatAngle + 90 if (BoatModelAngle < 0) set BoatModelAngle to BoatModelAngle + 360 elseif (BoatModelAngle >= 360) set BoatModelAngle to BoatModelAngle - 360 endif set BoatX to BoatX + (sin * FrameBoatVelocity) set BoatY to BoatY + (cos * FrameBoatVelocity) if (BoatMoving >= 1 && Dragging == 0 && OnLand == 0 && BoatZ != (WaterLevelZ + 1)) set BoatZ to WaterLevelZ + 1 ; if boat started moving above water, reset it to water level endif set BoatZWithRock to BoatZ + RockZOffset if (Resetting != 2) BoatRef.MoveTo Player endif BoatRef.SetPos x, BoatX BoatRef.SetPos y, BoatY BoatRef.SetPos z, BoatZWithRock BoatRef.SetAngle z, BoatModelAngle BoatRef.SetAngle x, BoatPitchAngle BoatRef.SetAngle y, BoatRollAngle endif if (BoatZ > LandZThreshold) set OnLand to 1 else set OnLand to 0 endif ; Boat rocking animation if (RockingEnabled == 1 && Dragging == 0 && Summoning == 0 && Grounded == 0 && OnLand == 0 && PlayerDistance < RockDistanceThreshold) ; Update random variation if (RockRandomTimer <= 0) set RockRandomTimer to RockRandomInterval ; Add some random variation to make it look natural set RockRandomPhase to (GetRandomPercent - 50) * 0.02 else set RockRandomTimer to RockRandomTimer - SecondsPassed endif ; Update rocking phases set RockPhase to RockPhase + (RockFrequency * SecondsPassed) if (RockPhase >= 360) set RockPhase to RockPhase - 360 endif set RockPhase2 to RockPhase2 + (RockFrequency2 * SecondsPassed) if (RockPhase2 >= 360) set RockPhase2 to RockPhase2 - 360 endif set RockPhase3 to RockPhase3 + (RockFrequency3 * SecondsPassed) if (RockPhase3 >= 360) set RockPhase3 to RockPhase3 - 360 endif SetStage RYB 15 ; Calculate sine for primary rocking (Z movement and pitch) SetStage RYB 16 ; Calculate sine for secondary rocking SetStage RYB 17 ; Calculate sine and cosine for roll motion ; Calculate rocking offsets ; Z movement: combine multiple sine waves set TargetRockZOffset to RockAmplitudeZ * (rockSin * 0.7 + rockSin2 * 0.3 + RockRandomPhase) ; Pitch (X rotation): boat tips forward/backward set TargetRockPitchOffset to RockAmplitudePitch * (rockSin * 0.8 + rockSin2 * 0.2) + PlayerWeightPitchOffset ; Roll (Y rotation): boat tips side to side, offset by 90 degrees using cosine set TargetRockRollOffset to RockAmplitudeRoll * (rockCos3 * 0.7 + rockSin2 * 0.3) + PlayerWeightRollOffset ; Adjust for boat speed (more rocking when moving) if (BoatMoving >= 1) if (BaseBoatVelocity < 0) set AbsoluteBoatVelocity to -BaseBoatVelocity else set AbsoluteBoatVelocity to BaseBoatVelocity endif ; Increase rocking amplitude based on speed set TargetRockZOffset to TargetRockZOffset * (1 + (AbsoluteBoatVelocity / BoatMaxVelocity) * RockSpeedFactor) set TargetRockPitchOffset to TargetRockPitchOffset * (1 + (AbsoluteBoatVelocity / BoatMaxVelocity) * RockSpeedFactor * 1.5) set TargetRockRollOffset to TargetRockRollOffset * (1 + (AbsoluteBoatVelocity / BoatMaxVelocity) * RockSpeedFactor * 0.7) endif ; Apply weather factor (based on wind speed) set WindSpeed to GetWindSpeed set TargetRockZOffset to TargetRockZOffset * (1 + (WindSpeed * RockWeatherFactor)) ; Limit extreme Z offsets that would mess with boat-in-water detection if (BoatZ + TargetRockZOffset < -RockMaxAbsoluteZ) set TargetRockZOffset to -RockMaxAbsoluteZ - BoatZ elseif (BoatZ + TargetRockZOffset > RockMaxAbsoluteZ) set TargetRockZOffset to RockMaxAbsoluteZ - BoatZ endif set TargetRockPitchOffset to TargetRockPitchOffset * (1 + (WindSpeed * RockWeatherFactor)) set TargetRockRollOffset to TargetRockRollOffset * (1 + (WindSpeed * RockWeatherFactor)) ; Smooth out the rocking effects so it doesn't snap into rocking position on boat launch set RockZOffset to RockZOffset + ((TargetRockZOffset - RockZOffset) * RockSmoothingFactor * SmoothedDeltaTime / TargetDeltaTime) set RockPitchOffset to RockPitchOffset + ((TargetRockPitchOffset - RockPitchOffset) * RockSmoothingFactor * SmoothedDeltaTime / TargetDeltaTime) set RockRollOffset to RockRollOffset + ((TargetRockRollOffset - RockRollOffset) * RockSmoothingFactor * SmoothedDeltaTime / TargetDeltaTime) else ; Dampen rocking when conditions aren't met set RockZOffset to RockZOffset * (1 - RockSmoothingFactor * SmoothedDeltaTime / TargetDeltaTime) set RockPitchOffset to RockPitchOffset * (1 - RockSmoothingFactor * SmoothedDeltaTime / TargetDeltaTime) set RockRollOffset to RockRollOffset * (1 - RockSmoothingFactor * SmoothedDeltaTime / TargetDeltaTime) ; Stop tiny movements if (RockZOffset > -0.01 && RockZOffset < 0.01) set RockZOffset to 0 endif if (RockPitchOffset > -0.01 && RockPitchOffset < 0.01) set RockPitchOffset to 0 endif if (RockRollOffset > -0.01 && RockRollOffset < 0.01) set RockRollOffset to 0 endif endif if (BoatMoving >= 1 || Dragging >= 1 || Resetting >= 1 || (PlayerDistance < RockDistanceThreshold && (RockZOffset != 0 || RockPitchOffset != 0 || RockRollOffset != 0))) set ang to BoatAngle SetStage RYB 12 ; Calculate sin, cos, & tan for boat angle endif if (BoatMoving >= 1 || Dragging >= 1 || Resetting >= 1 || (PlayerDistance < RockDistanceThreshold && (RockZOffset != 0 || RockPitchOffset != 0 || RockRollOffset != 0))) ; Transform world pitch/roll to boat's local pitch/roll set BoatPitchAngle to (RockPitchOffset + DragPitchAngle) * cos + RockRollOffset * sin set BoatRollAngle to -(RockPitchOffset + DragPitchAngle) * sin + RockRollOffset * cos ; Calculate sine/cosine for the transformed angles if (BoatPitchAngle != 0) SetStage RYB 13 ; Calculate sin, cos for pitch else set SinPitch to 0 set CosPitch to 1 endif if (BoatRollAngle != 0) SetStage RYB 14 ; Calculate sin, cos for roll else set SinRoll to 0 set CosRoll to 1 endif endif if (BoatMoving >= 1 || Dragging >= 1 || Resetting >= 1) set BoatMarkerZ to BoatZWithRock + BoatMarkerZOffset if (Resetting == 2) BoatMarker.MoveTo BoatRef else BoatMarker.MoveTo Player endif BoatMarker.SetPos x, BoatX BoatMarker.SetPos y, BoatY BoatMarker.SetPos z, BoatMarkerZ if (BoatMoving >= 1) if (FrameBoatVelocity > 0) set ColliderX to BoatX + (sin * ColliderOffset) set ColliderY to BoatY + (cos * ColliderOffset) else set ColliderX to BoatX - (sin * ColliderOffsetReverse) set ColliderY to BoatY - (cos * ColliderOffsetReverse) endif if (ColliderMoveTimer > 0) Set ColliderMoveTimer to ColliderMoveTimer - SecondsPassed else SetStage RYB 20 ; Enable and position collider Set ColliderMoveTimer to ColliderMoveFreq endif endif elseif (BoatMoving == 0 && Dragging == 0 && Summoning == 0 && (PlayerDistance < RockDistanceThreshold && (RockZOffset != 0 || RockPitchOffset != 0 || RockRollOffset != 0))) ; Apply rocking to stationary boat. set BoatZWithRock to BoatZ + RockZOffset BoatRef.SetPos z, BoatZWithRock BoatRef.SetAngle x, BoatPitchAngle BoatRef.SetAngle y, BoatRollAngle set BoatMarkerZ to BoatZWithRock + BoatMarkerZOffset BoatMarker.SetPos z, BoatMarkerZ endif ; Update attachments (seat, chest, lamp, ladder) positions and angles both while moving and stationary (if rocking) if (BoatMoving >= 1 || Dragging >= 1 || Resetting >= 1 || (BoatMoving == 0 && Dragging == 0 && (PlayerDistance < RockDistanceThreshold && (RockZOffset != 0 || RockPitchOffset != 0 || RockRollOffset != 0)))) SetStage RYB 30 ; Calculate seat position and angle SetStage RYB 40 ; Update seat position and angle if (ChestPurchased == 1) SetStage RYB 31 ; Calculate chest position and angle SetStage RYB 41 ; Update chest position and angle endif if (LampPurchased == 1) SetStage RYB 32 ; Calculate lamp(s) position and angle SetStage RYB 42 ; Update lamp(s) position and angle endif if (LadderPurchased == 1 && LadderDeployed == 1) SetStage RYB 33 ; Calculate ladder position and angle SetStage RYB 43 ; Update ladder position and angle endif endif if (BoatMoving == 1) set BoatMoving to 2 ; boat is now fully in motion endif if (Resetting >= 1) set Resetting to 0 endif end