I can say there is in fact a better way, called be lazy and use Roblox’s default tools and some remotes. However this doesn’t work for all games. In my game for example: Project Blu Development - Roblox I’m using completely custom characters, and the player doesn’t have a character under their Player.Character property. I’m not even using humanoids so I couldn’t even activate the default tool object and had to create my own object, which ran into a ton of problems on the server and on other clients because of replication issues.
The very first issue I had, was source code control. You just have to suck it up and put tool data where it can be stolen, otherwise its not going to be a pleasant scripting experience for you.
After that I had the issue of wanting to have a Tool Model on the Server and Client, that was a real hassle to work with, sometimes one or the other wouldn’t get welded and would fall to the deletion plane and the game broke. Sometimes things didn’t replicate properly. Tried doing server only, same problem. Eventually found out all I really needed was having the tool model on the client only, not the server, which is really all you need.
Third, and biggest problem, was telling the server (and other clients) what Player1 was doing. For simplicity I just replicated every thing the player did, and if was something important event like Equipping, Uneqipping, Activating, Deactivating the tool, everyone ran their tools function for that specific event. This resulted in some really good behaviors that nullified a lot of replication issues I was having.
Here’s some code from my game, I don’t expect you to full understand it as parts of the code is missing, but the concept should be clear enough to get you a general idea.
ToolClass (The base object)
---------------------
-- Roblox Services --
---------------------
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local LocalPlayer = Players.LocalPlayer
------------
-- Reverb --
------------
local Reverb = require(ReplicatedStorage:WaitForChild("Reverb"))
local Utilities = require(Reverb.Utility)
-------------
-- Network --
-------------
local ToolEvent = script:WaitForChild("ToolEvent")
-------------
--
-------------
local Cache = {}
local DefaultTransitionTime = 0.1
--------------------------------
-- Class Functions, Variables --
--------------------------------
local Class = {}
Class.__index = Class
local function CreateEvent(Tool, EventName)
local Event = Utilities.NewEvent(EventName)
Event:Connect(function(...)
if RunService:IsClient() and Players[Tool.Owner.Name] == LocalPlayer then
ToolEvent:FireServer(Tool.ID, EventName, ...)
elseif RunService:IsServer() then
ToolEvent:FireAllClients(Tool.ID, EventName, ...)
end
end)
return Event
end
function Class.new(ID, Owner, ToolModel, ToolGrip, AnimationList)
local Tool = setmetatable({
Name = (ToolModel and ToolModel.Name) or "Tool_" .. tostring(ID),
ID = ID, -- To keep track of Tools
Owner = Owner, -- What entity (Character/Rig) owns the model.
Handle = ToolModel:FindFirstChild("Handle") or ToolModel.PrimaryPart,
ToolModel = ToolModel, -- ToolModel
ToolGrip = ToolGrip, -- ToolGrip (Motor6D/Weld)
AnimationList = AnimationList or {}, -- The animations the tool has access to.
IsEquipped = false,
InputDirection = Vector3.new(),
}, Class)
-- Fired when the Tool is equipped.
Tool.Equipped = CreateEvent(Tool, "Equipped")
-- Fired when the Tool is unequipped.
Tool.Unequipped = CreateEvent(Tool, "Unequipped")
-- Fired when Tool.Active = true
Tool.Activated = CreateEvent(Tool, "Activated")
-- Fired when Tool.Active = false
Tool.Deactivated = CreateEvent(Tool, "Deactivated")
-- Fired when the Tool is dropped.
Tool.Dropped = CreateEvent(Tool, "Dropped")
-- To allow for custom events to be replicated.
Tool.CustomEvent = Utilities.NewEvent("CustomEvent")
Tool.CustomEvent:Connect(function(EventName, DataToSend, LocalEvent)
if Tool[EventName] == nil then warn("Warning: There is no method called " .. tostring(EventName) .. "for " .. Tool.Name) return end
if LocalEvent then return end
if RunService:IsClient() and Players[Tool.Owner.Name] == LocalPlayer then
ToolEvent:FireServer(Tool.ID, "CustomEvent", {EventName, DataToSend})
elseif RunService:IsServer() then
ToolEvent:FireAllClients(Tool.ID, "CustomEvent", {EventName, DataToSend})
end
end)
Tool.Destroyed = Utilities.NewEvent("Tool.Destroyed")
Cache[ID] = Tool -- Add the Tool to the Cache for easy tracking.
-- Keeps things clean in the event the ToolModel's parent is deleted, nice little auto clean up. ^w^
if ToolModel then
ToolModel:GetPropertyChangedSignal("Parent"):Connect(function()
if ToolModel.Parent == nil then
Tool:Destroy()
end
end)
end
return Tool
end
function Class:Equip(NewOwner)
if NewOwner == nil then warn("Warning: cannot equip tool to a nil owner.") return end
if NewOwner == self.Owner then return end
-- Unload animations first.
if self.Owner then
self:UnloadAnimations()
end
-- Set ToolGrip's Part1 next.
if self.ToolModel and self.ToolGrip then
self.ToolGrip.Part1 = NewOwner.Model.HumanoidRootPart
self.ToolModel.PrimaryPart.Anchored = false
end
self.Owner = NewOwner -- Set owner next.
self:LoadAnimations() -- Then load animations.
self.Equipped:Fire(NewOwner.UID)
self.IsEquipped = true
-- warn(self.Owner.Name .. " has equipped " .. self.Name)
end
function Class:Unequip()
self.Unequipped:Fire()
self:Deactivate()
self.IsEquipped = false
-- warn(self.Owner.Name .. " has unequipped " .. self.Name)
end
function Class:Drop()
self:Deactivate()
self.IsEquipped = false
self:UnloadAnimations()
-- Set ToolGrip's Part1 to nil since there is no Owner.
if self.ToolModel then
if self.ToolGrip then
self.ToolGrip.Part1 = nil
end
self.ToolModel.PrimaryPart.Anchored = true
local _, Position, Normal = Utilities.RaycastWithWhiteList(self.ToolModel.PrimaryPart.Position, Vector3.new(0, -100, 0), {game.Workspace.Map})
Position = Position + Vector3.new(0, 1, 0)
local CF = CFrame.new(Position, Position + Normal) * CFrame.Angles(math.rad(-0), 0, 0)
self.ToolModel:SetPrimaryPartCFrame(CF)
if RunService:IsServer() then
self.ToolModel.Parent = game.Workspace.RayIgnore.ToolModels
end
end
self.Dropped:Fire(self.Owner)
self.Owner = nil
end
--------------------
-- Input Handling --
--------------------
function Class:Activate()
if self.Owner == nil or not self.IsEquipped then return end
self.Active = true
self.Activated:Fire()
-- warn(self.Owner.Name .. " has activated " .. self.Name)
end
function Class:Deactivate()
self.Active = false
self.Deactivated:Fire()
-- warn(self.Owner.Name .. " has deactivated " .. self.Name)
end
function Class:InputBegan(Input)
if self.Owner == nil or not self.IsEquipped then return end
-- warn(" [" .. self.Owner.Name .. "].InputBegan = " .. tostring(Input))
end
function Class:InputEnded(Input)
if self.Owner == nil or not self.IsEquipped then return end
-- warn(" [" .. self.Owner.Name .. "].InputBegan = " .. tostring(Input))
end
------------------------
-- Animation Handling --
------------------------
function Class:LoadAnimations()
if self.Owner == nil then return end
local AnimationController = self.Owner.AnimationController
if AnimationController == nil then return end
self.AnimationController = AnimationController
if self.Animations == nil then
-- warn("Loading animations for", self.Name, ".")
local Animations = {}
for Index, Track in pairs(self.AnimationList) do
local Tracks = {}
for TrackIndex, TrackData in pairs(Track) do
local AnimationObject = self.ToolModel:FindFirstChild(Index.."_"..tostring(TrackIndex))
if not AnimationObject then
AnimationObject = Instance.new("Animation")
AnimationObject.AnimationId = "rbxassetid://" .. TrackData.Id
AnimationObject.Name = Index.."_"..tostring(TrackIndex)
AnimationObject.Parent = self.ToolModel
end
Tracks[TrackIndex] = {
Track = AnimationController:LoadAnimation(AnimationObject),
Weight = TrackData.Weight,
AnimationObject = AnimationObject,
}
end
Animations[Index] = {
Tracks = Tracks,
ActiveTrack = nil
}
end
self.Animations = Animations
else
local AnimationController = self.Owner.AnimationController
for Index, Data in pairs(self.Animations) do
for TrackIndex = 1, #Data do
local TrackData = Data[TrackIndex]
if TrackData.Track then
TrackData.Track:Destroy()
end
TrackData.Track = AnimationController:LoadAnimation(TrackData.AnimationObject)
end
end
end
end
function Class:UnloadAnimations()
if self.Animations then
for Index, Data in pairs(self.Animations) do
for TrackIndex = 1, #Data.Tracks do
local TrackData = Data.Tracks[TrackIndex]
if TrackData.Track then
TrackData.Track:Stop()
TrackData.Track:Destroy()
end
end
end
self.AnimationController = nil
end
end
local function RollTrack(Tracks)
return Tracks[math.random(1, #Tracks)]
end
function Class:PlayAnimation(AnimationName, IsLooped)
if self.Owner == nil then return end
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 RunService:IsClient() and Players[self.Owner.Name] == LocalPlayer then
ToolEvent:FireServer(self.ID, "PlayAnimation", AnimationName)
elseif RunService:IsServer() then
ToolEvent:FireAllClients(self.ID, "PlayAnimation", AnimationName)
end
return AnimationData.ActiveTrack
else
warn("Warning: " .. self.Name .. ".Animations[" .. tostring(AnimationName) .. "] is missing or nil.")
end
end
function Class:AdjustAnimation(Rig, AnimationName, AnimationLength, Weight, TransitionTime)
if self.Owner == nil then return end
local AnimationData = self.Animations[AnimationName]
if AnimationData and AnimationData.ActiveTrack then
local Track = AnimationData.ActiveTrack
TransitionTime = TransitionTime or DefaultTransitionTime
Track:AdjustSpeed( (AnimationLength and Track.Length ~= 0) and Track.Length/AnimationLength or nil )
Track:AdjustWeight(Weight, TransitionTime)
if RunService:IsClient() and Players[self.Owner.Name] == LocalPlayer then
ToolEvent:FireServer(self.ID, "AdjustAnimation", AnimationName, AnimationLength, Weight, TransitionTime)
elseif RunService:IsServer() then
ToolEvent:FireAllClients(self.ID, "AdjustAnimation", AnimationName, AnimationLength, Weight, TransitionTime)
end
end
end
function Class:StopAnimation(AnimationName)
if self.Owner == nil then return end
local AnimationData = self.Animations[AnimationName]
if AnimationData and AnimationData.ActiveTrack then
AnimationData.ActiveTrack:Stop()
if RunService:IsClient() and Players[self.Owner.Name] == LocalPlayer then
ToolEvent:FireServer(self.ID, "StopAnimation", AnimationName)
elseif RunService:IsServer() then
ToolEvent:FireAllClients(self.ID, "StopAnimation", AnimationName)
end
else
warn("Warning: " .. self.Name .. ".Animations[" .. tostring(AnimationName) .. "] is missing or nil.")
end
end
function Class:StopAllAnimations()
for Index, AnimationData in pairs(self.Animations) do
if AnimationData and AnimationData.ActiveTrack then
AnimationData.ActiveTrack:Stop()
end
end
if RunService:IsClient() and Players[self.Owner.Name] == LocalPlayer then
ToolEvent:FireServer(self.ID, "StopAllAnimations")
elseif RunService:IsServer() then
ToolEvent:FireAllClients(self.ID, "StopAllAnimations")
end
end
-----------------------
-- Clean up
------------------------
function Class:Destroy()
Cache[self.ID] = nil
if self.ToolModel then
self.ToolModel:Destroy()
self:UnloadAnimations()
end
self.Destroyed:Fire()
end
-----------------------
-- Replication Logic --
-----------------------
if RunService:IsServer() then
ToolEvent.OnServerEvent:Connect(function(Player, ...)
for _, OtherPlayer in pairs(Players:GetPlayers()) do
if OtherPlayer ~= Player then
ToolEvent:FireClient(OtherPlayer, ...)
end
end
end)
else
ToolEvent.OnClientEvent:Connect(function(ID, EventName, Data)
local Tool = Cache[ID]
if Tool then
if EventName == "Equipped" then
Tool:Equip(Data)
elseif EventName == "Unequipped" then
Tool:Unequip(Data)
elseif EventName == "PlayAnimation" then
Tool:PlayAnimation(Data)
elseif EventName == "StopAnimation" then
Tool:StopAnimation(Data)
elseif EventName == "StopAllAnimations" then
Tool:StopAllAnimations()
elseif EventName == "Dropped" then
Tool:Drop()
elseif EventName == "CustomEvent" then
Tool[Data[1]](Tool, (Data[2] and typeof(Data[2]) == "table" and #Data[2] > 1 and unpack(Data[2])) or Data[2])
end
else
warn("Cache[".. tostring(ID) .."] is missing or nil.")
end
end)
end
return Class
RangedWeaponClass (inherited from ToolClass)
---------------------
-- Roblox Services --
---------------------
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local SoundService = game:GetService("SoundService")
--------------------
-- Reverb Modules --
--------------------
local Reverb = require(ReplicatedStorage:WaitForChild("Reverb"))
local Utilities = require(Reverb.Utility)
local Core = require(ReplicatedStorage:WaitForChild("Core"))
local WorldData = require(Core.WorldData)
--------------------
-- Networking
--------------------
local IsServer = RunService:IsServer()
local Replicate = script:WaitForChild("Replicate")
local function ReplicateShot(Tool, Shots)
-- figure out why the spread isn't theree.
Tool.GearManagerInterface.CreateProjectile(Tool, Shots, Tool:GetIgnoreList())
if not IsServer then
Replicate:FireServer(Tool.ID, "ShotFired", 1)
else
Replicate:FireAllClients(Tool.ID, "ShotsFired", 1)
end
end
------------------------
-- Local Cache
------------------------
local Cache = {}
------------------------
-- Class Construction --
------------------------
local ToolClass = require(script.Parent.ToolClass)
local Class = {}
Class.__index = Class
setmetatable(Class, ToolClass)
-----------------------------
-- Generic Class Functions --
-----------------------------
function Class.new(ID, Owner, ToolModel, ToolGrip)
local Template = require(ToolModel:WaitForChild("ToolData"))
local Tool = setmetatable(ToolClass.new(ID, Owner, ToolModel, ToolGrip, Template.AnimationList), Class)
-- Hacky, but works.
local F = {Equip = true, Unequip = true, Activate = true, Deactivate = true, Update = true, InputBegan = true, InputEnded = true}
for Index, Value in pairs(Template) do
if F[Index] then
Tool[Index] = function(self, ...)
Class[Index](self, ...)
Value(self, ...)
end
else
Tool[Index] = Value
end
end
if ToolModel then
local Handle = ToolModel.Handle
if Tool.HasLaser then
local Attachment0 = Utilities.Create("Attachment", {Name = "LaserOrigin", Parent = Handle})
local Attachment1 = Utilities.Create("Attachment", {Name = "LaserEndPoint", Parent = Handle})
local BeamObject = Utilities.Create("Beam", {
Name = "LaserAttachment",
FaceCamera = true,
Color = ColorSequence.new(Color3.fromRGB(255, 0, 0), Color3.fromRGB(255, 0, 0)),
LightEmission = 1,
Transparency = NumberSequence.new(0.2),
Attachment0 = Attachment0,
Attachment1 = Attachment1,
Width0 = 0.05,
Width1 = 0.05,
Enabled = false,
Parent = Handle,
})
Tool.Laser = {Attachment0 = Attachment0, Attachment1 = Attachment1, Beam = BeamObject}
end
if Tool.HasFlashLight then
local FlashlightAttachment = Utilities.Create("Attachment", {Name = "FlashlightAttachment", Parent = Handle})
local SpotLight = Utilities.Create("SpotLight", {
Name = "FlashLight",
Angle = 45,
Brightness = 5,
Range = 45,
Color = Color3.fromRGB(255, 249, 225),
Shadows = true,
Enabled = false,
Parent = FlashlightAttachment
})
Tool.Flashlight = {Attachment0 = FlashlightAttachment, Light = SpotLight}
end
end
if Owner then
Tool:LoadAnimations()
end
Tool:LoadSounds()
Tool.UserInterface = nil
Tool.LastAttack = 0
Cache[ID] = Tool
return Tool
end
function Class:Equip(NewOwner, UserInterface, camShake)
ToolClass.Equip(self, NewOwner)
if self.Laser then
self.Laser.Beam.Enabled = true
self.LaserIgnoreList = self:GetIgnoreList()
end
if self.Flashlight then
self.Flashlight.Light.Enabled = true
end
if UserInterface then
UserInterface.GetElement("WeaponHud").Toggle(true)
UserInterface.GetElement("WeaponHud").UpdateWeaponIcon(self)
self.UserInterface = UserInterface
self:UpdateAmmoCounter()
end
self.camShake = camShake
end
function Class:Unequip()
ToolClass.Unequip(self)
if self.Laser then
self.Laser.Beam.Enabled = false
self.LaserIgnoreList = nil
end
if self.Flashlight then
self.Flashlight.Light.Enabled = false
end
end
function Class:Destroy()
ToolClass.Destroy(self)
if self.Laser then
self.Laser.Attachment0:Destroy()
self.Laser.Attachment1:Destroy()
self.Laser.Beam:Destroy()
end
if self.Flashlight then
self.Flashlight.Attachment0:Destroy()
self.Flashlight.Light:Destroy()
end
Cache[self.ID] = nil
end
--------------------
-- Audio Handling --
--------------------
function Class:LoadSounds()
if self.ToolModel then
local Sounds = {}
local Objects = self.ToolModel:GetDescendants()
for Index = 1, #Objects do
local Object = Objects[Index]
if Object:IsA("Sound") then
Sounds[Object.Name] = Object
Object.SoundGroup = SoundService.Tools
end
end
self.Sounds = Sounds
self.ActiveSounds = {}
end
end
function Class:PlaySound(SoundName)
if self.Sounds then
if self.Sounds[SoundName] then
self.CustomEvent:Fire("PlaySound", SoundName)
local Sound = self.Sounds[SoundName]:Clone()
Sound.SoundGroup = self.Sounds[SoundName].SoundGroup
Sound.Pitch = Sound.Pitch + math.random(-100,100)/1000
Sound.PlayOnRemove = false
Sound.Parent = self.Handle
Sound:Play()
coroutine.resume(coroutine.create(function()
Sound.Ended:Wait()
Sound:Destroy()
end))
return Sound
else
warn(self.Name.. ".Sounds[" .. tostring(SoundName) .. "] is missing or nil.")
end
end
end
--------------------------------------------
-- Ranged Weapon Specific Class Functions --
--------------------------------------------
function Class:GetIgnoreList(Extras)
local CharactersFolder = game.Workspace.Characters
local IgnoreList = {game.Workspace.RayIgnore}
local LocalMachineCharacter
local OtherMachineCharacterFolder
if IsServer then
OtherMachineCharacterFolder = CharactersFolder.Client
LocalMachineCharacter = CharactersFolder.Server:FindFirstChild(self.Owner.Name)
else
OtherMachineCharacterFolder = CharactersFolder.Server
LocalMachineCharacter = CharactersFolder.Client:FindFirstChild(self.Owner.Name)
end
if LocalMachineCharacter then
IgnoreList[#IgnoreList+1] = LocalMachineCharacter
end
if OtherMachineCharacterFolder then
IgnoreList[#IgnoreList+1] = OtherMachineCharacterFolder
end
if Extras then
for _, Value in pairs(Extras) do
IgnoreList[#IgnoreList+1] = Value
end
end
return IgnoreList
end
function Class:UpdateAmmoCounter()
if self.UserInterface then
self.UserInterface.GetElement("WeaponHud").UpdateAmmo(self)
end
end
function Class:Restock(Amount)
self.TotalAmmo = math.min(self.TotalAmmo + Amount, self.MaxAmmo)
if IsServer then
Replicate:FireAllClients(self.ID, "AmmoPickup", Amount)
else
self:UpdateAmmoCounter()
end
end
function Class:Reload()
if self.TotalAmmo == "inf" or self.TotalAmmo == 0 then return end
print("Reloading")
self.IsReloading = true
local AmountToReload = math.min(self.MagSize, self.TotalAmmo)
while self.TotalAmmo >= 1 and self.Ammo < AmountToReload and self.IsReloading do
self:PlaySound("ReloadSound")
local Track = self:PlayAnimation("Reload")
Track.Stopped:Wait()
if not self.IsReloading then
return
end
if self.IsShotgun then
self.Ammo = self.Ammo+1
print(Reverb.RunMode, "TotalAmmo:", self.TotalAmmo)
else
self.Ammo = AmountToReload
end
self:UpdateAmmoCounter()
end
self.IsReloading = false
print("Reloading finished")
end
function Class:HandleRecoil()
if self.camShake then
local Recoil = self.Recoil
self.camShake:ShakeOnce(
Recoil.Magnitude,
Recoil.Roughness,
Recoil.FadeIn,
Recoil.FadeOut
)
end
end
function Class:EjectShell(HumanoidRootPart)
if RunService:IsServer() or self.ToolModel == nil or self.ShellCasingType == nil then return end
local Handle = self.Handle
-- ParticleManager.CreateShellCasing(
-- self.ShellCasingType, -- ShellCasingType
-- Handle.ShellEmitter.WorldCFrame, -- CF
-- Handle.CFrame.RightVector * 7, -- Velocity
-- Vector3.new(math.random() * 90, 0, 0) -- RotVelocity
-- )
local Ids = {
953031970,
961361674,
961369740,
}
local Sound = Instance.new("Sound")
Sound.SoundId = "rbxassetid://"..Ids[math.random(1, #Ids)]
Sound.Volume = 0.8
Sound.PlaybackSpeed = 0.8
Sound.PlayOnRemove = true
Sound.EmitterSize = 30
Sound.Parent = HumanoidRootPart
Sound:Destroy()
end
function Class:ShotgunBlast(HumanoidRootPart, Direction)
local Shots = {}
for Pellet_Index = 1, self.NumberOfPellets do
local Spread = -(self.Spread.Max/2) + (Pellet_Index/self.NumberOfPellets) * self.Spread.Max
Direction = (CFrame.new(HumanoidRootPart.Position, HumanoidRootPart.Position + Direction) * CFrame.Angles(0, math.rad(Spread), 0)).LookVector
Shots[#Shots+1] = {HumanoidRootPart.Position, Direction}
end
return Shots
end
function Class:SingleShot(HumanoidRootPart, Direction)
local Shots = {}
local N = math.random(-1, 1)
if N == 0 then N = 1 end
self.Spread.Current = math.min(self.Spread.Current + self.Spread.Step, 1)
local Spread = math.random() * (self.Spread.Max * self.Spread.Current)
Direction = (CFrame.new(HumanoidRootPart.Position, HumanoidRootPart.Position + Direction) * CFrame.Angles(0, math.rad(Spread * N), 0)).LookVector
Shots[#Shots+1] = {HumanoidRootPart.Position, Direction}
return Shots
end
function Class:Attack()
if self.IsAttacking then return end
if self and self.Owner.Model.Parent then
if self.Ammo < 1 then
return
end
if not self.IsShotgun and self.IsReloading then
return
else
self.IsReloading = false
end
local HumanoidRootPart = self.Owner.Model:FindFirstChild("HumanoidRootPart")
local ToolModel = self.ToolModel
local Handle = self.Handle
self.IsAttacking = true
self.LastAttack = tick()
if self.IsBurst then
local BurstAmount = self.NumberOfRoundsInBurst
if self.Ammo < BurstAmount then
BurstAmount = self.Ammo
end
for _ = 1, BurstAmount do
self.LastAttack = tick()
self:PlayAnimation("Shoot")
local Shots = {}
if self.IsShotgun then
Shots = self:ShotgunBlast(HumanoidRootPart, self.InputDirection)
else
Shots = self:SingleShot(HumanoidRootPart, self.InputDirection)
end
ReplicateShot(self, Shots)
self:PlaySound("FireSound", true, true)
self.Ammo = math.max(self.Ammo-1, 0)
self.TotalAmmo = math.max(self.TotalAmmo-1, 0)
self:EjectShell(HumanoidRootPart)
self:UpdateAmmoCounter()
self:HandleRecoil()
wait(1/self.FireRate)
end
if self.UsePumpAnimation then
local Track = self:PlayAnimation("Pump")
if self.Sounds["PumpSound"] then
self:PlaySound("PumpSound")
end
--Track.Stopped:wait()
end
if self.BurstDelay > 0 then
wait(self.BurstDelay)
end
elseif self.IsShotgun then
self:PlayAnimation("Shoot")
local Shots = self:ShotgunBlast(HumanoidRootPart, self.InputDirection)
ReplicateShot(self, Shots)
self:PlaySound("FireSound", true, true)
self.Ammo = math.max(self.Ammo-1, 0)
self.TotalAmmo = math.max(self.TotalAmmo-1, 0)
self:EjectShell(HumanoidRootPart)
self:UpdateAmmoCounter()
self:HandleRecoil()
if self.UsePumpAnimation then
local Track = self:PlayAnimation("Pump")
if self.Sounds["PumpSound"] then
self:PlaySound("PumpSound")
end
--Track.Stopped:wait()
end
wait(1/self.FireRate)
else
local Track = self:PlayAnimation("Shoot")
local Shots = self:SingleShot(HumanoidRootPart, self.InputDirection)
ReplicateShot(self, Shots)
self:PlaySound("FireSound", true, true)
self.Ammo = math.max(self.Ammo-1, 0)
self.TotalAmmo = math.max(self.TotalAmmo-1, 0)
self:EjectShell(HumanoidRootPart)
self:UpdateAmmoCounter()
self:HandleRecoil()
wait(1/self.FireRate)
end
self.IsAttacking = false
if self.IsAutomatic and self.Active then
self:Attack()
end
end
end
function Class:Update(TimeElapsed)
if self.Owner == nil then return end
-- local Character = self.Owner.Model
-- local HumanoidRootPart = Character:FindFirstChild("HumanoidRootPart")
-- if HumanoidRootPart == nil then return end
-- if self.ToolModel then
-- if self.Laser then
-- local _, HitPosition = Utilities.RaycastWithIgnoreList(HumanoidRootPart.Position, HumanoidRootPart.CFrame.LookVector * 999, self.LaserIgnoreList)
-- --self.Laser.Attachment0.CFrame = self.ToolModel.PrimaryPart.CFrame
-- self.Laser.Attachment1.WorldPosition = (HitPosition)
-- end
-- end
if tick() - self.LastAttack >= 0.1 then
self.Spread.Current = math.max(0, self.Spread.Current - (self.Spread.Recovery * TimeElapsed))
end
end
--------------------------------------------
--
--------------------------------------------
local function OnReplicatedDo(ID, EventName, Extra)
local Tool = Cache[ID]
if Tool then
-- warn(EventName)
if EventName == "ShotFired" then
if Tool.TotalAmmo == "inf" then
return
end
Tool.TotalAmmo = Tool.TotalAmmo - Extra
-- warn(Tool.Name .. "[" .. tostring(Tool.ID) .. "].TotalAmmo left: " .. tostring(Tool.TotalAmmo))
elseif EventName == "AmmoPickup" then
Tool:Restock(Extra)
end
end
end
if IsServer then
Replicate.OnServerEvent:Connect(function(Player, ID, EventName, Extra)
OnReplicatedDo(ID, EventName, Extra)
end)
else
Replicate.OnClientEvent:Connect(function(ID, EventName, Extra)
OnReplicatedDo(ID, EventName, Extra)
end)
end
return Class