Trying to understand Roblox gear, need some help

So I’m currently learning how to code weapons, and I’ve been told the best resource for this was to look at free models. I’m a bit skeptical of the trustworthiness of free models, though, so I thought the next best thing would be to look at official Roblox gear. I decided to take a look at the Super-G LS33 Disruptor, which was one of my favorite gears growing up, using InsertService and I was able to figure out the vast majority of the code, but have been unable to figure out a few parts of it. I also have a few questions about the necessity and use for some of the code that I was less sure of.

Below is the code with my comments. There are four scripts in total; the first two are server scripts and the last two are local scripts.

RayGun:

--Rescripted by Luckymaxer

Tool = script.Parent -- assigning the gun to the variable "Tool"
Handle = Tool:WaitForChild("Handle") -- assigning the gun's brick to the variable "Handle"

Players = game:GetService("Players") -- fetching Players service
Debris = game:GetService("Debris") -- fetching Debris service

ProjectileScript = script:WaitForChild("ProjectileScript") -- fetching the disabled script childed to this script

BasePart = Instance.new("Part") -- creating a new part
BasePart.Shape = Enum.PartType.Block -- changing the shape to be cubic
BasePart.Material = Enum.Material.Plastic -- changing the material to plastic
BasePart.TopSurface = Enum.SurfaceType.Smooth -- changing the surfaces to smooth
BasePart.BottomSurface = Enum.SurfaceType.Smooth -- changing the surfaces to smooth
BasePart.FormFactor = Enum.FormFactor.Custom -- changing the resizing system to be custom 
BasePart.Size = Vector3.new(0.2, 0.2, 0.2) -- changing the size to a small cube
BasePart.CanCollide = true -- setting cancollide true
BasePart.Locked = true -- making unable to be selected
BasePart.Anchored = false -- making it unanchored

BaseProjectile = BasePart:Clone() -- making the projectiles clones of the previously defined part
BaseProjectile.Name = "Projectile" -- naming them as projectiles
BaseProjectile.Shape = Enum.PartType.Ball -- making them spheres
BaseProjectile.Size = Vector3.new(1, 1, 1) -- changing their size to be a bit bigger
BaseProjectile.BrickColor = BrickColor.new("Bright yellow") -- making them yellow
BaseProjectile.Reflectance = 0.5 -- making them shiny
BaseSpark = Instance.new("Sparkles") -- creating a sparkle effect
BaseSpark.Name = "Spark" -- naming the sparkle effect
BaseSpark.SparkleColor = Color3.new(1, 1, 0) -- changing the color of the sparkles
BaseSpark.Enabled = true -- turning them on
BaseSpark.Parent = BaseProjectile -- childing them to the projectiles

Grips = {
	Up = CFrame.new(0, -0.600000024, -0.550000012, 1, 0, -0, -0, 0.19592391, -0.980619073, 0, 0.980619192, 0.19592388),
	Out = CFrame.new(0, -0.600000024, -0.550000012, 1, 0, -0, 0, 0, -1, 0, 1, -0),
} -- cframes of the animated change in grips (???)

Sounds = {
	Equip = Handle:WaitForChild("Equip"),
	PewPew = Handle:WaitForChild("PewPew"),
	Hit = Handle:WaitForChild("Hit"),
} -- assigning all the sounds to variables inside a sound table

ReloadTime = 0.2 -- creating a constant for the reload time of 0.2 seconds

ServerControl = (Tool:FindFirstChild("ServerControl") or Instance.new("RemoteFunction")) -- creating a remote function referred to as "ServerControl" or assigning a pre-existing remote event of the same name to a variable
ServerControl.Name = "ServerControl" -- naming the remote function
ServerControl.Parent = Tool -- childing it to the gun

ClientControl = (Tool:FindFirstChild("ClientControl") or Instance.new("RemoteFunction")) -- see above except with "ClientControl"
ClientControl.Name = "ClientControl" -- naming the remote function
ClientControl.Parent = Tool -- childing it to the gun

ToolEquipped = false -- creating a boolean that will tell us whether the gun is equipped

Tool.Grip = Grips.Out -- sets the grip position to the default one
Tool.Enabled = true -- makes the tool able to be used

function Fire(Direction) -- function that runs when the gun fires, takes a variable "direction"
	
	local SpawnPos = (Handle.Position + (Direction * 10)) -- identifies where the projectile is going to spawn in physical space by taking the gun's position and then adding some space by taking into account the direction
	
	local Gravity = 196.20 -- defines the force of gravity acting on the projectile
	local Force = 180 -- defines another force acting on the projectile
	
	local Mass = (BaseProjectile:GetMass() * Gravity) -- calculates the "weight" of the projectile

	local Projectile = BaseProjectile:Clone() -- clones a projectile from the previously defined projectile
	Projectile.CFrame = CFrame.new(SpawnPos) -- sets the projectile's CFrame to its spawn position
	Projectile.Velocity = (Direction * Force) -- sets the projectile's velocity equal to the direction it's being fired and the previously defined force
	local HitSound = Sounds.Hit:Clone() -- clones the hit sound
	HitSound.Parent = Projectile -- childs the sound to the projectile
	local BodyVelocity = Instance.new("BodyVelocity") -- creating a bodyvelocity
	BodyVelocity.maxForce = Vector3.new(Mass, Mass, Mass) -- sets the maximum force that can be exerted on the projectile in any direction to its "weight"
	BodyVelocity.velocity = (Direction * Force) -- sets the velocity of the bodyvelocity to the same thing as the projectile's velocity
	BodyVelocity.Parent = Projectile -- childs the bodyvelocity to the projectile
	
	local CreatorTag = Instance.new("ObjectValue") -- creating a new objectvalue
	CreatorTag.Value = Player -- assigning its value to the player firing the gun???
	CreatorTag.Name = "Creator" -- naming it creator
	CreatorTag.Parent = Projectile -- childing it to the projectile
	
	local ProjectileScriptClone = ProjectileScript:Clone() -- copying the disabled script childed to this script
	ProjectileScriptClone.Disabled = false -- enabling it
	ProjectileScriptClone.Parent = Projectile -- childing it to the projectile
	
	Debris:AddItem(Projectile, 5) -- destroying the projectile after five seconds in open flight
	Projectile.Parent = game:GetService("Workspace") -- finally adding the projectile to physical space

	Sounds.PewPew:Play() -- playing the firing sound

