I created a simple base class for creating a character that can have different abilities and I think the structure is pretty good there are 3 functions that must/should be implemented by derived classes and this is where the bulk of the work goes and then the base class provides some utility functions for some important things like playing sound and spawning particles.
function BaseCharacter.new()
local self = setmetatable({},BaseCharacter);
return self;
end
function BaseCharacter:PlaySound()
-- Use some AudioManager to play sound
end
function BaseCharacter:SpawnParticles()
-- Use some ParticleManager to spawn particles
end
function BaseCharacter:AbilityOne()
warn("BaseCharacter:AbilityOne() must be implemented!")
end
-- AbilityTwo(), AbilityThree(), ...
The problem is figuring out how to fit this into a client-server architecture Iād like to have good responsiveness (i.e when player makes an input something immediately happens) so the client would have to have some info about how the run the abilities, but then how would that get communicated to the server to do important things like replicating, damage, etc. Would the client and server need their own copies of the same class? Or is there a more efficient solution?
Usually how I handle this is that I just have a shared dictionary (in the form of a module) that both the client and server can reference for their own needs.
Say you have an ability that should play a sound when you do something, and deals some amount of damage when it hits a humanoid, you COULD send over damage dealt and the sound to play over a remote, but that could easily be tampered with and break your game.
But with a shared dictionary, you could just send over which ability you are going to use, and have the server lookup everything else.
Any tampering done inside of the module will not be replicated to the server, and is pretty much impervious to such.
This also solves the responsiveness issue, since you can just use whatever values you need in the table, and just update whatever you need on the client.
local AbilityDict = require(ā¦)
function Ability1()
PlaySound(AbilityDict.Ability1.SoundId) -- youll have to make sure to send a replication signal to other clients seperately, since if you play the sound on the server it will sound like it plays twice for the client who uses the ability
PlayAnimation(AbilityDict.Ability1.AnimationId)
FireAbilityRemote(AbilityName)
end
Server:
local AbilityDict = require(ā¦)
function OnAbility(Player, AbilityName)
local AbilityInUse = AbilityDict[AbilityName]
-- the rest of your logic
end
Obviously this doesnāt omit you from using other sanity checks (i.e. remote spam), but it does make it pretty much impossible for exploiters to just change values and send over bad data
If I may ask, whatās the reasoning for having an ability dictionary instead of having the corresponding values (damage, soundId, etc) in the script that uses them as variables?
I donāt have too much experience with this but Iāve always split up an ability up into two modules. One for the server, and one for the client. The server has a āuseā function and the client has two āuseā functions (one for the user, and one for when used by another player/character).
Splitting up the modules gives me good scalability (sorta easy to remove and add abilities) and it also allows me to do āclient-side onlyā abilities.
For example, if there is an ESP ability then other clients donāt need to replicate it (only the user will have ESP after all), but I can also do āserver-sideā abilities (ones that I want sanity checks for i.e creating a projectile that will do damage).
Also, not advising this, but (using my structure) you could have abilities stored in one ModuleScript, and whenever you require the module itāll return the version of the ability based on what context the script is running. I donāt do this but you technically could.
For example:
local RunService = game:GetService("RunService")
local AbilityServer = {}
local AbilityClient = {}
function AbilityServer.Use()
blahblah
end
function AbilityClient.Use()
blahblah
end
if RunService:IsClient() then
return AbilityClient
else
return AbilityServer
end