FallService - Handle fall damage easy and flexible

Fall service

Hello! Do you bored/can’t/insert_your_reason_here to make fall damage that fulfill that list?:

  • Server Side check only
  • Does not depend on Humanoid state, Instead checks Velocity of PrimaryPart / Torso
  • Customizable damage that depends on material (Even for water)
  • Multiple groups that you can create (Earth, Moon, Lobby, etc…)
  • SetCustomData for specific player or character (overrides group data)
  • Good interface and no memory leaks

Okay, I am too bored to do it! Let’s change this then.
There’s some cons:

  • Physic is server based, so fall damage will have small latency (~ 0.2 sec)
  • All calculations runs every frame after physic (.Stepped)

I hope this is not affects you, if not - let’s continue.
This is example:

Click
local PlayersService = game:GetService("Players")
local FallService = require(script.FallService)
local DefaultGroup = FallService.new("Default", 5, 0.4, { --last argument is optional
	["MaterialsDamage"] = {
		["Water"] = 0, -- multiply damage by 0 if fall in water (so 0 damage)
		["Fabric"] = 0.75, -- by default all materials have 1 multiplier
		["Plastic"] = 1.25, 
		["Metal"] = 1.5,
	}
})

PlayersService.PlayerAdded:Connect(function(player)
	DefaultGroup:LinkPlayer(player) --[[
	Every time player`s character respawns, on his character will be called Enable
	Auto unlink if player left from game
	]]

	FallService:SetCustomData(player, nil, nil, { --We override MaterialsDamage for player`s character.
		["MaterialsDamage"] = {
			["Water"] = 0.5, --half damage
		}
	})
end)

Okay, I hope you understand now, let me describe API:

API
local options = { -- that's how options looks like for now
	["MaterialsDamage"] = {}
}

FallService.new(groupName, minDistance, damageMultiplier, options) : FallGroup
FallService:SetCustomData(characterOrPlayer, minDistance, damageMultiplier, options) -- Set data that will be used instead of group data (pass 2 arguments as nil to clear)
FallService:GetCustomData(characterOrPlayer) -- Returns custom data or nil (if custom data is not exist)
FallService:GetGroupOf(characterOrPlayer)
FallService:GetGroup(groupName)
FallService:GetFallSignalFor(characterOrPlayer, inInvocation) --[[
Returns signal, this signal is constant (untill character destroyed / player left). 
Pass second argument as true if you call this method in CharacterAdded/PlayerAdded invocation point.
Signal created on :Enable() call for character and on PlayerAdded event for player.
]]