end

function Activated() -- function that runs when the tool is activated (clicked)
	if not Tool.Enabled or not ToolEquipped or not CheckIfAlive() then -- if the gun is not able to be used, equipped or the player is dead, then...
		return -- do nothing, end the function
	end
	
	local TargetPos = InvokeClient("MousePosition") -- assigns the position of the player's mouse on the screen to a variable
	if not TargetPos then -- if there is no position to assign, then...
		return -- do nothing, end the function
	end
	TargetPos = TargetPos.Position -- sets the previously defined variable to a position in 3d space???
	local Direction = (TargetPos - Handle.Position).unit -- sets the direction by creating a 3d unit vector that points from the gun's brick to the target position
	
	Tool.Enabled = false -- makes the gun unable to be used for the duration of the shot
	Tool.Grip = Grips.Up -- transfers the position of the gun to its "fired" angle
	Fire(Direction) -- runs the firing function with the given direction
	wait(ReloadTime) -- waits for the defined period of time
	Tool.Grip = Grips.Out -- transfers back to the default angle
	wait(ReloadTime) -- waits again for the defined period of time
	Tool.Enabled = true -- allows the gun to be used again now that the shot has been fired

end

function CheckIfAlive() -- function that checks if the player is alive, used in other functions to prevent use after death
	return (((Player and Player.Parent and Character and Character.Parent and Humanoid and Humanoid.Parent and Humanoid.Health > 0) and true) or false) -- ends the function by providing whether or not the player, character and humanoid exist, are childed to something in the game, and the humanoid is not out of health
end

function Equipped() -- function that runs when the tool is equipped
	Character = Tool.Parent -- the character is defined as the tool's parent
	Player = Players:GetPlayerFromCharacter(Character) -- the player is defined as the player of the character who the tool is childed to
	Humanoid = Character:FindFirstChild("Humanoid") -- the humanoid is defined as that character's humanoid
	if not CheckIfAlive() then -- if the check is run and the player is dead, then...
		return -- do nothing, end the function
	end
	ToolEquipped = true -- sets the previously defined boolean to true
	Sounds.Equip:Play() -- plays the equipping sound
end

function Unequipped() -- function that runs when the tool is unequipped
	Tool.Grip = Grips.Out -- sets the gun's position to the default position regardless of what's happening
	for i, v in pairs(Sounds) do -- take every sound in the sound table and...
		v:Stop() -- stop it playing
	end
	ToolEquipped = false -- sets the previously defined boolean to false
end

function InvokeClient(Mode, Value) -- ???
	local ClientReturn = nil
	pcall(function()
		ClientReturn = ClientControl:InvokeClient(Player, Mode, Value)
	end)
	return ClientReturn
end

ServerControl.OnServerInvoke = (function(player, Mode, Value) -- ???
	if player ~= Player or not ToolEquipped or not CheckIfAlive() or not Mode or not Value then -- if the player from the server invoke is not the same as the gun's owner, the tool is not equipped, the gun's owner is dead or "Mode" and "Value" don't exist, then... (???)
		return -- do nothing, end the function
	end
end)

Tool.Activated:connect(Activated) -- if the tool is activated, run the activated function
Tool.Equipped:connect(Equipped) -- if the tool is equipped, run the equipped function
Tool.Unequipped:connect(Unequipped) -- if the tool is unequipped, run the unequipped function

[Disabled] ProjectileScript:

--Rescripted by Luckymaxer

Part = script.Parent -- assigning the projectile to a variable

Players = game:GetService("Players") -- fetching Players service
Debris = game:GetService("Debris") -- fetching Debris service

Creator = Part:WaitForChild("Creator") -- assigning the objectvalue that describes the owner of the gun the projectile was fired from

Sounds = {
	Hit = Part:WaitForChild("Hit")
} -- assigning all the applicable sounds to a table

Damage = 30 -- assigning the damage the projectile will do to a constant

function IsTeamMate(Player1, Player2) -- function that is used to prevent friendly fire
	return (Player1 and Player2 and not Player1.Neutral and not Player2.Neutral and Player1.TeamColor == Player2.TeamColor) -- ends the function by providing whether or not both players exist, are not neutral, and have the same color team
end

