Player pushing boat by walking into it

Got it working!! WOOO :clap:

I ended up going back to CFrame but using your suggestion of rendering the collisions only on the client. Then I used this code to sync momentum:

--BrahGames 07-27-2021 rewritten https://devforum.roblox.com/t/jailbreak-train-platform-system/236339 
--slightly edited to work with bodymovers and such /maxx

local Players = game:GetService("Players")
local player = game.Players.LocalPlayer
local RunService = game:GetService('RunService')

local CheckStates = true
local LastPartCFrame
local LastPart
local Function
local Function2
function CFrameLogic(Raycast, CFramePart)
	--------------------------------------------------------------- MOVE PLAYER TO NEW POSITON FROM OLD POSITION
	if Raycast.Instance ~= LastPart then
		LastPart = Raycast.Instance
		LastPartCFrame = nil
	end
	local Train = Raycast.Instance
	if LastPartCFrame == nil then -- If no LastTrainCFrame exists, make one!
		LastPartCFrame = Train.CFrame -- This is updated later.
	end
	local TrainCF = Train.CFrame
	local Rel = TrainCF * LastPartCFrame:inverse()
	LastPartCFrame = Train.CFrame -- Updated here.
	CFramePart.CFrame = Rel * CFramePart.CFrame -- Set the player's CFrame
	--print("set")
end
Function = RunService.Heartbeat:Connect(function()
	--------------------------------------------------------------- CHECK PLATFORM BELOW
	-- Build a "RaycastParams" object
	local HumanoidRootPart = player.Character.HumanoidRootPart
	local humanoid = player.Character.Humanoid
	local raycastParams = RaycastParams.new()
	raycastParams.FilterType = Enum.RaycastFilterType.Include
	raycastParams.FilterDescendantsInstances = {game:GetService("CollectionService"):GetTagged("BoatCollisions")}
	raycastParams.IgnoreWater = true
	raycastParams.CollisionGroup = "Default"

	-- Cast the ray
	local Hit = workspace:Raycast(HumanoidRootPart.CFrame.p, Vector3.new(0,-15,0), raycastParams) --Casts a ray facing down
	--------------------------------------------------------------------------------------------
	if Hit then -- Check if the ray hits and if it hits an object inside of MovingObjects
		if CheckStates then -- check if the character is jumping
			local currentState = humanoid:GetState()
			--if currentState  == Enum.HumanoidStateType.Freefall or currentState  == Enum.HumanoidStateType.Jumping or currentState  == Enum.HumanoidStateType.FallingDown then
				CFrameLogic(Hit, HumanoidRootPart)
			--else
				--LastPartCFrame = nil -- Clear the value when the player gets off.
			--end
		else
			CFrameLogic(Hit, HumanoidRootPart)
		end
	else
		LastPartCFrame = nil -- Clear the value when the player gets off.
	end
end)
Function2 = player.Character.Humanoid.Died:Connect(function()
	Function:Disconnect() -- Stop memory leaks
	Function2:Disconnect() -- Stop memory leaks
end)

It’s a slightly modified version of the code from this post.

Thanks so much for your help @BendsSpace.

4 Likes