Is this the best way to handle abilities?

The system I am currently using works off a table of functions that are named like abilities depending on player class, and number pressed. but is this actually any good?

function AbilityModule.AbilityTable(Player,Character,Number,MousePosition,Class,Animate)
	local Humanoid:Humanoid,RootPart,Animator:Animator=VerifyCharacter(Character)
	local AnimateFolder=GameStorage.Animation.Ability:FindFirstChild(Animate)
	local AbilityTable={
		["Robert_0"]=function()
		end,
		["Robert_1"]=function()
		end,
		["Robert_2"]=function()
		end,
		["Robert_3"]=function()
		end,
	local AbilityFunction=AbilityTable[Class.."_"..Number]
	return AbilityFunction or false
end

Is there a better way to do this? it works fine as it is, but I’m wondering if there’s a more better way to do this. No if statement though, I recall that being insanely unoptimized.

The way I use it is to create a module for each type of skill, for example a moveset that has 5 skills, I store every part of the server in a module on the server where the functions are called by another script depending on the attribute that the skill tool has.

And it is important to remember that all VFX, visual effects and particles need to be done on the client through a replication module. If you create the visual effects on the server, this will affect the game’s performance.

image

Module Example:

local Debris = game:GetService("Debris")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Lighting = game:GetService("Lighting")
local TweenService = game:GetService("TweenService")
local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local SoundService = game:GetService("SoundService")
local RunService = game:GetService("RunService")

--// Constants
local ClientStorage = ReplicatedStorage:WaitForChild("Storage")
local Animations = ReplicatedStorage:WaitForChild("Animations")
local VisualEffects = ClientStorage:WaitForChild("Visual Effects")

local RestlessAnimations = Animations:FindFirstChild("Inates"):FindFirstChild("Restless Gambler")
local RestlessVfxs = VisualEffects:FindFirstChild("Inates"):FindFirstChild("Restless Gambler")
local RestlessSounds = SoundService:WaitForChild("Inates"):FindFirstChild("Restless Gambler")

local Map = workspace:FindFirstChild("Map")
local DebrisFolder = Map:FindFirstChild("VisualEffects")
local EntitiesFolder = Map:FindFirstChild("Entities")

--// Modules
local ServerModules = ServerStorage:WaitForChild("Modules")
local Modules = ReplicatedStorage:WaitForChild("Modules")
local Utils = ReplicatedStorage:WaitForChild("Utils")
local Events = ReplicatedStorage:WaitForChild("Events")

--// Required Modules
local RockModule = require(Modules:WaitForChild("RockModule"))
local UtilsModule = require(Modules:WaitForChild("UtilsModule"))
local SoundReplicator = require(Modules:WaitForChild("SoundReplicator"))
local HitboxModule = require(Modules:WaitForChild("HitboxModule"))
local HitboxClass = require(Modules:WaitForChild("HitboxClass"))
local Replicator = require(Utils:WaitForChild("Replicator"))
local RagdollModule = require(Modules:WaitForChild("RagdollModule"))
local Server_LocalRequests = require(Utils:WaitForChild("ServerLocalRequests"))
local CombatModule = require(ServerModules:FindFirstChild("CombatModule"))
local ClashModule = require(ServerModules:WaitForChild("ClashModule"))

--// Skills
local Skills = {}

Skills["ShutterDoors"] = {
	Action = function(Character : Model)
		local Player = Players:GetPlayerFromCharacter(Character)
		local Humanoid = Character:FindFirstChildWhichIsA("Humanoid")
		local RootPart = Character:FindFirstChild("HumanoidRootPart")
		
		if Character:GetAttribute("UsingSkill") or not Character:GetAttribute("CanUseSkill") then
			return
		end
		
		Character:SetAttribute("UsingSkill",true)
		
		local ShutterDoorTrackAnimation = Humanoid:FindFirstChildWhichIsA("Animator"):LoadAnimation(RestlessAnimations:FindFirstChild("ShutterDoor Character"))
		ShutterDoorTrackAnimation:Play()
		
		--// Hitbox
		local HitboxParams = {
			SizeOrPart = 6,
			DebounceTime = 0.25,
			UseClient = Player and Player or nil,
			Debug = false,
			Blacklist = {Character},
		} :: HitboxTypes.HitboxParams

		local newHitbox, connected = HitboxClass.new(HitboxParams)
		
		task.spawn(function()
			ShutterDoorTrackAnimation:GetMarkerReachedSignal("Attack"):Wait()
			newHitbox:SetPosition(Character.HumanoidRootPart.CFrame)
			newHitbox:WeldTo(Character.HumanoidRootPart, CFrame.new(0,0,-6))
			newHitbox:Start()
			
			newHitbox.HitSomeone:Once(function(hitChars)
				Character:SetAttribute("HittedSomeone",true)
				newHitbox:Stop()

				for index,HittedCharacters in hitChars do
					if HittedCharacters:FindFirstChildWhichIsA("Humanoid") then
						local HittedRootPart = HittedCharacters:FindFirstChild("HumanoidRootPart")
						local Distance = (RootPart.Position - HittedRootPart.Position).Magnitude
						
						local EnemyToSelfUnit = (Character.Head.Position - HittedCharacters.Head.Position).Unit
						local EnemyLook = HittedCharacters.Head.CFrame.LookVector

						local DotProduct = EnemyToSelfUnit:Dot(EnemyLook)
						local BehindEnemy : boolean = false

						if DotProduct <= -0.01 then
							BehindEnemy = true
						end
						
						if Distance >= 20 then return end

						if not HittedCharacters:GetAttribute("IFrames") then
							if HittedCharacters:GetAttribute("Blocking") and not HittedCharacters:GetAttribute("Parry") and not BehindEnemy then
								CombatModule.CombatFunctions:BlockDamage(Character, HittedCharacters)
							elseif HittedCharacters:GetAttribute("Parry") and not BehindEnemy then
								CombatModule.CombatFunctions:Parry(Character, HittedCharacters)
							else
								SoundReplicator:PlaySound(RestlessSounds,"ShutterDoorsHit",Character.PrimaryPart,true)

								local AlignPosition = Instance.new("AlignPosition")
								AlignPosition.Mode = Enum.PositionAlignmentMode.OneAttachment
								AlignPosition.Attachment0 = HittedRootPart:FindFirstChildWhichIsA("Attachment")
								AlignPosition.ApplyAtCenterOfMass = true
								AlignPosition.ReactionForceEnabled = true
								AlignPosition.RigidityEnabled = true 

								AlignPosition.MaxForce = 999999
								AlignPosition.Responsiveness = 99999999

								AlignPosition.Position = RootPart.Position + RootPart.CFrame.LookVector * 6
								AlignPosition.Parent = HittedRootPart

								Debris:AddItem(AlignPosition, 1)

								CombatModule.CombatFunctions:StunCharacter(HittedCharacters,0.5,5)
							end
						end
					end
				end
			end)
		end)
		
		Replicator:FireAllDistanceClients(Character,500,{Folder = ReplicatedStorage:FindFirstChild("ClientSkills").Inates,ModuleName = "RestlessGambler_Effects",FunctionName = "DoorCastAnimation"},Character)

		task.delay(0.5,function()
			newHitbox:Stop()
			Character:SetAttribute("HittedSomeone",nil)
		end)
		
		ShutterDoorTrackAnimation.Ended:Wait()
		ShutterDoorTrackAnimation:Destroy()
		
		Character:SetAttribute("UsingSkill",false)
	end;
	
	CooldownDuration = 2;
}

Effects Module (For Client Replication)

--// Services
local Debris = game:GetService("Debris")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Lighting = game:GetService("Lighting")
local TweenService = game:GetService("TweenService")
local Players = game:GetService("Players")
local SoundService = game:GetService("SoundService")

--// Constants
local ClientStorage = ReplicatedStorage:WaitForChild("Storage")
local Animations = ReplicatedStorage:WaitForChild("Animations")
local VisualEffects = ClientStorage:WaitForChild("Visual Effects")

local RestlessAnimations = Animations:WaitForChild("Inates"):FindFirstChild("Restless Gambler")
local RestlessVfxs = VisualEffects:WaitForChild("Inates"):FindFirstChild("Restless Gambler")
local RestlessSounds = SoundService:WaitForChild("Inates"):FindFirstChild("Restless Gambler")

local Map = workspace:FindFirstChild("Map")
local DebrisFolder = Map:FindFirstChild("VisualEffects")

--// Modules
local ClientModules = ReplicatedStorage:WaitForChild("Modules")
local Utils = ReplicatedStorage:WaitForChild("Utils")
local Events = ReplicatedStorage:WaitForChild("Events")

--// Required Modules
local RockModule = require(ClientModules:WaitForChild("RockModule"))
local UtilsModule = require(ClientModules:WaitForChild("UtilsModule"))
local SoundReplicator = require(ClientModules:WaitForChild("SoundReplicator"))
local HitboxModule = require(ClientModules:WaitForChild("HitboxModule"))
local HitboxClass = require(ClientModules:WaitForChild("HitboxClass"))
local Replicator = require(Utils:WaitForChild("Replicator"))
local ImpactFrames = require(ClientModules:WaitForChild("ImpactFrames"))
local VoxBreaker = require(ClientModules:WaitForChild("VoxBreaker"))
local BetweenRocks = require(ClientModules:WaitForChild("BetweenRocks"))
local CutsceneModule = require(ClientModules:WaitForChild("CutsceneModule"))
--// Misc
local Camera = workspace.CurrentCamera

--// Shaker
local CameraShaker = require(ClientModules:WaitForChild("CameraShaker"))
local camShake = CameraShaker.new(Enum.RenderPriority.Camera.Value, function(shakeCf)
	Camera.CFrame = Camera.CFrame * shakeCf
end)

camShake:Start()

--// Function's
local Skills = {}

local function CreateShutterDoors(Character : Model)
	local RootPart = Character:WaitForChild("HumanoidRootPart")
	local Doors = RestlessVfxs:FindFirstChild("Doors"):Clone()
	Doors.Name = "ShutterDoors"
	Doors:PivotTo(RootPart.CFrame)
	Doors.Parent = Character

	local Weld = Instance.new("Weld")
	Weld.Name = "ShutterDoorsWeld"
	Weld.Part0 = RootPart
	Weld.Part1 = Doors.PrimaryPart
	Weld.Parent = Doors.PrimaryPart

	Doors.LeftDoor.Trail.Enabled = true
	Doors.RightDoor.Trail.Enabled = true
	
	for index,value in Doors:GetDescendants() do
		if value:IsA("BasePart") then
			value:SetAttribute("OldTransparency",value.Transparency)
			value.Transparency = 1
		end
	end
	
	return Doors
end

function Skills:DoorCastAnimation(Character : Model)
	local Doors = CreateShutterDoors(Character)
	
	if Doors then
		local DoorsAnimationController = Doors:FindFirstChildWhichIsA("AnimationController")
		local Weld = Doors.PrimaryPart:FindFirstChildWhichIsA("Weld")
		
		SoundReplicator:PlaySound(RestlessSounds,"ShutterDoorsSummon",Character.PrimaryPart,true)
		
		for index,value in Doors:GetDescendants() do
			if value:IsA("BasePart") then
				TweenService:Create(value,TweenInfo.new(0.25),{Transparency = value:GetAttribute("OldTransparency") or 0}):Play()
			end
		end
		
		local DoorFailedAnimation = DoorsAnimationController:FindFirstChildWhichIsA("Animator"):LoadAnimation(RestlessAnimations.Door_ShutterDoorCast)
		DoorFailedAnimation:Play()
		Debris:AddItem(DoorFailedAnimation, DoorFailedAnimation.Length)

		DoorFailedAnimation:GetMarkerReachedSignal("Attack"):Wait()
		
		if Weld then
			if Character:GetAttribute("HittedSomeone") then -->> Hitted Someone
				local DoorHitAnimation = DoorsAnimationController:FindFirstChildWhichIsA("Animator"):LoadAnimation(RestlessAnimations.Door_ShutterDoorHit)
				DoorHitAnimation:Play()
				Debris:AddItem(DoorHitAnimation, DoorHitAnimation.Length)
			else
				SoundReplicator:PlaySound(RestlessSounds,"ShutterDoorsCast",Character.PrimaryPart,true)
			end
			
			Doors.PrimaryPart.Anchored = true
			Doors.LeftDoor.Trail.Enabled = false
			Doors.RightDoor.Trail.Enabled = false
			Doors.Parent = DebrisFolder
			
			Weld:Destroy()
			
			task.delay(1.5,function()
				UtilsModule.DisableAll(Doors)
				for index,value in Doors:GetDescendants() do
					if value:IsA("BasePart") then
						TweenService:Create(value,TweenInfo.new(1),{Transparency = 1}):Play()
					end
				end
				task.wait(2.5)
				Doors:Destroy()
			end)
			
			local Effect = RestlessVfxs.ShutterDoor.Attachment:Clone()
			Effect.Parent = Doors.Main
			UtilsModule.EmitAll(Effect)
		end
	end
end

Server-Side

Showcase:

4 Likes

Love this reply, the nesting though…

I can’t tell if it means this:
Screenshot 2025-04-29 122139
or something like this:
Screenshot 2025-04-29 122123
It’s kind of hard for goofball who’s been using that table method for very long to understand.
I’ve been hyper analyzing this to let sure I am right, but I’m still not sure.

Whatever fits you best honestly like you can do Robert and 0-3 or you can store all the abilities in robert. Either way works and is just preference.

I’d prefer using the abilities in one module through functions.

1 Like

Thank you, very cool. This!!!:point_up_2::point_up_2::point_up_2:

1 Like