function TagHumanoid(humanoid, player) -- function to add an objectvalue to a player's humanoid
	local Creator_Tag = Instance.new("ObjectValue") -- creates a new objectvalue
	Creator_Tag.Name = "creator" -- names it "creator"
	Creator_Tag.Value = player -- sets it to the player
	Debris:AddItem(Creator_Tag, 2) -- calls Debris service to set it to destroy itself within 2 seconds
	Creator_Tag.Parent = humanoid -- childs it to the humanoid
end

function UntagHumanoid(humanoid) -- function to remove all tags from a humanoid
	for i, v in pairs(humanoid:GetChildren()) do -- take all the children of the humanoid and...
		if v:IsA("ObjectValue") and v.Name == "creator" then -- if the child is an objectvalue and it's named "creator", then...
			v:Destroy() -- destroy it
		end
	end
end

function Touched(Hit) -- function that runs when the projectile is touched
	if not Hit or not Hit.Parent then -- if "Hit" does not exist or it is not childed to something, then...
		return -- do nothing, end the function
	end
	local character = Hit.Parent -- assigns the parent of the thing that was hit to a variable, intended to be the player's character
	if character:IsA("Hat") then -- if the parent of the thing is a hat or accessory, then...
		character = character.Parent -- the variable is now the previous parent's parent, aka the player's character
	end
	local HasForceField = false -- creating a boolean to determine whether the player has a forcefield or not
	for i, v in pairs(character:GetChildren()) do -- take all the children of the character and...
		if v:IsA("ForceField") then -- if one of them is a forcefield, then...
			HasForceField = true -- change the boolean to reflect that they have a forcefield
		end
	end
	local humanoid = character:FindFirstChild("Humanoid") -- assigning the character's humanoid to a variable
	if not HasForceField and humanoid and humanoid.Health > 0 then -- if the player doesn't have a forcefield, the humanoid exists and its not dead, then...
		local CreatorPlayer = (((Creator and Creator.Value and Creator.Value:IsA("Player")) and Creator.Value) or nil) -- ???
		local player = Players:GetPlayerFromCharacter(character) -- assigns the player of the character being shot to a variable
		if CreatorPlayer and player and (CreatorPlayer == player or IsTeamMate(CreatorPlayer, player)) then -- ???
			return -- do nothing, end the function
		end
		UntagHumanoid(humanoid) -- run the untagging function to clear all previous tags
		TagHumanoid(humanoid, CreatorPlayer) -- run the tagging function to add the tag to "CreatorPlayer" (???)
		humanoid:TakeDamage(Damage) -- delivers the damage to the humanoid
	end
	Sounds.Hit:Play() -- plays the "Hit" sound
	Part:Destroy() -- clears the projectile
end

Part.Touched:connect(Touched) -- when the projectile is touched, run the "Touched" function

Debris:AddItem(Part, 5) -- tells debris service to delete the projectile after five seconds

LocalScript:

--Made by Luckymaxer

Tool = script.Parent -- assigning the gun to a variable
Handle = Tool:WaitForChild("Handle") -- assigning the gun's brick to a variable

Players = game:GetService("Players") -- fetching the "Players" service
RunService = game:GetService("RunService") -- fetching the "Run" service
UserInputService = game:GetService("UserInputService") -- fetching the "User Input" service

Animations = {} -- creating a table of animations (???)
LocalObjects = {} -- creating a table of "local objects" (???)

ServerControl = Tool:WaitForChild("ServerControl") -- assigning the server control remote function to a variable
ClientControl = Tool:WaitForChild("ClientControl") -- assigning the client control remote function to a variable

InputCheck = Instance.new("ScreenGui") -- creates a new screen gui
InputCheck.Name = "InputCheck" -- names it "InputCheck"
InputButton = Instance.new("ImageButton") -- creates an image button
InputButton.Name = "InputButton" -- names it "InputButton"
InputButton.Image = "" -- changes the image to nothing
InputButton.BackgroundTransparency = 1 -- makes the background of the button transparent
InputButton.ImageTransparency = 1 -- makes the image transparent
InputButton.Size = UDim2.new(1, 0, 1, 0) -- sets the button to cover the entirety of its parent
InputButton.Parent = InputCheck -- childs the button to the previous gui

Rate = (1 / 60) -- creates a constant that describes a rate (???)

ToolEquipped = false -- creates a boolean that describes whether or not the gun is equipped

function SetAnimation(mode, value) -- ???
	if mode == "PlayAnimation" and value and ToolEquipped and Humanoid then
		for i, v in pairs(Animations) do
			if v.Animation == value.Animation then
				v.AnimationTrack:Stop()
				table.remove(Animations, i)
			end
		end
		local AnimationTrack = Humanoid:LoadAnimation(value.Animation)
		table.insert(Animations, {Animation = value.Animation, AnimationTrack = AnimationTrack})
		AnimationTrack:Play(value.FadeTime, value.Weight, value.Speed)
	elseif mode == "StopAnimation" and value then
		for i, v in pairs(Animations) do
			if v.Animation == value.Animation then
				v.AnimationTrack:Stop(value.FadeTime)
				table.remove(Animations, i)
			end
		end
	end
end

function CheckIfAlive() -- function that checks if the player is alive, used in other functions to prevent use after death
	return (((Character and Character.Parent and Humanoid and Humanoid.Parent and Humanoid.Health > 0 and Player and Player.Parent) and true) or false) -- ends the function by providing whether or not the player, character and humanoid exist, are childed to something in the game, and the humanoid is not out of health
