My "Hold Shift to slow fall" script somehow works without being mid-air

Greetings!
I only recently got into scripting. Looking up the DevForum was a massive help to my learning (thank you for everything!) and I figured I could seek some help directly.

So basically, in my game, the player is supposed to obtain an ability that allows them to slow their fall when they hold shift while being mid-air. It works! Whenever the player is mid-air, their fall is slowed for as long as the Shift key is held and everything goes back to normal if they release it.
(The game is going to be a metroidvania so I want to implement abilities that help with moving around on the map. I figured unlockable abilities that require key press would be best.)

However, I noticed one issue that isn’t quite desired:
As the title of this topic says, the slow fall is activated even if I’m not in the process of falling!
Whenever I hold the shift key while on the ground, the player sinks into it.

The slow fall itself is already how I want it to be (the player falls at a fixed and constant speed, just like those parachutes from old Roblox games), but I want to make it so if the player is not in freefall, it can’t be activated.

Here’s the whole script! I added some comments too.

local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local A = require(game.ReplicatedStorage:WaitForChild("AbilityModule")) 

local pGui = A.player:WaitForChild("PlayerGui")
local sfGui = pGui:WaitForChild("AbilityGui").SlowFall
local sfGiverEvent = ReplicatedStorage:WaitForChild("sfGiverEvent")
sfGui.Text = "Slow Fall Disabled"
--Everything above this comment works perfectly and may be irrelevant

local function onCharacterAdded(character)
	local humanoid = character:WaitForChild("Humanoid")
	local bVelo = Instance.new("BodyVelocity", A.player.Character:WaitForChild("HumanoidRootPart"))
	bVelo.MaxForce = Vector3.new(0,1,0)


if A.SlowFon then --Slow fall can only be activated if the player obtained it. No issue here.
		humanoid.StateChanged:Connect(function(old, new)
			if new == Enum.HumanoidStateType.Freefall then --Supposed to proceed only if player is in freefall,
				UserInputService.InputBegan:Connect(function(input)
					if input.KeyCode == Enum.KeyCode.LeftShift then --Activates slow fall, works well
						bVelo.MaxForce = Vector3.new(0,100000,0)
						bVelo.P = 10000
						bVelo.Velocity = Vector3.new(0,-10,0)
					end
				end)
				UserInputService.InputEnded:Connect(function(input)
					if input.KeyCode==Enum.KeyCode.LeftShift then --Stops slow fall, works well
						bVelo.MaxForce = Vector3.new(0,1,0)
						bVelo.Velocity = Vector3.new(0,16,0)
						bVelo.P = 0
					end
				end)
			else --I tried doing "elseif new ~= Enum.HumanoidStateType.Freefall then" but it didn't seem to make a difference.
				
			end
		end)
	end
end


local function onGiverFired()
	A.SlowFon = true
	sfGui.Text = "Slow Fall Enabled!" 
	onCharacterAdded(A.player.Character)
end

sfGiverEvent.OnClientEvent:Connect(onGiverFired)

A.player.CharacterAdded:connect(onCharacterAdded)

Note that may be important
I played around with the issue some more after writing this post and it appears that when the player just got the ability or respawned while having it, the issue doesn’t happen.
The issue only starts happening after the player jumped for the first time after spawning/getting the ability.
In other words, whenever onCharacterAdded is called, the issue isn’t here until the player jumps or falls for the first time and, of course, unlocked the ability.

I thank you in advance for your help! I have a feeling I wasn’t too far from the solution.
I hope I was descriptive enough!

2 Likes

The issue stems from how the input is handled. Every time the player enters a free-fall state, they are given the ability to slow-fall; however, landing does not take this away, as the even listeners are still connected. After the player has entered free-fall at least once, they will be able to activate the ability whenever they please.

I have modified your code to address this but have chosen to use ContextActionService. There is nothing wrong with using UserInputService, but I find the CAS easier to manage in most cases (touch input being the notable exception) than connecting and disconnecting UIS event listeners. Here’s what I got:

