CLIENT/SERVER Code Review

Overall, I have a setup for my game as current, and while I don’t feel like restarting to do anything to it, I do soon plan on creating a side project so that I can use the new knowledge I have in a new scope. Right now I’m in combat, but a lot of things will be setup similarly so I was just curious if this looks like a good setup.

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")

local ActionContainer = ReplicatedStorage.Modules.ActionContainer
local Shared = ReplicatedStorage.Modules.Shared

local BridgeNet = require(ReplicatedStorage.Libraries.BridgeNet2)
local Constants = require(ReplicatedStorage.Modules.WhitelistedInputs)

local ActionHandler_C = {
    ["CombatActions_C"] = require(ActionContainer.CombatActions_C),
    ["MovementActions_C"] = require(ActionContainer.MovementActions_C)
}

local function FuncSender(InputData : { })
        local Actionnare, CurrentAction    
        CurrentAction = function()
        for ActionsIndex, PickedClass in pairs(ActionHandler_C) do
            if type(PickedClass) == "table" then
                if PickedClass[InputData.Key] then                    
                    Actionnare = PickedClass[InputData.Key]
                    return require(ActionContainer[ActionsIndex].Functions_C)[Actionnare["Name"]] -- Directs me to the Action for a certain Action Subset, whether being Movement Actions or Combat Actions
                    end
                end
            end
        end
    
    local Action = CurrentAction()
    
    Action()
end

local InputClient = {}

function InputClient.FunctionSender(Input: InputObject, GameProcesssedEvents : BoolValue )
    
    if GameProcesssedEvents then    return end
        
    if not table.find(Constants.WhitelistedUserInputTypes, Input.UserInputType) then return end
    if not table.find(Constants.WhitelistedKeybinds, Input.KeyCode) then return end
    
    FuncSender(({
        Type = Input.UserInputType,
        State = Input.UserInputState,
        Key = (Input.KeyCode == Enum.KeyCode.Unknown and Input.UserInputType or Input.KeyCode)
        
    }))    
end

return InputClient

Client-Sided setup, explaination commented on what the random function is doing there

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")

local VFXModules = ReplicatedStorage.Modules.VFXModules
local CombatActions_CM = ReplicatedStorage.Modules.Shared.CombatActions_CM
local ClientVFX = ReplicatedStorage.RE.ClientVFX

local BridgeNet = require(ReplicatedStorage.Libraries.BridgeNet2);
local PlrControllerModule = require(ReplicatedStorage.Modules.Client);
local HitboxModule_C = require(CombatActions_CM.HitboxModule_C);
local ClientSideBehavior = require(ReplicatedStorage.Modules.ClientSideBehavior)

local BaseHitboxTable = {}
BaseHitboxTable.OriginalPart = nil
BaseHitboxTable.HitboxSize = Vector3.new(3.5, 4.25, 4)
BaseHitboxTable.Duration = nil

--local ClientVFX = BridgeNet.ClientBridge("VFXController") 

local VFXFunc = function(seen)
    --[[if WrapperPlayer:GetState(seen, "P-Blocking") then
        Animation:AdjustSpeed(0)
    end]]

    local MainModuleBase = VFXModules:FindFirstChild("CombatVFXBegin")

    local RequiredModule = require(MainModuleBase)
    RequiredModule["HitReaction"](seen)    
    return seen:FindFirstChild("HumanoidRootPart")
end

UserInputService.InputBegan:Connect(PlrControllerModule.FunctionSender) 
UserInputService.InputEnded:Connect(PlrControllerModule.FunctionSender)

What actually happens on the client

VFXFunction is primarily for the reactive VFX system I’ve implemented.

Afterwards, when the Action is found and done, the client side functionality is done over here
image
, in the Functions script, and most function are setup like this

Server is very similar expect the fact that it instead carries the functionality and introduces the State System which I’m not completely sure is needed to be shown here, but I will show the server-side of things, and if you’d allow me I could go further in depth about the state system.

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")

local Modules_S = ServerStorage.Modules
local ClientRemote = ReplicatedStorage.RE.ClientRemote
local Shared = ReplicatedStorage.Modules:WaitForChild("Shared")

local Things = require(Shared.DataHandler.playerdataHandler)
local BridgeNet = require(ReplicatedStorage.Libraries.BridgeNet2)
local BehaviorControl = require(ServerStorage.Modules.BehaviorControl)

local ActionHandler_Server = ServerStorage.Modules.ActionHandler_S

local CA_S = require(Modules_S.ActionHandler_S.CombatActions_S)
local MA_S = require(Modules_S.ActionHandler_S.MovementActions_S)

local ActionHandler_S = {
    ["CombatActions_S"] = CA_S;
    ["MovementActions_S"] = MA_S;
}

function ActionHandler_S:Init(Params: {})

    local Action, CurrentAction

    Action, CurrentAction = {}, function()
        for Index, Actions in pairs(ActionHandler_S) do
            if type(Actions) == "table" then
                if not Actions[Params.Input] then continue end
                return require(ActionHandler_Server[Index])[Params.Input]        
            end
        end
    end
    local Actionnare = CurrentAction()

    Actionnare(Params)
end


--local ClientRemote = BridgeNet.ServerBridge("ClientRemote")

ClientRemote.OnServerEvent:Connect(function(Player, Params)    
    ActionHandler_S:Init(Params)
end)


image

would appreciate the feedback so much, thank you.

2 Likes

unfortunately, I will be bumping

this is actually a pretty solid setup, especially if it’s going to be a larger scale project in the future.
scalability is very important when it comes to development and things of that nature.

2 Likes

Good overall setup, one thing I would suggest is using Luau typing to create Interfaces for each module and using design patterns to facilitate the communication between those modules. I would also suggest you use the concept of Dependency Injection as it ensure you use the interfaces with each module correctly and allows you to change implementations very quickly without any major issues and changes. But overall, I think the structure is a good starting point and is great!