Help determining character position for spawning system

Recently, I made a boat spawning system for my friends game and only one problem has occurred so far and that is telling the distance from land to water. I’m not really sure how to approach this issue as I don’t know if it will take a raycasting approach or simply a mathematics equation I am missing when spawning the boat.

Any help will be appreciated thank you!

What it should spawn like:
image

How it sometimes spawn depending on character pos:
image

Spawning Script:

--> Services
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CollectionService = game:GetService("CollectionService")

--> Variables
local Remotes = ReplicatedStorage.Remotes
local Assets = ReplicatedStorage.Assets
local CurrentBoats = workspace:FindFirstChild("CurrentBoats")

local CurrentCamera = workspace.CurrentCamera

--> References

--> Dependancies

--> Constructor
local BoatServer = {}

---------------------

local function ReturnBoatInstance(a): boolean
	if CurrentBoats:FindFirstChild(a) and a:IsDescendantOf(CurrentBoats) then
		return true else return false
	end
end

-- Returns when i get a chance to import to real game :D
function GetPartsInterferingWithBoat(BoatInstance: Model)
	-- not finished lol
	local PartsTouching = workspace:GetPartBoundsInBox(BoatInstance.PrimaryPart.CFrame, BoatInstance.PrimaryPart.Size)
	-- doesnt return parts touching when boat is clearly on land.
	-- prints -> {} 
	print(PartsTouching)
end

function BoatServer:TeleportBoat(BoatInstance: Model, Player: Player)
	local Character = Player and Player.Character
	if not Character then warn(`{Player.Name} character could not be found.`) end

	-- Create a temporary part that will be designed to help guide the boat to a "correct" position.
	local BoatGuidance = Instance.new("Part")
	BoatGuidance.Anchored = true
	BoatGuidance.Size = Vector3.new(1,1,1)
	-- Equation for determing character postition.
	local VectorPosition = (Character:GetPivot().Position - CurrentCamera.CFrame.LookVector) - Vector3.yAxis/Character:GetPivot().Y - Vector3.zAxis/Character:GetPivot().Z - Vector3.xAxis*7
	BoatGuidance.Position = VectorPosition
	BoatGuidance.Parent = workspace

	if BoatGuidance and not ReturnBoatInstance(BoatInstance.Name) then
		local RequestBoat = Assets.Boats:FindFirstChild(BoatInstance.Name):Clone()
		RequestBoat:PivotTo(BoatGuidance.CFrame * CFrame.Angles(0, math.rad(180), 0))
		BoatGuidance:Destroy()
		RequestBoat.Parent = CurrentBoats
	end

	task.defer(function()
		while true do
			task.wait(1/3 * 3)
			GetPartsInterferingWithBoat(BoatInstance)
		end
	end)
end

Remotes.Shared:FindFirstChild("SummonBoat").OnServerInvoke = function(Player, Boat)
	if ReturnBoatInstance(Boat.Name) then
		return BoatServer:TeleportBoat(Boat, Player)
	else
		return BoatServer:TeleportBoat(Boat, Player)
	end
end

return BoatServer

I took the simple approach:

You could use .magnitude from an invisible part or anything that would determine the coastline of the island. If it is close in any axis, then you could move it in an axis determined by the magnitude that’s already there. This would make sure your boat spawns on land.

This is a mile out there with untested scripting… Attempted raycasting approach.

ServerScript
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CollectionService = game:GetService("CollectionService")

local Remotes = ReplicatedStorage.Remotes
local Assets = ReplicatedStorage.Assets
local CurrentBoats = workspace:FindFirstChild("CurrentBoats")
local CurrentCamera = workspace.CurrentCamera

local BoatServer = {}

local function ReturnBoatInstance(a): boolean
	if CurrentBoats:FindFirstChild(a) and a:IsDescendantOf(CurrentBoats) then
		return true 
	else 
		return false
	end
end

local function GetDistanceToWater(BoatInstance: Model)
	local origin = BoatInstance.PrimaryPart.Position
	local direction = Vector3.new(0, -500, 0)
	
	local rayParams = RaycastParams.new()
	rayParams.FilterDescendantsInstances = {BoatInstance}
	rayParams.FilterType = Enum.RaycastFilterType.Blacklist

	local result = workspace:Raycast(origin, direction, rayParams)
	
	if result then
		local hitPart = result.Instance
		local distance = (origin - result.Position).Magnitude

		if hitPart and hitPart:IsDescendantOf(workspace:FindFirstChild("Water")) then
			print("Boat is over water, distance:", distance)
		else
			print("Boat is over land, distance:", distance)
		end

		return distance
	end

	print("No hit detected.")
	return nil
end

function BoatServer:TeleportBoat(BoatInstance: Model, Player: Player)
	local Character = Player and Player.Character
	if not Character then 
		warn(`{Player.Name} character could not be found.`) 
		return
	end

	local BoatGuidance = Instance.new("Part")
	BoatGuidance.Anchored = true
	BoatGuidance.Size = Vector3.new(1,1,1)
	
	local VectorPosition = (Character:GetPivot().Position - CurrentCamera.CFrame.LookVector) - Vector3.yAxis/Character:GetPivot().Y - Vector3.zAxis/Character:GetPivot().Z - Vector3.xAxis*7
	BoatGuidance.Position = VectorPosition
	BoatGuidance.Parent = workspace

	if BoatGuidance and not ReturnBoatInstance(BoatInstance.Name) then
		local RequestBoat = Assets.Boats:FindFirstChild(BoatInstance.Name):Clone()
		RequestBoat:PivotTo(BoatGuidance.CFrame * CFrame.Angles(0, math.rad(180), 0))
		BoatGuidance:Destroy()
		RequestBoat.Parent = CurrentBoats
	end

	task.defer(function()
		while true do
			task.wait(1)
			local distance = GetDistanceToWater(BoatInstance)
			if distance then
				print("Distance from boat to surface:", distance)
			end
		end
	end)
end

Remotes.Shared:FindFirstChild("SummonBoat").OnServerInvoke = function(Player, Boat)
	if ReturnBoatInstance(Boat.Name) then
		return BoatServer:TeleportBoat(Boat, Player)
	else
		return BoatServer:TeleportBoat(Boat, Player)
	end
end

return BoatServer
LocalScript
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Remotes = ReplicatedStorage.Remotes
local LocalPlayer = Players.LocalPlayer
local Mouse = LocalPlayer:GetMouse()

local BoatName = "BoatModel"

local function SummonBoat()
	local Character = LocalPlayer.Character
	if not Character then return end

	local Boat = ReplicatedStorage.Assets.Boats:FindFirstChild(BoatName)
	if not Boat then return end

	local Response = Remotes.Shared:FindFirstChild("SummonBoat"):InvokeServer(Boat)
end

Mouse.Button1Down:Connect(SummonBoat)