Hi,
I am working on a combat game and I tried using modules to handle server and client but I am having problems with the server and client running for all the players… for example, if player2 clicks to attack, it does the animation for player2 only but everyones hitbox will be on…
Here’s how the modules look like:
Client:
local Client = {}
Client.__index = Client
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local SoundService = game:GetService("SoundService")
local Debris = game:GetService("Debris")
local Players = game:GetService("Players")
local Util = ReplicatedStorage.Source.Util
local Modules = ReplicatedStorage.Source.Modules
local ContextActionService = require(Modules.ContextActionUtility)
local BridgeNet2 = require(Modules.BridgeNet2)
local bind = require(Util.bind)
local ComboBridge = BridgeNet2.ReferenceBridge("Combo")
local HitBridge = BridgeNet2.ReferenceBridge("Hit")
local PlaceHolderDebounce = false
function Client.new(Character: Model, Animations)
local self = setmetatable({},Client)
self.Character = Character
self.Humanoid = Character.Humanoid:: Humanoid
self.Root = Character.HumanoidRootPart
self.LoadedAnimations = {}
self.Settings = require(script.Parent.Settings)
self.GlobalSettings = require(script.Parent.Parent.GLOBAL_SETTINGS)
self.Sounds = SoundService[script.Parent.Name]
self.Animations = Animations
-- constants
self._combo = 1
self._lastClicked = tick()
HitBridge:Connect(function(EnemyHumanoid)
print("Hit", EnemyHumanoid.Name)
end)
self:Equip()
return self
end
function Client:Equip()
-- Load the animations
for _, anim in pairs(self.Animations:GetChildren()) do
self.LoadedAnimations[anim.Name] = self.Humanoid.Animator:LoadAnimation(anim)
self.LoadedAnimations[anim.Name].Priority = Enum.AnimationPriority.Action3 -- second highest...
end
-- Make sure Idle is looped
self.LoadedAnimations.Idle.Looped = true
-- Play the animations on equip
self.LoadedAnimations.Equip:Play()
self.LoadedAnimations.Idle:Play()
-- Bind action for mobile and pc
local function Attack(Action, InputState, InputType)
self:M1(Action,InputState,InputType)
end
bind(true,
self.GlobalSettings.M1_ACTION_NAME,
Attack,
self.GlobalSettings.M1_BUTTON_IMAGE,
self.GlobalSettings.M1_SLOT,
self.GlobalSettings.M1_ENUM
);
end
function Client:M1(Action, InputState, InputType)
if InputState ~= Enum.UserInputState.Begin then
return
end
if self.Humanoid.Health <= 0 then
return
end
if not PlaceHolderDebounce then
PlaceHolderDebounce = true
if self._lastAnimation and self._lastAnimation.IsPlaying then
-- stops the last animation
self._lastAnimation:Stop()
end
local comboString = "Combo"..tostring(self._combo)
if tick() - self._lastClicked >= self.GlobalSettings["COMBO_RESET"] then
-- resets the combo under a certain time
self._combo = 1
end
coroutine.wrap(function(...) -- enables the hitbox without delaying the rest of the script
task.wait(self.Settings["START_HTIBOX_TIME"])
ComboBridge:Fire(comboString) -- hitbox is done on server
end)()
self._lastAnimation = self.LoadedAnimations[comboString]
self._lastClicked = tick()
self.LoadedAnimations[comboString]:Play() -- play the next combo animation
self.Humanoid.WalkSpeed = self.GlobalSettings.M1_WALKSPEED -- set the walkspeed to hitting walkspeed
task.wait(self.Settings["M1_COOLDOWN"]) -- cooldown
if self._combo == 3 then -- last animation = stop moving
self.Humanoid.WalkSpeed = 0
task.wait(self.GlobalSettings.LAST_COMBO_COOLDOWN)
end
self.Humanoid.WalkSpeed = 16 -- change to normal speed
if self._combo < self.Settings.COMBOS then -- adds 1 to combo so it actually changes anims
self._combo += 1
else
self._combo = 1
end
PlaceHolderDebounce = false
end
end
return Client
Server
--[[
TODO:
- Handle SFX on server-side
- Handle Hitbox on server-side (better connection)
- Make the sword look cooler.
]]
local SoundService = game:GetService("SoundService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local Debris = game:GetService("Debris")
local Util = ReplicatedStorage.Source.Util
local Modules = ReplicatedStorage.Source.Modules
local BridgeNet2 = require(Modules.BridgeNet2)
local RaycastHitboxV4 = require(Modules.RaycastHitboxV4)
local playSound = require(Util.playSound)
local ComboBridge = BridgeNet2.ReferenceBridge("Combo")
local HitBridge = BridgeNet2.ReferenceBridge("Hit")
local Server = {}
Server.__index = Server
function Server.new(Character: Model, Tool: Tool)
local self = setmetatable({},Server)
self.Character = Character
self.Humanoid = Character:WaitForChild("Humanoid"):: Humanoid
self.Root = Character:WaitForChild("HumanoidRootPart")
self.Tool = Tool:Clone()
self.Player = Players:GetPlayerFromCharacter(Character)
self.Settings = require(script.Parent.Settings)
self.Sounds = SoundService[script.Parent.Name]
self.Particles = ReplicatedStorage.Instances.Characters.Reaper.Particles
self.Speed = 10
self.Force = 80000
self.Tool.Parent = self.Player.Backpack
self.Humanoid:EquipTool(self.Tool)
playSound(self.Sounds["Equip"],self.Root)
self.Hitbox = RaycastHitboxV4.new(self.Tool["Handle"])
self.Hitbox.Visualizer = true
ComboBridge:Connect(function(Player,Combo: string)
print("Hitstart from"..Player.Name)
local HitboxTime = self.Settings[string.upper(Combo)]
self.Hitbox:HitStart(HitboxTime)
playSound(self.Sounds[Combo],self.Root)
end)
self.Hitbox.OnHit:Connect(function(hit, humanoid)
self:Hit(hit,humanoid)
end)
return self
end
function Server:M1(Combo: string)
end
function Server:Hit(hit,humanoid)
local enemyRoot = hit.Parent:FindFirstChild("HumanoidRootPart")
if humanoid == self.Humanoid then
return
end
if not enemyRoot then
return
end
local TotalForce = self.Force
local Knockback1 = Instance.new("LinearVelocity")
Knockback1.VectorVelocity = self.Root.CFrame.LookVector * self.Speed
Knockback1.MaxForce = TotalForce
Knockback1.Attachment0 = self.Root.RootAttachment
local Knockback2 = Instance.new("LinearVelocity")
Knockback2.VectorVelocity = self.Root.CFrame.LookVector * self.Speed
Knockback2.MaxForce = TotalForce
Knockback2.Attachment0 = enemyRoot:FindFirstChild("RootAttachment")
Knockback1.Parent = self.Root
Knockback2.Parent = enemyRoot
local HitParticle = self.Particles["Hit"].Attachment:Clone()
HitParticle.Parent = enemyRoot
HitParticle:FindFirstChild("ParticleEmitter"):Emit(1)
Debris:AddItem(Knockback1, 0.1)
Debris:AddItem(Knockback2, 0.1)
Debris:AddItem(HitParticle, 0.3)
playSound(self.Sounds["Hit"], hit, math.random(10,20)/10)
humanoid:TakeDamage(self.Settings.M1_DAMAGE)
end
return Server
How it is being tested:
Goes into server first:
local ChangeClass = BridgeNet2.ReferenceBridge("ChangeClass")
local Directories = {
ServerScriptService.Source.Services
}
Knit.Start():andThen(function()
print("Server: Knit")
end)
function onPlayerAdded(player: Player)
player.CharacterAdded:Connect(function(Character: Model)
ChangeClass:Fire(player, "Reaper")
local Module = require(ReplicatedStorage.Source.Classes.Characters.Reaper).new(Character)
Module:Init()
end)
end
From a remote it goes into client:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Modules = ReplicatedStorage.Source.Modules
local BridgeNet2 = require(Modules.BridgeNet2)
local ChangeClass = BridgeNet2.ReferenceBridge("ChangeClass")
ChangeClass:Connect(function(class)
local Module = require(ReplicatedStorage.Source.Classes.Characters.Reaper).new(game.Players.LocalPlayer.Character)
Module:Init()
end)