Hi! Love that you have created this and provided all this extra info on how to use it.
I’m having trouble with running FastCaster:Fire() once but receiving two FastCaster.RayHit:Connect(OnRayHit) back.
Edit: Fixed it!
The problem was that I was having the server script in each gun, listen for the SendHitEvent. This event was in ReplicatedStorage, so every server script in every tool would damage the hit player once one of the players sent the SendHitEvent. I fixed it by moving the event inside the tool. First I tried moving the server script out of the tool, but it required that I keep the state of all the clients in a table or something. And I didn’t want the extra complexity.
Original post
This means that my DamagePlayer() function is fired twice on the server, and I don’t want that.
The issue occurs when I run a test server with two players. For one player it fires once. For three players I would imagine it fires 3 times.
I don’t think FastCaster:Fire() should be ran more than once, because it is only ran in the LocalScript of the player who fires at that moment.
LocalScript
local Tool = script.Parent
local Handle = Tool:WaitForChild("Handle")
local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local EventStorage = game:GetService("ReplicatedStorage")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local FireEvent = EventStorage:WaitForChild("FireEvent")
local ClientProjectileEvent = EventStorage:WaitForChild("ClientProjectileEvent")
local Configs = Tool:WaitForChild("Configs")
local constants = require(Configs:WaitForChild("Settings"))
local Mouse = nil
local ExpectingInput = false
local Camera = workspace.CurrentCamera
local FirePointObject = Handle:WaitForChild("GunFirePoint")
local FastCast = require(ReplicatedStorage:WaitForChild("FastCastRedux"))
local OriginalShotCaster = FastCast.new()
local FireSound = Handle:WaitForChild("Fire")
local Debris = game:GetService("Debris")
local LaserBullet = require(Tool:WaitForChild("Bullet"))
local SendHitEvent = EventStorage:WaitForChild("SendHitEvent")
function UpdateMouseIcon()
if Mouse and not Tool.Parent:IsA("Backpack") then
Mouse.Icon = "rbxasset://textures/GunCursor.png"
end
end
function OnEquipped(PlayerMouse)
Mouse = PlayerMouse
ExpectingInput = true
UpdateMouseIcon()
end
function OnUnequipped()
ExpectingInput = false
UpdateMouseIcon()
end
Tool.Equipped:Connect(OnEquipped)
Tool.Unequipped:Connect(OnUnequipped)
UserInputService.InputBegan:Connect(function (input, gameHandledEvent)
if gameHandledEvent or not ExpectingInput then
--The ExpectingInput value is used to prevent the gun from firing when it shouldn't on the clientside.
--This will still be checked on the server.
return
end
if input.UserInputType == Enum.UserInputType.MouseButton1 and Mouse ~= nil then
local FireDirection = (Mouse.Hit.p - FirePointObject.WorldPosition).Unit
FireEvent:FireServer(FireDirection)
local humanoidRootPart = Tool.Parent:WaitForChild("HumanoidRootPart", 1) -- Add a timeout to this.
local myMovementSpeed = humanoidRootPart.Velocity -- To do: It may be better to get this value on the clientside since the server will see this value differently due to ping and such.
local modifiedBulletSpeed = (FireDirection * constants.BULLET_SPEED) + myMovementSpeed -- We multiply our direction unit by the bullet speed. This creates a Vector3 version of the bullet's velocity at the given speed. We then add MyMovementSpeed to add our body's motion to the velocity.
FireProjectile(FirePointObject.WorldPosition, FireDirection * constants.BULLET_MAXDIST, modifiedBulletSpeed, nil, Tool.Parent, false, constants.BULLET_GRAVITY, "LaserPistol")
end
end)
function PlayFireSound(position)
local NewSound = FireSound:Clone()
NewSound.Parent = Handle
NewSound:Play()
Debris:AddItem(NewSound, NewSound.TimeLength)
end
function FireProjectile(Origin, DirectionWithMagnitude, Speed, cosmeticBulletObject, ignoreDescendantsInstance, ignoreWater, bulletAcceleration, Type)
OriginalShotCaster.IgnoreDescendantsInstance = ignoreDescendantsInstance
PlayFireSound(ignoreDescendantsInstance:WaitForChild("DebugGun"):WaitForChild("Handle"))
if Type == "LaserPistol" then
local NewBullet = LaserBullet:Clone()
NewBullet.CFrame = CFrame.new(Origin, Origin + DirectionWithMagnitude.Unit)
NewBullet.Parent = workspace.CurrentCamera
print("Firing cast in FireProjectile")
OriginalShotCaster:Fire(Origin, DirectionWithMagnitude, Speed, NewBullet, ignoreDescendantsInstance, ignoreWater, bulletAcceleration)
end
end
function OnRayHit(hitPart, hitPoint, normal, material, cosmeticBulletObject)
print("OnRayHit for OriginalShotCaster")
SendHitEvent:FireServer(hitPart, hitPoint, normal, material, cosmeticBulletObject)
end
--OriginalShotCaster.LengthChanged:Connect(DrawBulletModule.OnRayUpdated)
--OriginalShotCaster.RayHit:Connect(DrawBulletModule.OnRayHit) --For displaying the hit marker
OriginalShotCaster.RayHit:Connect(OnRayHit) --For taking damage
Server
-- Latest update 24 November 2019 - Upgrade to redux.
local Tool = script.Parent
local Handle = Tool:WaitForChild("Handle")
local EventStorage = game:GetService("ReplicatedStorage")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local FireEvent = EventStorage:WaitForChild("FireEvent")
local FirePointObject = Handle:WaitForChild("GunFirePoint")
local FastCast = require(ReplicatedStorage:WaitForChild("FastCastRedux"))
local ImpactParticle = Handle:WaitForChild("ImpactParticle")
local SendCastEvt = EventStorage:WaitForChild("SendCast")
local Debris = game:GetService("Debris")
local Configs = Tool:WaitForChild("Configs")
local SendHitEvent = EventStorage:WaitForChild("SendHitEvent")
local constants = require(Configs:WaitForChild("Settings"))
local CanFire = true -- Used for a cooldown.
-- Now we set the caster values.
--local Caster = FastCast.new() --Create a new caster object.
-- Bonus points: If you're going to be slinging a ton of bullets in a short period of time, you may see it fit to use PartCache.
-- https://devforum.roblox.com/t/partcache-for-all-your-quick-part-creation-needs/246641
function Fire(direction)
-- Called when we want to fire the gun.
if Tool.Parent:IsA("Backpack") then return end -- Can't fire if it's not equipped.
-- Note: Above isn't in the event as it will prevent the CanFire value from being set as needed.
-- UPD. 11 JUNE 2019 - Add support for random angles.
local directionalCF = CFrame.new(Vector3.new(), direction)
-- Now, we can use CFrame orientation to our advantage.
-- Overwrite the existing Direction value.
local direction = (directionalCF * CFrame.fromOrientation(0, 0, constants.RNG:NextNumber(0, constants.TAU)) * CFrame.fromOrientation(math.rad(constants.RNG:NextNumber(constants.MIN_BULLET_SPREAD_ANGLE, constants.MAX_BULLET_SPREAD_ANGLE)), 0, 0)).LookVector
-- UPDATE V6: Proper bullet velocity!
-- IF YOU DON'T WANT YOUR BULLETS MOVING WITH YOUR CHARACTER, REMOVE THE THREE LINES OF CODE BELOW THIS COMMENT.
-- Requested by https://www.roblox.com/users/898618/profile/
-- We need to make sure the bullet inherits the velocity of the gun as it fires, just like in real life.
local humanoidRootPart = Tool.Parent:WaitForChild("HumanoidRootPart", 1) -- Add a timeout to this.
local myMovementSpeed = humanoidRootPart.Velocity -- To do: It may be better to get this value on the clientside since the server will see this value differently due to ping and such.
local modifiedBulletSpeed = (direction * constants.BULLET_SPEED) + myMovementSpeed -- We multiply our direction unit by the bullet speed. This creates a Vector3 version of the bullet's velocity at the given speed. We then add MyMovementSpeed to add our body's motion to the velocity.
-- NOTE: It may be a good idea to make a Folder in your workspace named "CosmeticBullets" (or something of that nature) and use FireWithBlacklist on the descendants of this folder!
-- Quickly firing bullets in rapid succession can cause the caster to hit other casts' bullets from the same gun (The caster only ignores the bullet of that specific shot, not other bullets).
-- Do note that if you do this, you will need to remove the Equipped connection that sets IgnoreDescendantsInstance, as this property is not used with FireWithBlacklist
-- Fire the caster
--Caster:Fire(FirePointObject.WorldPosition, direction * constants.BULLET_MAXDIST, modifiedBulletSpeed, nil, Tool.Parent, false, constants.BULLET_GRAVITY)
-- Uncomment this later: SendCastEvt:FireAllClients(FirePointObject.WorldPosition, direction * constants.BULLET_MAXDIST, modifiedBulletSpeed, nil, Tool.Parent, false, constants.BULLET_GRAVITY, "LaserPistol")
end
function DamagePlayer(player, hitPart, hitPoint, normal, material, cosmeticBulletObject)
-- This function will be connected to the Caster's "RayHit" event.
-- (removed from server) cosmeticBulletObject:Destroy() -- Destroy the cosmetic bullet.
if hitPart and hitPart.Parent then -- Test if we hit something
local humanoid = hitPart.Parent:FindFirstChildOfClass("Humanoid") -- Is there a humanoid?
if humanoid then
print("Player "..player.Name.." ordered Take damage")
humanoid:TakeDamage(Configs:WaitForChild("ShotDamage").Value) -- Damage. Configs:WaitForChild("ShotDamage").Value script.Parent.Configs:WaitForChild("ShotDamage").Value
end
end
end
-- Before I make the connections, I will check for proper values. In production scripts that you are writing that you know you will write properly, you should not do this.
-- This is included exclusively as a result of this being an example script, and users may tweak the values incorrectly.
assert(constants.MAX_BULLET_SPREAD_ANGLE >= constants.MIN_BULLET_SPREAD_ANGLE, "Error: MAX_BULLET_SPREAD_ANGLE cannot be less than MIN_BULLET_SPREAD_ANGLE!")
if (constants.MAX_BULLET_SPREAD_ANGLE > 180) then
warn("Warning: MAX_BULLET_SPREAD_ANGLE is over 180! This will not pose any extra angular randomization. The value has been changed to 180 as a result of this.")
constants.MAX_BULLET_SPREAD_ANGLE = 180
end
FireEvent.OnServerEvent:Connect(function (clientThatFired, mouseDirection)
if not CanFire then
return
end
CanFire = false
Fire(mouseDirection)
wait(constants.FIRE_DELAY)
CanFire = true
end)
SendHitEvent.OnServerEvent:Connect(DamagePlayer)
--Caster.RayHit:Connect(OnRayHit)
In my script, FastCaster is called OriginalShotCaster