end

function Equipped(Mouse) -- function that plays when the tool is equipped
	Character = Tool.Parent -- assigns the parent of the gun to a variable
	Player = Players:GetPlayerFromCharacter(Character) -- assigns the player of the character to a variable
	Humanoid = Character:FindFirstChild("Humanoid") -- assigns the character's humanoid to the variable
	ToolEquipped = true -- set the previously defined boolean to true
	if not CheckIfAlive() then -- if the player is dead, then...
		return -- do nothing, end the function
	end
	Spawn(function() -- ???
		PlayerMouse = Player:GetMouse() -- assigns a variable to the player's mouse
		Mouse.Button1Down:connect(function() -- when the player presses down the left mouse button, run this function...
			InvokeServer("Button1Click", {Down = true}) -- ???
		end)
		Mouse.Button1Up:connect(function() -- when the player releases the left mouse button, run this function...
			InvokeServer("Button1Click", {Down = false}) -- ???
		end)
		local PlayerGui = Player:FindFirstChild("PlayerGui") -- assigns a variable to a gui inside the player called "PlayerGui" (???)
		if PlayerGui then -- if this gui exists, then...
			if UserInputService.TouchEnabled then -- ???
				InputCheckClone = InputCheck:Clone() -- creates a clone of the gui created earlier in the script
				InputCheckClone.InputButton.InputBegan:connect(function() -- when the button in the gui created earlier is pressed down, run this function...
					InvokeServer("Button1Click", {Down = true}) -- does the same thing as when the left mouse button is pressed down
				end)
				InputCheckClone.InputButton.InputEnded:connect(function() -- when the button in the gui created arleri is released, run this function...
					InvokeServer("Button1Click", {Down = false}) -- does the same thing as when the left mouse button is released
				end)
				InputCheckClone.Parent = PlayerGui -- sets the parent of the gui clone to the gui being assigned earlier
			end
		end
	end)
end

function Unequipped() -- runs when the gun is unequipped
	LocalObjects = {} -- ???
	if InputCheckClone and InputCheckClone.Parent then -- if the clone of the gui exist and has a parent, then...
		InputCheckClone:Destroy() -- destroy it
	end
	for i, v in pairs(Animations) do -- take every animation and...
		if v and v.AnimationTrack then -- if the animation and the animation track exist, then... (???)
			v.AnimationTrack:Stop() -- stop the animation from playing
		end
	end
	for i, v in pairs({ObjectLocalTransparencyModifier}) do -- ???
		if v then
			v:disconnect()
		end
	end
	Animations = {} -- ???
	ToolEquipped = false -- set the boolean to false
end

function InvokeServer(mode, value) -- ???
	pcall(function()
		local ServerReturn = ServerControl:InvokeServer(mode, value)
		return ServerReturn
	end)
end

function OnClientInvoke(mode, value) -- ???
	if mode == "PlayAnimation" and value and ToolEquipped and Humanoid then
		SetAnimation("PlayAnimation", value)
	elseif mode == "StopAnimation" and value then
		SetAnimation("StopAnimation", value)
	elseif mode == "PlaySound" and value then
		value:Play()
	elseif mode == "StopSound" and value then
		value:Stop()
	elseif mode == "MousePosition" then
		if not PlayerMouse and CheckIfAlive() then
			PlayerMouse = Player:GetMouse()
		end
		return {Position = PlayerMouse.Hit.p, Target = PlayerMouse.Target}
	elseif mode == "SetLocalTransparencyModifier" and value and ToolEquipped then
		pcall(function()
			local ObjectFound = false
			for i, v in pairs(LocalObjects) do
				if v == value then
					ObjectFound = true
				end
			end
			if not ObjectFound then
				table.insert(LocalObjects, value)
				if ObjectLocalTransparencyModifier then
					ObjectLocalTransparencyModifier:disconnect()
				end
				ObjectLocalTransparencyModifier = RunService.RenderStepped:connect(function()
					for i, v in pairs(LocalObjects) do
						if v.Object and v.Object.Parent then
							local CurrentTransparency = v.Object.LocalTransparencyModifier
							if ((not v.AutoUpdate and (CurrentTransparency == 1 or  CurrentTransparency == 0)) or v.AutoUpdate) then
								v.Object.LocalTransparencyModifier = v.Transparency
							end
						else
							table.remove(LocalObjects, i)
						end
					end
				end)
			end
		end)
	end
end

ClientControl.OnClientInvoke = OnClientInvoke -- ???
Tool.Equipped:connect(Equipped) -- when the gun is equipped, run the "Equipped" function
Tool.Unequipped:connect(Unequipped) -- when the gun is unequipped, run the "Unequipped" function

MouseIcon:

--Made by Luckymaxer

Mouse_Icon = "rbxasset://textures/GunCursor.png" -- assigning a variable to the link for the gun cursor
Reloading_Icon = "rbxasset://textures/GunWaitCursor.png" -- assigning a variable to the link for the gun reload cursor

Tool = script.Parent -- assigning a variable to the gun

Mouse = nil -- creating a nil variable for later assignment

function UpdateIcon() -- function that updates the mouse icon when run
	if Mouse then -- if "mouse" exists, then...
		Mouse.Icon = Tool.Enabled and Mouse_Icon or Reloading_Icon -- ???
	end
end