local ContextActionService = game:GetService("ContextActionService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local A = require(game.ReplicatedStorage:WaitForChild("AbilityModule")) 

local pGui = A.player:WaitForChild("PlayerGui")
local sfGui = pGui:WaitForChild("AbilityGui").SlowFall
local sfGiverEvent = ReplicatedStorage:WaitForChild("sfGiverEvent")
sfGui.Text = "Slow Fall Disabled"

local sf_action_string = "SlowfallAbility"
local activation_delay = math.sqrt(2*workspace.Gravity*7.5)/(workspace.Gravity)
--7.5 is the default jump height in studs.

local function onCharacterAdded(character)
	local humanoid = character:WaitForChild("Humanoid")
	local bVelo = Instance.new("BodyVelocity", A.player.Character:WaitForChild("HumanoidRootPart"))
	bVelo.MaxForce = Vector3.new(0,1,0)
	
	local slow_fall = false	--This variable reports whether or not the user is attempting to slow-fall, not whether or not they actually are.
	
	--Declare activation and deactivation functions.
	local function activate_slow_fall()
		--ONLY activate ability in correct context.
		if humanoid:GetState() == Enum.HumanoidStateType.Freefall then
			bVelo.MaxForce = Vector3.new(0,100000,0)
			bVelo.P = 10000
			bVelo.Velocity = Vector3.new(0,-10,0)
		end
	end
	local function deactivate_slow_fall()
		--Ability can be deactivated in any context.
		bVelo.MaxForce = Vector3.new(0,1,0)
		bVelo.Velocity = Vector3.new(0,16,0)
		bVelo.P = 0
	end
	
	--Handle all input with one handler passed to ContextActionService:BindAction
	local function handle_slow_fall(_,input_state)
		if input_state == Enum.UserInputState.Begin then
			slow_fall = true
			activate_slow_fall()
		elseif input_state == Enum.UserInputState.End then
			slow_fall = false
			deactivate_slow_fall()
		end
		return Enum.ContextActionResult.Pass
	end
	
	if A.SlowFon then
		--If the player can slow-fall, bind the ability to LeftShift
		ContextActionService:BindAction(sf_action_string,handle_slow_fall,false,Enum.KeyCode.LeftShift)
		
		--Set up humanoid event listeners to help manage state.
		local humanoid_state_connection, humanoid_died_connection
		humanoid_state_connection = humanoid.StateChanged:Connect(function(old, new)
			if new == Enum.HumanoidStateType.Freefall then
				if slow_fall then
					--If holding shift when entering the air, enter slow-fall.
					if old == Enum.HumanoidStateType.Jumping then
						--If the player jumped to enter the air, wait long enough for the player to reach the apex of their jump.
						task.wait(activation_delay)
						if not slow_fall then return end
					end
					activate_slow_fall()
				end
			else
				--If the player is not in the air, deactivate slow-fall.
				deactivate_slow_fall()
			end
		end)
		
		--When the player dies, clean up connections and deactivate their ability to slow-fall until they respawn.
		humanoid_died_connection = humanoid.Died:Connect(function()
			if humanoid_died_connection then
				humanoid_died_connection:Disconnect()
				humanoid_died_connection = nil
			end
			if humanoid_state_connection then
				humanoid_state_connection:Disconnect()
				humanoid_state_connection = nil
			end
			ContextActionService:UnbindAction(sf_action_string)
			slow_fall = false
			deactivate_slow_fall()
		end)
	end
end


local function onGiverFired()
	A.SlowFon = true
	sfGui.Text = "Slow Fall Enabled!" 
	onCharacterAdded(A.player.Character)
end

sfGiverEvent.OnClientEvent:Connect(onGiverFired)

A.player.CharacterAdded:connect(onCharacterAdded)

This code grants players the ability to slow-fall but ensures it can only be activated when in the air. It is also set up to activate whenever the player jumps or falls off a ledge while holding shift.

When jumping, the code waits a predetermined amount of time before activating slow-fall to allow the player to reach the apex of their jump; bear in mind, though, that the predetermined time is hard-coded based on default gravity and jump power. An additional handler would need to be written to account for changes in either of those variables.

1 Like