So this morning i got the nice idea to port my shooting into something more OOP oriented (atleast that’s what i think i’m doing)
it’s been going smoothly, buuut automatics kind of, shoot once then fire the updated hit pos remote but the gun itself doesn’t shoot
and i’m gonna be honest i entirely forgot how i fixed this in the past, so instead of brainstorming this i’d rather just ask on here
do note that i think some of this code suffers from unnecessary repetition so i apologize if it hurts your eyes (it’s the thought that counts right?)
client-sided functions (they get called and parameters are passed and that’s it
-- the "ApplyRecoil" function is just static recoil that i don't feel the need to include as it doesn't cause any issues
local shootFunctions = {
SEMI = function(weapon: Tool, mouse: Mouse)
local FIRERATE = gunStats[weapon.Name]["FIRERATE"]
shootEvent:FireServer(mouse.Hit.Position)
ApplyRecoil(weapon)
task.wait(FIRERATE)
end,
AUTOMATIC = function(weapon: Tool, mouse: Mouse, shootAnim: Animation) -- It happens here!!
local FIRERATE = gunStats[weapon.Name]["FIRERATE"]
shooting = true
shootEvent:FireServer(mouse.Hit.Position)
local buttonUpConnection
buttonUpConnection = mouse.Button1Up:Connect(function()
disableIsHoldingEvent:FireServer()
shooting = false
buttonUpConnection:Disconnect()
end)
while shooting do
if weapon.Bullets.Value < 1 then
disableIsHoldingEvent:FireServer()
shooting = false
break
end
updateHitPosEvent:FireServer(mouse.Hit.Position)
shootAnim:Play()
ApplyRecoil(weapon)
task.wait(FIRERATE)
end
end,
SHOTGUN = function(weapon: Tool, mouse: Mouse, shootAnim: Animation)
local FIRERATE = gunStats[weapon.Name]["FIRERATE"]
shootEvent:FireServer(mouse.Hit.Position)
shootAnim:Play()
ApplyRecoil(weapon)
task.wait(FIRERATE)
end,
AUTO_SHOTGUN = function(weapon: Tool, mouse: Mouse, shootAnim: Animation) -- This is unused for now.
local FIRERATE = gunStats[weapon.Name]["FIRERATE"]
shooting = true
local buttonUpConnection
buttonUpConnection = mouse.Button1Up:Connect(function()
disableIsHoldingEvent:FireServer()
shooting = false
buttonUpConnection:Disconnect()
end)
while shooting do
if weapon.Bullets.Value < 1 then
disableIsHoldingEvent:FireServer()
shooting = false
break
end
updateHitPosEvent:FireServer(mouse.Hit.Position)
shootAnim:Play()
ApplyRecoil(weapon)
task.wait(FIRERATE)
end
end,
}
server-sided script that receives the remote events (this is NOT the module script)
ShootEvent.OnServerEvent:Connect(function(player, initialHitPos)
local tool = player.Character:FindFirstChildOfClass("Tool")
local bullets = tool.Bullets
local spread = tool.Spread
local FIRERATE = gunInfo[tool.Name]["FIRERATE"]
if tool.Shooting.Value then player:Kick("You need more power.") return end
if tool.Reloading.Value then player:Kick("Why?") return end
if tool.Equipping.Value then player:Kick("Someone's in a hurry.") return end
if bullets.Value < 1 then player:Kick("Your manipulation techniques are atrocious.") return end
if bullets.Value > gunInfo[tool.Name]["MAX_BULLETS"] then player:Kick("The underworld calls to you.") return end
local hitPos = initialHitPos
local updateHitPosConnection = UpdateHitPosEvent.OnServerEvent:Connect(function(updatePlayer, newHitPos)
if updatePlayer == player then
hitPos = newHitPos
end
end)
coroutine.wrap(function() -- is this unnecessary? probably
tool.FlashPart.FlashImage.Enabled = true
tool.FlashPart.Light.Enabled = true
task.wait(0.1)
tool.FlashPart.FlashImage.Enabled = false
tool.FlashPart.Light.Enabled = false
end)()
shootingModule.Shoot(tool.FlashPart, player, hitPos, tool)
tool.Shooting.Value = true
tool.Bullets.Value -= 1
tool.FlashPart.ShootSound:Play()
task.wait(FIRERATE)
tool.Shooting.Value = false
updateHitPosConnection:Disconnect()
end)
server-sided module script that does all the raycasting (this shouldn’t cause any issues but feel free to investigate it, just note that it’s nearly 200 lines of code)
local rStorage = game:GetService("ReplicatedStorage")
local dService = game:GetService("Debris")
local sScriptService = game:GetService("ServerScriptService")
local players = game:GetService("Players")
-- Events;
local playerEvents = rStorage:WaitForChild("PlayerEvents")
local damageIndicatorEvent = playerEvents.DamageGUI
-- Third party modules;
local weapons = sScriptService:WaitForChild("Weapons")
local guns = weapons:WaitForChild("Gun")
local gunInfo = require(guns.GunStats)
-- // NORMAL RAYCASTING FUNCTION
local function Raycasting(origin: Part, player: Player, hitPos: Mouse, weapon: Tool)
local HEADSHOT_DAMAGE = gunInfo[weapon.Name]["HEADSHOT_DAMAGE"]
local BODYSHOT_DAMAGE = gunInfo[weapon.Name]["BODYSHOT_DAMAGE"]
-- Raycast parameters.
local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
raycastParams.FilterDescendantsInstances = {player.Character}
raycastParams.IgnoreWater = true
local direction = (hitPos - origin.Position).Unit
local displacement = direction * gunInfo[weapon.Name]["MAX_DISTANCE"]
local result = workspace:Raycast(
origin.Position,
displacement,
raycastParams
)
local endPos = nil
-- Did the raycast hit something?
if result then
endPos = result.Position
local character = result.Instance.Parent
local humanoid = character:FindFirstChild("Humanoid")
if humanoid ~= players:GetPlayerFromCharacter(character) then
if result.Instance == character.Head then
humanoid:TakeDamage(HEADSHOT_DAMAGE)
damageIndicatorEvent:FireClient(player, HEADSHOT_DAMAGE, humanoid)
else
humanoid:TakeDamage(BODYSHOT_DAMAGE)
damageIndicatorEvent:FireClient(player, BODYSHOT_DAMAGE, humanoid)
end
end
else
endPos = origin.Position + displacement
end
local tracer = Instance.new("Part")
tracer.Parent = workspace
tracer.Anchored = true
tracer.CanCollide = false
tracer.CanQuery = false
tracer.Transparency = 0.3
tracer.Color = Color3.new(0.960784, 0.666667, 0.156863)
dService:AddItem(tracer, 0.2)
local tracerLength = (endPos - origin.Position).Magnitude
tracer.Size = Vector3.new(0.07, 0.07, tracerLength)
tracer.CFrame = CFrame.lookAt(origin.Position + (endPos - origin.Position) / 2, endPos)
if weapon.AmmoType.Value == "NRG" then
tracer.Color = Color3.fromRGB(0,255,0)
end
end
------------------------------------------------------------------------
-- // SHOTGUN RAYCASTING FUNCTION
local function ShotgunRaycasting(origin: Part, player: Player, hitPos: Mouse, weapon: Tool)
local PELLETS = gunInfo[weapon.Name]["PELLETS"]
local HORIZONTAL_SPREAD = {min = -0.2, max = 0.4}
local VERTICAL_SPREAD = {min = -0.2, max = 0.2}
local MAX_DISTANCE = gunInfo[weapon.Name]["MAX_DISTANCE"]
local HEADSHOT_DAMAGE = gunInfo[weapon.Name]["HEADSHOT_DAMAGE"]
local BODYSHOT_DAMAGE = gunInfo[weapon.Name]["BODYSHOT_DAMAGE"]
for pellet=1, PELLETS do
local ran = Random.new()
local spreadX = ran:NextNumber(HORIZONTAL_SPREAD.min, HORIZONTAL_SPREAD.max)
local spreadY = ran:NextNumber(VERTICAL_SPREAD.min, VERTICAL_SPREAD.max)
local direction = (hitPos + Vector3.new(spreadX, spreadY)).Unit
local displacement = direction * MAX_DISTANCE
local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {player.Character}
raycastParams.IgnoreWater = true
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
local direction = (hitPos - origin.Position).Unit
local spreadDirection = (direction + Vector3.new(spreadX, spreadY, 0)).Unit
local displacement = spreadDirection * MAX_DISTANCE
local result = workspace:Raycast(
origin.Position,
displacement,
raycastParams
)
local endPos = nil
if result then
endPos = result.Position
local character = result.Instance.Parent
local humanoid = character:FindFirstChild("Humanoid")
if humanoid ~= players:GetPlayerFromCharacter(character) then
if result.Instance == character.Head then
humanoid:TakeDamage(HEADSHOT_DAMAGE)
damageIndicatorEvent:FireClient(player, HEADSHOT_DAMAGE, humanoid)
else
humanoid:TakeDamage(BODYSHOT_DAMAGE)
damageIndicatorEvent:FireClient(player, BODYSHOT_DAMAGE, humanoid)
end
end
else
endPos = origin.Position + displacement
end
-- // TRACERS
local tracer = Instance.new("Part")
tracer.Parent = workspace
tracer.Anchored = true
tracer.CanCollide = false
tracer.CanQuery = false
tracer.Transparency = 0.6
tracer.Color = Color3.new(0.960784, 0.666667, 0.156863)
dService:AddItem(tracer, 0.2)
local tracerLength = (endPos - origin.Position).Magnitude
tracer.Size = Vector3.new(0.07, 0.07, tracerLength)
tracer.CFrame = CFrame.lookAt(origin.Position + (endPos - origin.Position) / 2, endPos)
end
end
------------------------------------------------------------------------
-- // MODULE ITSELF
local shootingModule = {}
local shootFunctions = { -- Call these and index with tool's gun type value.
-- there's A LOT of repetition here :(
SEMI = function(origin: Part, player: Player, hitPos: Mouse, weapon: Tool)
Raycasting(origin, player, hitPos, weapon)
end,
AUTOMATIC = function(origin: Part, player: Player, hitPos: Mouse, weapon: Tool)
Raycasting(origin, player, hitPos, weapon)
end,
SHOTGUN = function(origin: Part, player: Player, hitPos: Mouse, weapon: Tool)
ShotgunRaycasting(origin, player, hitPos, weapon)
end,
AUTO_SHOTGUN = function(origin: Part, player: Player, hitPos: Mouse, weapon: Tool)
ShotgunRaycasting(origin, player, hitPos, weapon)
end,
}
function shootingModule.Shoot(origin: Part, player: Player, hitPos: Mouse, weapon: Tool)
shootFunctions[weapon.GunType.Value](origin, player, hitPos, weapon)
end
return shootingModule