function OnEquipped(ToolMouse) -- function that runs when the gun is equipped
	Mouse = ToolMouse -- sets the nil variable equal to the "ToolMouse" (???)
	UpdateIcon() -- runs the "UpdateIcon" function
end

function OnChanged(Property) -- ???
	if Property == "Enabled" then
		UpdateIcon()
	end
end

Tool.Equipped:connect(OnEquipped) -- when the gun is equipped, run the "Equipped" function
Tool.Changed:connect(OnChanged) -- when the gun is "changed", run the "Changed" function (???)

Here are a list of questions I was hoping could be answered:

  1. Anything that has triple question marks next to it I don’t understand at all and need help with
  2. What is the purpose of the two local scripts aside from some adding some polishing when the two server scripts seem to cover the entire functioning of the gun?
  3. Going off the previous question, am I correct in my assumption that LocalScript has to do with configuring the gun for mobile players?
  4. What are the “Animations” and “LocalObjects” in LocalScript in reference to? The only real animation I know about is the shifting of the gun’s grip and that’s done in the server scripts.
  5. In RayGun, how am I supposed to read the 12-argument CFrames? I’ve looked on the developer wiki but I don’t understand how to interpret the “rotation matrix” the article refers to.
  6. What is the utility in RayGun of creating the projectile, then cloning it and changing the clone to what the actual projectile looks like, then, in the firing function, cloning that projectile again and making the clone of the clone what’s ultimately fired? Wouldn’t it be easier to just create the projectile within the firing function?
  7. I need another reminder as to what differentiates remote functions and remote events, and why the former is being used here instead of the latter.
  8. Is any of this a good way of going about things? What are the possible negatives you see in this?

Please let me know if you need help understanding my comments for whatever reason.

questions 2 & 3
local scripts allow you to detect player input, change things depending on the player for example GUI that is a common thing that you use a local script for.

question 7
my understanding between remote events and remote functions is that remote events are a one way communication and remote functions are a 2 way communication.

Not to diss on the original authors but official Roblox gear is one of the worst places I can think of to learn to code. Often times they’re bloated, full of overkill complexity and have so many redundant “one-script-fits-all” requirements and functions. The gear may no longer work anyways. I can explain a couple things though, in the order I’m reading the post.

RayGun:

grips is what you think it is, it’s using oldschool CFraming of the tools’ Handle’s grip position to make it move. Should be legacy stuff by now, the engines capable of much cleaner animation.

InvokeClient is the server script invoking the client to do work, I’m not sure what without playing with the gear myself.

OnServerInvoke is the server listening for the client to invoke it. Looks like a redundancy.

LocalScript:

Animations and LocalObjects are containers for what they sound like, not the easiest setup to debug.

Rate looks like a global constant intended to reference to make things run equivalent to 60 fps, even if the client is not at that speed. I don’t see it used anywhere though. There used to be problems that players on worse clients would simulate things slower, which created havoc when FPS unlocked players could run things well beyond the programmers’ intent.

SetAnimation cancels any active animation and starts a new one if mode==“PlayAnimation”

Spawn(function() ...) is basic multithreading, today you should wrap coroutines instead.

LocalObjects and Animations set to {} is clearing the container.

InvokeServer is wrapping an InvokeServer call in a pcall, safest way to do it.

OnClientInvoke is the work the client should do when the server invokes it.

ClientControl.OnClientInvoke = OnClientInvoke looks like a funny way to write ClientControl.OnClientInvoke:connect(OnClientInvoke)

MouseIcon:

Mouse.Icon = Tool.Enabled and Mouse_Icon or Reloading_Icon
This one I like. Ternary operators are very useful. This is a really condensed version of:

if Tool.Enabled == true then
    Mouse.Icon = Mouse_Icon
else
    Mouse.Icon = Reloading_Icon
end

Ternary operators are universal to other programming languages and is definitely something to learn!

Mouse = ToolMouse is assigning the users’ mouse to a global variable, as the programmer doesn’t want to reference the mouse unless the tool is actively being held.

OnChanged is a function listening for Tool.Changed. It’s seeing if the tool goes from unequipped to equipped, at which point it’s calling UpdateIcon()

To your general questions:

  1. Two local scripts isn’t a hard requirement, but the programmer chose to separate the functionality of the cursor icon from the gun’s mechanics as they aren’t co-dependant.

  2. I don’t see anything deliberately accommodating mobile players, in fact, the only Input events I’m seeing are Mouse.Button1Down and Button1Up, strictly PC controls.

  3. Animations and LocalObjects tables look redundant, can’t explain that myself.

  4. CFrame constructor logic is ugly, and unless you really need to, it’s not worth learning. This may have been the only way at the time of the gear being programmed to create CFrames, but now it’d be easier to store a CFrame.new() variable.

  5. No clue that sounds weird

  6. Remote events say “do this”, remote functions say “do this and tell me what you get” (they expect a return/reply)

  7. I would honestly nuke a lot of this and just take some skeleton bits you may want like the equip/unequip handling…

1 Like

Imma try to answer all of your queries and if anyone finds any of em incorrect, pls feel free to point it out.

1

RayGun Script

The Grips consist of CFrames which specify the orientation and position of the tool when it is Equipped/Unequipped. The second one is default whereas the first one is the user-specified grip.

The CreatorTag ObjectValue is a trick used by games with Ranged Projectiles to determine which player hit the target last. This helps in assists ane kills.

