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)