Player Ability System

This is my most recent iteration on a more advanced ability system, all of the ability systems were just made by trial and error, because there is not any resources on similar systems (from what I’ve researched).

The system currently is just a demo ability framework, no actual abilities have been made (just a sword that you hold).

I’m looking for advice on how I can have more freedom with communication between server and client without having a big bowl of spaghetti code.

I’m also interested in hearing about other methods of ability systems that could be better than what I currently have.

example on a ability that would require this
An ability that spawns up to 3 fireballs around a player, the client would need to control these fireballs for smooth movement, and the client needs to be updated whenever fireballs are removed or added on the server (child added could technically work but I think it would be better with remotes).

AbilityService (ServerScriptService)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local HTTPService = game:GetService("HttpService")
local RunService = game:GetService("RunService")
local AbilityInfo = require(ReplicatedStorage.AbilityInfo)
local Maid = require(ReplicatedStorage.Maid)

local replicateEquipRE = ReplicatedStorage.ReplicateEquipRE
local replicateUnequipRE = ReplicatedStorage.ReplicateUnequipRE
local activateRE = ReplicatedStorage.ActivateRE
local players = {}

local abilityFunctions = {
	MeleeEquip = function(ability)
		print("equip")
		
		local weapon = ability.Model:Clone()
		weapon.Parent = ability.Character
		ability.Weapon = weapon
		
		ability.Maid:GiveTask(weapon)
		return weapon
	end,
	MeleeUnequip = function(ability)
		print("unequip")
	end,
	MeleeActivate = function(ability)
		print("Activate")
	end
}

local AbilityService = {}

function AbilityService.GetPlayerAbilities(player)
	local abilities = players[player] or {}
	players[player] = abilities
	return abilities
end

function AbilityService.EquipAbility(player, character, abilityName, index)
	local abilities = AbilityService.GetPlayerAbilities(player)
	local abilityInfo = assert(AbilityInfo[abilityName], "No Ability Info")
	assert(not abilities[index], "Slot is taken")
	
	local ability = setmetatable({
		Player = player,
		Character = character,
		Index = index,
		Maid = Maid.new(),
		Active = true,
	}, {__index = abilityInfo}) -- set the abilities metatable to the abilityInfo
	
	abilities[index] = ability
	
	local args = abilityFunctions[ability.Functions.Equip](ability)
	replicateEquipRE:FireClient(player, abilityName, index, args)
end

function AbilityService.UnequipAbility(player, index)
	local abilities = AbilityService.GetPlayerAbilities(player)
	local ability = assert(abilities[index], "No Ability in Slot" .. index)
	local args = abilityFunctions[ability.Functions.Unequip](ability)
	ability.Active = false
	ability.Maid:Destroy()
	abilities[index] = nil
	replicateUnequipRE:FireClient(player, index)
end

function AbilityService.ClearAbilities(player)
	local abilities = AbilityService.GetPlayerAbilities(player)
	
	for i, ability in pairs(abilities) do
		AbilityService.UnequipAbility(player, i)
	end
end

activateRE.OnServerEvent:Connect(function(player, index, ...)
	local abilities = AbilityService.GetPlayerAbilities(player)
	local ability = abilities[index]
	
	if ability then
		abilityFunctions[ability.Functions.Activate](ability)
	end
end)

return AbilityService
AbilityController (StarterPlayerScripts)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")
local HTTPService = game:GetService("HttpService")
local RunService = game:GetService("RunService")
local AbilityInfo = require(ReplicatedStorage.AbilityInfo)
local Maid = require(ReplicatedStorage.Maid)

local player = game.Players.LocalPlayer
local replicateEquipRE = ReplicatedStorage.ReplicateEquipRE
local replicateUnequipRE = ReplicatedStorage.ReplicateUnequipRE
local activateRE = ReplicatedStorage.ActivateRE
local abilities = {}

local slotsToKeybinds = {
	[1] = Enum.KeyCode.One,
	[2] = Enum.KeyCode.Two
}

local abilityFunctions = {
	MeleeEquip = function(ability, weapon)
		print("equip")
		ability.Weapon = weapon
		print(weapon.Name)
	end,
	MeleeUnequip = function(ability)
		print("unequip")
	end,
	MeleeActivate = function(ability)
		print("Activate")
		activateRE:FireServer(ability.Index)
	end
}

local AbilityController = {}

function AbilityController.EquipAbility(abilityName, index, ...)
	local abilityInfo = assert(AbilityInfo[abilityName], "No Ability Info")
	assert(not abilities[index], "Slot is taken")
	
	local ability = setmetatable({
		Player = player,
		Character = player.Character,
		Index = index,
		Maid = Maid.new(),
		Active = true,
	}, {__index = abilityInfo}) -- set the abilities metatable to the abilityInfo
	
	abilities[index] = ability
	
	abilityFunctions[ability.Functions.Equip](ability, ...)
end

function AbilityController.UnequipAbility(index)
	local ability = assert(abilities[index], "No Ability in Slot" .. index)
	local args = abilityFunctions[ability.Functions.Unequip](ability)
	ability.Active = false
	ability.Maid:Destroy()
	abilities[index] = nil
end

replicateEquipRE.OnClientEvent:Connect(AbilityController.EquipAbility)
replicateUnequipRE.OnClientEvent:Connect(AbilityController.UnequipAbility)


UserInputService.InputBegan:Connect(function(input, proc)
	if not proc then
		for i, ability in pairs(abilities) do
			local keybind = slotsToKeybinds[i]
			if input.KeyCode == keybind then
				abilityFunctions[ability.Functions.Activate](ability)
			end
		end
	end
end)

return AbilityController
Ability Info Module (ReplicatedStorage)
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local AbilityInfo = {}

AbilityInfo.Sword = {
	Functions = {
		Equip = "MeleeEquip",
		Activate = "MeleeActivate",
		Unequip = "MeleeUnequip"
	},
	Model = ReplicatedStorage.ClassicSword
}

return AbilityInfo

Demo (includes a script and local script that require the modules).
QuoteoryAbilityDemo.rbxl (28.7 KB)

8 Likes