Custom swimming trigger

Hello! I’m trying to make a game similar to MGS3. part of that is wading through water, and crouching in water.

the problem I’m having, is roblox makes my character swim as soon as he crouches in the water. I understand the water is deep, but I don’t want the character to swim yet. it also causes issues just moving through the water, since sometimes his torso touches it.

External Media

I have a script that’s supposed to override this, but it isn’t working. any help is greatly appreciated !!

local RunService = game:GetService("RunService")

local SWIM_THRESHOLD_HEIGHT = -1

local function getTerrainWaterLevel(position)
	local min = Vector3.new(
		math.floor(position.X / 4) * 4 - 4,
		math.floor(position.Y / 4) * 4 - 4,
		math.floor(position.Z / 4) * 4 - 4
	)
	local max = Vector3.new(
		math.ceil(position.X / 4) * 4 + 4,
		math.ceil(position.Y / 4) * 4 + 4,
		math.ceil(position.Z / 4) * 4 + 4
	)
	local region = Region3.new(min, max)
	local material, occupancy = workspace.Terrain:ReadVoxels(region, 4)

	local highestWaterY = nil
	local size = material.Size
	for x = 1, size.X do
		for y = 1, size.Y do
			for z = 1, size.Z do
				if material[x][y][z] == Enum.Material.Water and occupancy[x][y][z] > 0 then
					local worldY = min.Y + (y - 1) * 4
					if not highestWaterY or worldY > highestWaterY then
						highestWaterY = worldY
					end
				end
			end
		end
	end
	return highestWaterY
end

local function shouldCharacterSwim(character)
	local humanoidRootPart = character:FindFirstChild("HumanoidRootPart")
	if not humanoidRootPart then return false end

	local waterLevel = getTerrainWaterLevel(humanoidRootPart.Position)
	local footLevel = humanoidRootPart.Position.Y - (humanoidRootPart.Size.Y / 2)
	return waterLevel and (waterLevel - footLevel) >= SWIM_THRESHOLD_HEIGHT
end

local function manageSwimmingState(character)
	local humanoid = character:FindFirstChild("Humanoid")
	local humanoidRootPart = character:FindFirstChild("HumanoidRootPart")
	if not humanoid or not humanoidRootPart then return end

	local shouldSwim = shouldCharacterSwim(character)
	local currentState = humanoid:GetState()

	local player = Players:GetPlayerFromCharacter(character)
	if player then
		print(player.Name .. " - ShouldSwim: " .. tostring(shouldSwim) .. " | CurrentState: " .. tostring(currentState))
	end

	if shouldSwim then
		humanoid:SetStateEnabled(Enum.HumanoidStateType.Swimming, true)
		if currentState ~= Enum.HumanoidStateType.Swimming then
			humanoid:ChangeState(Enum.HumanoidStateType.Swimming)
		end
	else
		humanoid:SetStateEnabled(Enum.HumanoidStateType.Swimming, false)

		-- If we're currently swimming, force out of it
		if currentState == Enum.HumanoidStateType.Swimming then
			local raycast = workspace:Raycast(humanoidRootPart.Position, Vector3.new(0, -8, 0))
			if raycast and raycast.Distance < 6 then
				humanoid:ChangeState(Enum.HumanoidStateType.Running)
			else
				humanoid:ChangeState(Enum.HumanoidStateType.Freefall)
			end
		end
	end
end

local function setupCharacterSwimming(character)
	local humanoid = character:WaitForChild("Humanoid")

	humanoid:SetStateEnabled(Enum.HumanoidStateType.Swimming, false)

	local connection
	connection = RunService.Heartbeat:Connect(function()
		if character.Parent then
			manageSwimmingState(character)
		else
			connection:Disconnect()
		end
	end)

	local stateConnection
	stateConnection = humanoid.StateChanged:Connect(function(oldState, newState)
		if not character.Parent then
			stateConnection:Disconnect()
			return
		end

		-- If somehow we got into swimming when we shouldn't
		if newState == Enum.HumanoidStateType.Swimming and not shouldCharacterSwim(character) then
			-- Immediately disable and change state
			humanoid:SetStateEnabled(Enum.HumanoidStateType.Swimming, false)
			local humanoidRootPart = character:FindFirstChild("HumanoidRootPart")
			if humanoidRootPart then
				local raycast = workspace:Raycast(humanoidRootPart.Position, Vector3.new(0, -8, 0))
				if raycast and raycast.Distance < 6 then
					humanoid:ChangeState(Enum.HumanoidStateType.Running)
				else
					humanoid:ChangeState(Enum.HumanoidStateType.Freefall)
				end
			end
		end
	end)
end

for _, player in pairs(Players:GetPlayers()) do
	if player.Character then
		setupCharacterSwimming(player.Character)
	end
end

Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		setupCharacterSwimming(character)
	end)
end)```

Voxels are very low-resolution, and I recommend testing your script with a fixed value (such as when the character’s HumanoidRootPart reaches a Y value of 5, the character is submerged.

A few things to check:

  • This is a LocalScript, or a Script with a RunContext of Client, right? It really doesn’t seem like it considering that you use Players.GetPlayers to set it up on all clients. It needs to be local if the character’s network owner is the player.
  • Is the raycast hitting the ground or is it hitting another part of the character?
  • What is shouldCharacterSwim returning? Is it working properly at all?

this is a script in serverscriptservice, and shouldcharacterswim is in fact working properly, and is returning false/true if/when it should

Ah, that’s your problem. If the character is associated with any Player, you cannot use a server script, and it must be a local one.

I recommend pasting the code into a LocalScript in StarterPlayerScripts just to see if it works, and if it does, you should consider redoing the code because only one player needs to be considered if it’s a LocalScript.

2 Likes

thank you so much!! this fixed it entirely

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.