FallGroup:Enable(character) -- Enable fallDamage for character
FallGroup:Disable(character) -- Disable fallDamage for character
FallGroup:LinkPlayer(player) -- Link player`s character to enable
FallGroup:UnlinkPlayer(player) -- Unlink player`s character
FallGroup:Toggle(characterOrPlayer, boolean) -- Enable/Disable (if 1 argument is character) or Link/Unlink (if 1 argument is player)
FallGroup:Destroy() --[[
Call`s UnlinkPlayer for each player in fall group and Disable for each character in fall group. 
Also all methods of this FallGroup after destroy will throw error if called
]]

You can get this module here: Link

Are you have suggestions or found bug? Let me know!

26 Likes

You should specify what happens in the docs when characterOrPlayer is a character (Enable/Disable) vs a player (Link/Unlink).

image

2 Likes

I described, but yeah, I think not so clean. I will edit it

This is really cool, It allows for setting fall damage really easily, Only thing that’s a small down-side is the when you’re done falling you get damaged a bit after, But oh well.

1 Like

Also, Recommend making a bindableevent for when players hit the ground, So they can add their own gui effects, fall damage sounds, or whatever they want to happen besides damage.

So the fall damage checks for your torso’s velocity rather than calculating it based on how long you’ve been in the air? Amazing.

Made this update, I will update tomorrow (I have night)

FallService V1.1.0

Added:

  • Signal dependency (by stravant and modified by sleitnick)
  • OnPlayerFall, OnCharacterFall - signals (for FallGroup and FallService)
  • :GetFallSignalFor(characterOrPlayer, inInvocation) - returns signal, more info in example

Changed:

  • Fixed clear on destroy for character. oops… (memory leak cause)

Warning: Don’t use past version of module because of memory leak.

New example with signals:

Example
local PlayersService = game:GetService("Players")
local FallService = require(script.FallService)
local DefaultGroup = FallService.new("Default", 5, 0.4, { --last argument is optional
	["MaterialsDamage"] = {
		["Water"] = 0, -- multiply damage by 0 if fall in water (so 0 damage), default is 0.25
		["Fabric"] = 0.75, -- by default all materials have 1 multiplier
		["Plastic"] = 1.25, 
		["Metal"] = 1.5,
	}
})

PlayersService.PlayerAdded:Connect(function(player)
	DefaultGroup:LinkPlayer(player) --[[
	Every time player`s character respawns, on his character will be called Enable
	Auto unlink if player left from game
	]]

	FallService:SetCustomData(player, nil, nil, { --We override MaterialsDamage for player`s character.
		["MaterialsDamage"] = {
			["Water"] = 0.5, --half damage
		},
	})

	DefaultGroup.OnCharacterFall:Connect(function(character, info)
		print("character with name \""..character.Name.."\" had fallen from this distance: ", info.fallDistance)
	end)
	FallService.OnPlayerFall:Connect(function(player, fallGroup, info)
		print("player with name \""..player.Name.."\" had fallen and got damaged by: ", info.damage)
	end)
	FallService:GetFallSignalFor(player, true):Connect(function(fallGroup, info)
		print("player had fallen in group with name:", fallGroup.groupName)
		-- material name is "" if ray not found ground
	end) --[[
	This signal always the same, and created onPlayerAdded. 
	Second argument is VERY important if you call this method on PlayerAdded event
	(second argument just yields 1 frame, so signal is 100% created)
	]]
	
	local isConnected = false
	player.CharacterAdded:Connect(function()
		if (isConnected) then return end
		isConnected = true
		
		FallService:GetFallSignalFor(player.Character, true):Connect(function(fallGroup, info) 
			print("character had fallen on this material:", info.material)
		end) --[[
		This signal too always the same, and created on :Enable(character), which is automatic in LinkPlayer,
		but :Enable() only called when character created.
	
		Second argument same here, because character fall event might be not added,
		because of CharacterAdded event above. 
		Always pass true as second argument if you get fall event in PlayerAdded/CharacterAdded invocation point
		]]
	end)
end)

Remember, always call :GetFallSignalFor(characterOrPlayer, inInvocation) with second argument
as true if you call it in CharacterAdded/PlayerAdded invocation point!

Also all signals returned by :GetFallSignalFor() are constant

Updated version of module is here: Tap to get

3 Likes

Where would I put the ModuleScript if I want to use it?

I’m assuming ServerSctiptService, since it’s all done on the server

1 Like

Yes, I will uncopylock template place soon

FallService V1.1.1

Changed :Destroy() behaviour, now if you destroyed group all signals for character/player in group will be disconnected (so don’t destroy if you need constant signals).
P.S: I don’t even know why :Destroy() is a feature, but don’t use it if you don’t need it.

Don’t rely on old :Destroy() behaviour, this will cause a memory leak.

Uncopylocked place (you can use this in production): Link
P.S: Module link is same as always

Saved me a headache, great module.

Awesome module! And the fallen damage varies from the types of materials according to how you set it? Nice. But please add a few videos of you demonstrating it.

I do recommend changing the name, as the word “Service” is a bit overused.

There’s uncopylocked place (so I think video will be useless), Service naming is used because I use Knit and there’s all modules that do stuff are services.

For everyone who gonna use it:
I didn’t write any unit tests, please, consider waiting for an update or implement this system yourself.

Hey! Awesome and lifesaving service! If I may, I’ve been trying to add water as a custom material variant of plastic, but the API only check for the basepart material. I’m going to try and make it work.

I hope you don’t mind, but here’s a modified code that check whether the material exist as a material variant: LINE 230 of FallService Module

			if (RayResult) then 
				
				local damageMaterial
				if RayResult.Instance.MaterialVariant ~= "" then
					damageMaterial = RayResult.Instance.MaterialVariant
				else
					damageMaterial = RayResult.Material.Name
				end
				
				damage *= (materialsDamage[damageMaterial] or 1)
				print(damage)
				print(damageMaterial)
			end