Feedback on this Tool Class System?

This is my first time using OOP, so I need some feedback to see if there’s anything that needs to be changed. This is a server and client script, which handles both the server and client, for better replication.

local ToolClass = {} -- Programmer : Galicate --// SERVICES //-- local PLAYER_SERVICE = game:GetService("Players") local REPLICATED_STORAGE = game:GetService("ReplicatedStorage") local RUN_SERVICE = game:GetService("RunService") local DEBRIS = game:GetService("Debris") --// METATABLES //-- ToolClass.__index = ToolClass --// CONSTANTS //-- local RemoteInstance = REPLICATED_STORAGE:WaitForChild("RemoteInstances") local ToolEvent = RemoteInstance:WaitForChild("toolEvent") local EventHandler = require(REPLICATED_STORAGE:WaitForChild("EventHandler")) local ItemDataFolder = REPLICATED_STORAGE:WaitForChild("ItemData") local LocalPlayer = PLAYER_SERVICE.LocalPlayer or nil --// VARIABLES //-- local toolClasses = {} local toolCache = {} --// LOCAL FUNCTIONS //-- local function init(...)	--// Load All Sub Class Tool Modules Into toolClasses	local subClasses = script:GetChildren()	for i, v in pairs(subClasses) do	if not v:IsA("ModuleScript") then continue end	toolClasses[v.Name] = require(v)	end	return end local function createEvent(self, eventName)	local newEvent = EventHandler.Event.new(eventName)	newEvent:Connect(function(...)	if RUN_SERVICE:IsServer() then	--// Fire To All Clients	ToolEvent:FireAllClients(self.UID, eventName, self.Owner, self.ToolId, self.Model)	elseif RUN_SERVICE:IsClient() and self.Owner == LocalPlayer then	--// Fire To Server	ToolEvent:FireServer(self.UID, eventName, self.Owner, self.ToolId, self.Model)	end	end)	return newEvent end local function onServerEvent(player, ...)	for i, v in pairs(PLAYER_SERVICE:GetPlayers()) do	if v ~= player then	--// Fire Tool Event To Client	ToolEvent:FireClient(v, ...)	end	end end local function onClientEvent(UID, eventName, playerInstance, toolId, toolModel)	if eventName == "Created" then	ToolClass.new(playerInstance, toolId, UID, toolModel)	end	local tool = toolCache[UID]	if tool then	if eventName == "Equipped" then	tool:Equip()	elseif eventName == "Unequipped" then	tool:Unequip()	elseif eventName == "Activated" then	tool:Activate()	elseif eventName == "Deactivated" then	tool:Deactivate()	elseif eventName == "Destroyed" then	tool:Destroy()	end	else	warn("Cache[" .. tostring(UID) .. "] is missing or nil.")	end end local function rollTrack(Tracks)	return Tracks[math.random(#Tracks)] end --// GLOBAL FUNCTIONS //-- function ToolClass.new(playerInstance, toolId, UID, toolModel)	--// Check For Errors	if not playerInstance then warn("Arg 1 nil") return end	if not toolClasses[toolId] then warn("Arg 2 nil") return end	if not UID then warn("Arg 3 nil") return end	if not toolModel then warn("Arg 4 nil") return end	local self = toolClasses[toolId].new(playerInstance, UID)	setmetatable(self, ToolClass)	--// Set Default Parameters	self.Owner = playerInstance	self.ToolId = toolId	self.UID = UID	self.Model = toolModel	--// Create Tool Parameters	self.ItemData = require(ItemDataFolder:FindFirstChild(toolId)) or {}	self.IsEquipped = false	self.Active = false	--// Create Events	self.Created = createEvent(self, "Created")	self.Equipped = createEvent(self, "Equipped")	self.Unequipped = createEvent(self, "Unequipped")	self.Activated = createEvent(self, "Activated")	self.Deactivated = createEvent(self, "Deactivated")	self.Destroyed = createEvent(self, "Destroyed")	--// Add Tool To Cache	toolCache[self.UID] = self	--// Fire Created Event	self.Created:Fire(self)	if toolModel then	toolModel:GetPropertyChangedSignal("Parent"):Connect(function()	if toolModel.Parent == nil then	self:Destroy()	end	end)	end	return self end function ToolClass:Equip()	if self.Owner then	self:UnloadAnimations()	end	self.Equipped:Fire()	self.IsEquipped = true	self:LoadAnimations()	warn(self.Owner.Name .. " has equipped " .. self.Model.Name)	toolClasses[self.ToolId].Equip(self) end function ToolClass:Unequip()	self.Unequipped:Fire()	self:Deactivate()	self.IsEquipped = false	warn(self.Owner.Name .. " has unequipped " .. self.Model.Name)	toolClasses[self.ToolId].Unequip(self) end function ToolClass:Activate()	if not self.IsEquipped then return end	self.Active = true	self.Activated:Fire()	warn(self.Owner.Name .. " has activated " .. self.Model.Name)	toolClasses[self.ToolId].Activate(self) end function ToolClass:Deactivate()	self.Active = false	self.Deactivated:Fire()	warn(self.Owner.Name .. " has deactivated " .. self.Model.Name)	toolClasses[self.ToolId].Deactivate(self) end function ToolClass:InputBegan(userInput)	if not self.IsEquipped then return end	warn(" [" .. self.Owner.Name .. "].InputBegan = " .. tostring(userInput)) end function ToolClass:InputEnded(userInput)	if not self.IsEquipped then return end	warn(" [" .. self.Owner.Name .. "].InputEnded = " .. tostring(userInput)) end function ToolClass:Destroy()	toolCache[self.UID] = nil	DEBRIS:AddItem(self.Model)	self.Destroyed:Fire()	toolClasses[self.ToolId].Destroy(self) end function ToolClass:LoadAnimations()	local animationController = self.Owner.Character.Humanoid:FindFirstChild("Animator")	if not animationController then return end	self.AnimationController = animationController	if self.Animations == nil then	local animations = {}	for i, v in pairs(self.ItemData.Animations) do	local tracks = {}	for i2, v2 in pairs(v) do	local animationObject = self.Model:FindFirstChild(i.."_"..tostring(i2))	if not animationObject then	animationObject = Instance.new("Animation")	animationObject.AnimationId = "rbxassetid://"..v2.Id	animationObject.Name = i.."_"..tostring(i2)	animationObject.Parent = self.Model	end	tracks[i2] = {	Track = animationController:LoadAnimation(animationObject),	Weight = v2.Weight,	AnimationObject = animationObject,	}	end	animations[i] = {	Tracks = tracks,	ActiveTrack = nil	}	end	self.Animations = animations	else	local AanimationController = self.Owner.Character.Humanoid:FindFirstChild("Animator")	for i, Data in pairs(self.Animations) do	for i2 = 1, #Data do	local TrackData = Data[i2]	if TrackData.Track then	TrackData.Track:Destroy()	end	TrackData.Track = animationController:LoadAnimation(TrackData.AnimationObject)	end	end	end end function ToolClass:UnloadAnimations()	if self.Animations then	for i, v in pairs(self.Animations) do	for TrackIndex = 1, #v.Tracks do	local TrackData = v.Tracks[TrackIndex]	if TrackData.Track then	TrackData.Track:Stop()	TrackData.Track:Destroy()	end	end	end	self.AnimationController = nil	end end function ToolClass:StopAnimation(animationName)	local animationData = self.Animations[animationName]	if animationData and animationData.ActiveTrack then	animationData.ActiveTrack:Stop()	if RUN_SERVICE:IsClient() and self.Owner == LocalPlayer then	ToolEvent:FireServer(self.ID, "StopAnimation", animationName)	elseif RUN_SERVICE:IsServer() then	ToolEvent:FireAllClients(self.ID, "StopAnimation", animationName)	end	else	warn("Warning: " .. self.ToolId .. ".Animations[" .. tostring(animationName) .. "] is missing or nil.")	end end function ToolClass:StopAllAnimations()	for i, v in pairs(self.Animations) do	if v and v.ActiveTrack then	v.ActiveTrack:Stop()	end	end	if RUN_SERVICE:IsClient() and self.Owner == LocalPlayer then	ToolEvent:FireServer(self.ID, "StopAllAnimations")	elseif RUN_SERVICE:IsServer() then	ToolEvent:FireAllClients(self.ID, "StopAllAnimations")	end end function ToolClass:PlayAnimation(animationName, isLooped)	local animationData = self.Animations[animationName]	if animationData then	local Data = rollTrack(animationData.Tracks)	animationData.ActiveTrack = Data.Track	animationData.Looped = isLooped or false	animationData.ActiveTrack:Play(nil, Data.Weight)	if RUN_SERVICE:IsClient() and self.Owner == LocalPlayer then	ToolEvent:FireServer(self.ID, "PlayAnimation", animationName)	elseif RUN_SERVICE:IsServer() then	ToolEvent:FireAllClients(self.ID, "PlayAnimation", animationName)	end	return animationData.ActiveTrack	else	warn("Warning: " .. self.ToolId .. ".Animations[" .. tostring(animationName) .. "] is missing or nil.")	end end --// EVENT CONNECTIONS //-- if RUN_SERVICE:IsServer() then	ToolEvent.OnServerEvent:Connect(onServerEvent) elseif RUN_SERVICE:IsClient() then	ToolEvent.OnClientEvent:Connect(onClientEvent) end --// INIT //-- init() return ToolClass 

P.S: I feel like you just copy pasted the class from this post and modified it a bit.

I replied to the wrong person lol.

I think the functionality is fine because it does what it looks like you want it to do, but there are some small naming things I would change up about this. If you’re not worried about style, because it is subjective after all, feel free to disregard the rest of this.(btw all my advice comes from the Roblox Lua Style Guide)

Instead of having variables that use get Roblox services be notated as a constant (e.g MY_CONSTANT) I would use pascal case instead. Then I would change the actual constants to LOUD_SNAKE_CASE.

local UserInputService = game:GetService("UserInputService") local MY_CONSTANT = 10 

Another change I would make stylistically is to change ToolClass to just Tool or PlayerTool, something along those lines. This is because a module that’s being used as a class usually doesn’t need Class at the end of the name. Although, it wouldn’t hurt keeping class for explicitness. Also, instead of playerInstance you can change it to player: Player. When you try to use the constructor in another file it’ll show that the parameter’s type is supposed to be Roblox’s Player type. I could go on about really small style things but it’s really up to you and it won’t matter if no one else is going to use it.

Cheers.

1 Like