For eg: Player1 hits Player2, so an ObjectValue is created inside Player2, with its value set to Player1. But then Player3 hits Player2 and kills him. Here the kill will be assigned to Player3 but by looking at the creator tag, we can also determine that Player1 hit Player2 so we can award him an assist. The ObjectValue is also added to Debris so it would be destroyed after 5 seconds to remove the assist if the player isn’t killed in that window.

The TargetPos = TargetPos.Position is just done lazily as a way to not do TargetPos.Position since InvokeClient("MousePosition") returns a table like this => {Position = PlayerMouse.Hit.p, Target = PlayerMouse.Target}.

InvokeClient is, as the name suggests, used to invoke a client through a RemoteFunction called ClientControl. Thd parameters Mode and Value are just strings specifying an action like PlayAnimation for the client to do, which the client will compute after the event is recieved client-side. However I must say that the variable Player is nowhere present in the script and I’d like to knpw if u had removed it.

The ServerControl.OnServerInvoke is used by the client to invoke the server but in reality it is stupid since the callback does nothing.

Projectile Script

local CreatorPlayer = (((Creator and Creator.Value and Creator.Value:IsA("Player")) and Creator.Value) or nil)

This code snippet is essentially an easy way to do this:

local CreatorPlayer
if Creator then
    if (not Creator.Value) or (not Creator.Value:IsA("Player") then
        CreatorPlayer = nil
    else
        CreatorPlayer = Creator.Value
else
    CreatorPlayer = nil
end

Its just checking if creator.Value exists and it is set to an Player instance.

The next one I can’t decipher since thr variable Creator is nowhere to be seen. It also affects my previous conclusion and due to that the previous one is done through logic and instinct.

LocalScript

The LocalObjects is just an array of objects whose Transparency property will be set to a specified value by the server. The reason it isn’t done server sided is because then it would be set like that for all players, when our aim is to just set it for one player.

The Animations is an array that holds all the animations that the player is playing during certain situations specified by the server. For eg: The client fires an event to server when he clicks the LMB and the server invokes the client to play the animation for shooting. EXCEPT THIS IS TOTALLY UNNECESSARY SINCE YOU CAN LOAD THE ANIMATION ON THE CLIENT SERVER SIDE!!!.

The function SetAnimation just plays or stops the animation sent by the server, with a mode determining the action and a value determining the animation, its speed, weight, fadetime. It is then appended to the previously mentioned Animations which holds all the playing animations.

Spawn I’d assume is just task.spawn for multithreading but today u shud use coroutines instead, which spawns a function that detects when the player has clicked/unclicked the LMB and fires the outcome to the server using a table {Down = true or false}. And in case the playet is using mobile, it checks if a certain button is pressed rather than the mouse.

When the gun is unequipped it just resets the Animations and LocalObjects. Before that though it loops through all playing animations inside Animations and stops it. It also disconnects the ObjectTransparencyModifier which is an unnecessary RunService.RenderStepped, which loops over all the objects which are keys in LocalObjects and sets it Transparency to the value of that object in the table.

The OnClientInvoke just recieves an action and value from the server and does stuff accordingly, pretty self-explanatory.

Lastly the ClientControl.OnClientInvoke callback, which means a function to be executed when the event is called, is set to the afore mentioned OnClientInvoke.

2

The 2 LocalScripts deals with Player functions such as detecting input and when the tool is equipped/unequipped. This can’t be done by a server, and the second localscript changed the Player's crosshair/mouseIcon, which again can’t be done through the server.

3

No. You are right, but it also does way more functionlities.

4

I have alr answered abt the LocalObjects, as for the Animations, changing the grip isn’t exactly an animation. It is just positioning the grip so it looks natural, just like u hold an object irl. Animations are more complex movement done through the Roblox's Animation Editor or 3rd party software like Blender or Moon Animator. Try and check em out when u have the time.

5

CFrames are made up of 12 components, even though most ppl stick with 6. The first three of the 12 numbers are the x, y, and z components of the CFrame, in other words the position. The rest of the numbers make up the rotation aspect of the CFrame. The columns represents the rightVector, upVector, and negative lookVector respectively. No need to know all of em. Just the position and orientation, which can be done multiple ways.

6
Your assumption are, I’m afraid incorrect. First about the cloning. Unlike wt u said, it isn’t actually cloning again and again and again, but rather creating the projectile. First a basepart is made and assigned its values and then it is cloned, can also b used as it is, but the creator did it to not have a tremendous code block describing the properties but rather have two seperate blocks. This part makes up the Projectile.

Then in the firing it is cloned again bcuz the player is gunna be firing multiple projectiles and if we don’t clone the initial one, its properties all will be changed and we won’t have a reference prototype.

Then atlast, the ProjectileScript is cloned and parented to the projectile as it holds all the projectile logic inside it, and is enabled to start computing.

7

You could say I’m lazy but either way I find this article on RemoteEvents and RemoteFunctions to be way more better than my own explanation XD.

8
Oh boi, do I have a lot to let you know…

First of all, this thing must be atleast 4 yrs old, and the API has improved since then. For eg: BodyVelocity has been superseded by VectorForce and AlignPosition, Spawn has a much better alternatives such as Promise and courotines.

And then the bad practices, InvokeClient shud never be done as it yeilds the whole script until the callback is recieved and if the client leaves b4 giving an input, it can break the entire script! And modules like FastCast are 10 times better than the implementation done here. And as I mentioned before Animations can be loaded server side and it is total stupidity to invoke a client for it, due to the risks I mentioned above.

All in all, don’t use this in an official game, just learn some essential bits such as the CreatorTag and using all my input, try to make ur own version of this.

TL;DR: Don’t use this, it’s API is outdated, its practices are bad, and there are tons of other projectile scripts/utilities which are better.

P.S: If u have any questions, you can ask em here or dm me abt it. Also idk abt u but I feel like I shall recieve the solution for my labour :slight_smile:

Regards,
Mystxry.

There are easier ways of doing these. These models are outdated and run on deprecated and/or just overcomplicated code. There are bad examples. If you want to make a gun, just learn about raycast weapons, if you like physical bullets, theres a thing for that too. Swords are pretty easy too. Just a touched event.

1 Like

I did a bit of coding on my own based on the feedback I got from the comments and based on the code from the gear and I created a test gun that isn’t working. I made two scripts, both server scripts, one for the gun and one that’s placed inside each projectile when it spawns.

The main issue I’m having right now is with getting the direction in which to fire the projectile. I saw a lot of feedback saying to avoid using :InvokeClient() but the script for the gear seems to be using that to get the position of the mouse in 3D space. What other ways of getting the position can I try? I tried the UIS function :GetMouseLocation() but it doesn’t seem to be giving me the position in 3D space, only the position on the screen.

Any other pieces of feedback or advice about things I could do better are also appreciated. I want to keep this a projectile-based gun, so I’m not going to switch to raycast even though some people may believe it to be better.

“GunScript” for the gun:

local gun = script.Parent
local grip = gun:WaitForChild("Handle")

local players = game:GetService("Players")
local debris = game:GetService("Debris")
local UIS = game:GetService("UserInputService")

local bulletScript = script:WaitForChild("BulletScript")

local template = Instance.new("Part")
template.Material = Enum.Material.Neon
template.Size = Vector3.new(0.25, 0.25, 4)
template.Color = Color3.fromRGB(239, 184, 56)
template.CanCollide = true
template.Anchored = false
template.Locked = true

local flash = Instance.new("PointLight")
flash.Name = "Flash"
flash.Color = template.Color
flash.Brightness = 2
flash.Enabled = true
flash.Parent = template

local RELOAD_TIME = 0.2
local BULLET_SPEED = 100

local equipped = true
gun.Enabled = true

local function fire(direction)
	local spawnPos = (grip.Position + (direction * 10))
	
	local gravity = 196.20
	local mass = (template:GetMass() * gravity)
	
	local bullet = template:Clone()
	bullet.CFrame = CFrame.new(spawnPos)
	bullet.Velocity = (direction * BULLET_SPEED)
	
	local bodyV = Instance.new("BodyVelocity")
	bodyV.MaxForce = Vector3.new(mass, mass, mass)
	bodyV.Velocity = (direction * BULLET_SPEED)
	bodyV.Parent = bullet
	
	local creator = Instance.new("ObjectValue")
	creator.Value = player
	creator.Name = "Creator"
	creator.Parent = bullet
	
	local bulletScriptClone = bulletScript:Clone()
	bulletScriptClone.Disabled = false
	bulletScriptClone.Parent = bullet
	
	debris:AddItem(bullet, 5)
	
	bullet.Parent = workspace
end

local function deadCheck()
	return (((player and player.Parent and character and character.Parent and humanoid and humanoid.Parent and humanoid.Health > 0) and true) or false)
end

local function activated()
	if not gun.Enabled or not equipped or not deadCheck() then
		return
	end

	local target = UIS:GetMouseLocation()
	if not target then
		return
	end
	target = target.Position -- ERROR: Position is not a valid member of Vector2
	local direction = (target - grip.Position).unit

	gun.Enabled = false
	fire(direction)
	wait(RELOAD_TIME)
	gun.Enabled = true

end

local function equipped()
	character = gun.Parent
	player = players:GetPlayerFromCharacter(character)
	humanoid = character:FindFirstChild("Humanoid")
	if not deadCheck() then
		return
	end
	equipped = true
end

local function unequipped()
	equipped = false
end

gun.Activated:Connect(activated)
gun.Equipped:Connect(equipped)
gun.Unequipped:Connect(unequipped)

“BulletScript” for the projectiles:

local bullet = script.Parent

local players = game:GetService("Players")
local debris = game:GetService("Debris")

local creator = bullet:WaitForChild("Creator")

local DAMAGE = 30

local function friendlyFire(player1, player2)
	return (player1 and player2 and not player1.Neutral and not player2.Neutral and player1.TeamColor == player2.TeamColor)
end

local function tag(humanoid, player)
	local creatorTag = Instance.new("ObjectValue")
	creatorTag.Name = "creator"
	creatorTag.Value = player
	debris:AddItem(creatorTag, 2)
	creatorTag.Parent = humanoid
end

local function untag(humanoid)
	for _, tag in pairs(humanoid:GetChildren()) do
		if tag:IsA("ObjectValue") and tag.Name == "creator" then
			tag:Destroy()
		end
	end
end

local function shot(hit)
	if not hit or not hit.Parent then
		return
	end
	local character = hit.Parent
	if character:IsA("Hat") then
		character = character.Parent
	end
	local forcefield = false
	for _, ff in pairs(character:GetChildren()) do
		if ff:IsA("ForceField") then
			forcefield = true
		end
	end
	local humanoid = character:FindFirstChild("Humanoid")
	if not forcefield and humanoid and humanoid.Health > 0 then
		local creatorPlayer
		if creator then
			if not creator.Value or not creator.Value:IsA("Player") then
				creatorPlayer = nil
			else
				creatorPlayer = creator.Value
			end
		else
			creatorPlayer = nil
		end
		local player = players:GetPlayerFromCharacter(character)
		if creatorPlayer and player and (creatorPlayer == player or friendlyFire(creatorPlayer, player)) then
			return
		end
		untag(humanoid)
		tag(humanoid)
		humanoid:TakeDamage(DAMAGE)
	end
	bullet:Destroy()
end

bullet.Touched:Connect(shot)

debris:AddItem(bullet, 5)

Still trying to figure out how to get the position of the mouse in 3d space, any help is appreciated.

Mouse.Hit -- Mouse Cframe

Although this still casts a ray, it does it automatically. There is a documentation for the mouse object, try checking that.

This is how you get the mouse if you don’t know.

local player = game.Players.LocalPlayer
local Mouse = player:GetMouse()

I made slight edits based on what you suggested. I actually knew about Mouse.Hit beforehand but thought that it could only work in local scripts. I got the LocalPlayer in a different way (by grabbing the player of the character the gun was parented to) and attempted to set the target position to Mouse.Hit, but the :GetMouse() function doesn’t seem to be returning anything.

GunScript:

local gun = script.Parent
local grip = gun:WaitForChild("Handle")

local players = game:GetService("Players")
local debris = game:GetService("Debris")
local UIS = game:GetService("UserInputService")

local bulletScript = script:WaitForChild("BulletScript")

local template = Instance.new("Part")
template.Material = Enum.Material.Neon
template.Size = Vector3.new(0.25, 0.25, 4)
template.Color = Color3.fromRGB(239, 184, 56)
template.CanCollide = true
template.Anchored = false
template.Locked = true

local flash = Instance.new("PointLight")
flash.Name = "Flash"
flash.Color = template.Color
flash.Brightness = 2
flash.Enabled = true
flash.Parent = template

local RELOAD_TIME = 0.2
local BULLET_SPEED = 100

local equipped = true
gun.Enabled = true

local function fire(direction)
	local spawnPos = (grip.Position + (direction * 10))
	
	local gravity = 196.20
	local mass = (template:GetMass() * gravity)
	
	local bullet = template:Clone()
	bullet.CFrame = CFrame.new(spawnPos)
	bullet.Velocity = (direction * BULLET_SPEED)
	
	local bodyV = Instance.new("BodyVelocity")
	bodyV.MaxForce = Vector3.new(mass, mass, mass)
	bodyV.Velocity = (direction * BULLET_SPEED)
	bodyV.Parent = bullet
	
	local creator = Instance.new("ObjectValue")
	creator.Value = player
	creator.Name = "Creator"
	creator.Parent = bullet
	
	local bulletScriptClone = bulletScript:Clone()
	bulletScriptClone.Disabled = false
	bulletScriptClone.Parent = bullet
	
	debris:AddItem(bullet, 5)
	
	bullet.Parent = workspace
end

local function deadCheck()
	return (((player and player.Parent and character and character.Parent and humanoid and humanoid.Parent and humanoid.Health > 0) and true) or false)
end

local function activated()
	if not gun.Enabled or not equipped or not deadCheck() then
		return
	end

	local target = mouse.Hit -- ERROR: attempt to index nil with 'Hit'
	if not target then
		return
	end
	target = target.Position
	local direction = (target - grip.Position).unit

	gun.Enabled = false
	fire(direction)
	wait(RELOAD_TIME)
	gun.Enabled = true

end

local function equipped()
	character = gun.Parent
	player = players:GetPlayerFromCharacter(character)
	humanoid = character:FindFirstChild("Humanoid")
	mouse = player:GetMouse()
	if not deadCheck() then
		return
	end
	equipped = true
end

local function unequipped()
	equipped = false
end

gun.Activated:Connect(activated)
gun.Equipped:Connect(equipped)
gun.Unequipped:Connect(unequipped)

If this gun script is not a local script, then I’m confused on how UserInputService works, as it takes a user input, meaning it requires to be in a local script.

Taking the player object is very easy, but listening to its inputs requires a local script.

And about the :GetMouse() not returning, it’s used to get player input, it’s probably because it’s in a server script, like I said input only works on a local script.

I figured that I might need a local script. The issue is that once I get the mouse position from the local script (should be pretty easy based on what you said) how do I then transfer that Vector3 data over to the server script? The original gear used :InvokeClient() and remote functions, but some of the replies to this thread have suggested that’s outdated and should be avoided.

:InvokeClient() should never be used as it yields the server. If the client left the game, it will yield infinitely. RemoteFunctions are mostly used for :InvokeServer(), but still, you can just use remote events.

The creator of the gear should have used Tool.Activated in the client. Since they use it on server, they still need to request the client for input and mouse position and stuff. If they had done it on the client, they will only need to communicate once and all requirements are given.

Remote events. Don’t use Remote functions. Like I said, you should Tool.Activated on the client so you only need to send information once.