This is my first tutorial here!
Point out any errors in the code, please. I might have missed something.
0. Quick info
-
0.1
I have made many past attempts at making pew pew guns on roblox in the past, and i thought i’d share some of the “basic” process for beginners here. Keep in mind none of this needs to be the way it is here, feel free to use your own methods. -
0.2
This article covers the very basics on setting up a working gun, making it aim (and how to make it easy to add other things), and making it pew pew at other people. However, This part of the article doesn’t cover projectile math (aka. actually damaging things) or server security! i will only mention how i did it myself.
1. You need to know these things to understand this tutorial properly!
-
Lua
-
How modulescripts work
-
Filtering enabled
-
Rigging and how it works,
Download the old moon animation studio plugin to rig easier. Using plugins to rig for you won’t save you here. Everything should be explained, though, so don’t be discouraged. -
Tables (arrays, storing functions and variables inside)
-
Other minor stuff
1.5 Custom velocity
For code to work properly (i use an older version of inputservice in velocity) you need the following module:
Credit: @ReturnedTrue
2. The spring module
It’s used pretty widely between games for all sorts of movement. Mostly sway. It’s a modulescript, which you can set up easily;
The module; (Prepared for camelCase uppercase the letters at will)
Credit: @x_o
Code usage example;
-- This is a localscript
-- set up some spring
local spring = require(game.ReplicatedStorage.spring)
local mySpring = spring.create()
-- random Random
local random = Random.new()
game:GetService("RunService").RenderStepped:Connect(function(deltaTime)
-- update the spring based on deltaTime.
local movement = mySpring:update(deltaTime)
-- move the part in workspace based on the spring's movement. don't kill me for the code
workspace.part.CFrame = CFrame.new(movement.x,5,0)
end)
while wait(random:NextNumber(.1,1)) do
-- shove the spring in a random direction with some sort of hammer. Amazing.
mySpring:shove(Vector3.new(random:NextNumber(-25,25),0,0))
end
3. Setting up the game
Firstly, we need some folders for general use:
Our magic gun code will be in modules>fps, and the thing that makes it magically do things will be inputController.
You may also get some custom modulescripts at this point. I use ReturnedTrue’s Velocity framework for inputs, leaderstats and code debugging and FastCast for projectiles (i wrote another module that uses fastcast and adds penetration, tracers, ricochets, and other magic things but it’s not public)
4. Setting up guns
Exciting, right? Get a gun model (I will use an ACS gun model cleared of scripts because i’m sharing the place)
We want to do a few things with it:
Add an offsets folder,
Add a settings module,
Add an animations folder,
Add a part named weaponRootPart, paste it in the position of the receiver, make sure the front face is going in the barrel’s direction and the up face is going up, and weld it to the receiver.
Rename one of the major parts (receiver, particularly) to receiver,
Add an attachment inside the receiver that we will use for muzzle flash, and put some cool particles inside of it,
You’ll see why we need both offsets and animations later. Here’s the final structure (don’t mind the stuff inside offsets just yet)
Finally, rig anything you might need for animations (chambering, pistol bolt, etc.) to the weaponRootPart!, weld the rest of the parts, make them not cast shadows (we’ll do it later again lol), and make sure everything is .Anchored = false, group it, name it, and throw it in ReplicatedStorage>weapons.
5. Making a viewmodel
BuT WheRe’s ThE ViewModEL? Less experienced scripters tend to merge the viewmodel with the gun while making it right now. This is stupid and dumb and i hate it for multiple reasons. A smarter approach is merging them together for animation when the gun is equipped. we will do this later, but for now let’s just make a viewmodel that can have that done in the first place.
Small note for blank Motor6d: local mt6d = Instance.new("Motor6D") mt6d.Parent = (viewmodel rootpart)
Make a part named rootPart, group it, and add blank motor6ds for both the arms (one named left, second named right)
Add a BLANK Motor6D, throw it in the rootpart, and set Part0 to the viewmodel rootpart. We will use it for connecting the gun with the viewmodel.
Finally, add an animation controller.
Optionally, if you want coolio camera movement you can add a camera Part, rig it, and animate it later.
Throw the viewmodel into replicatedstorage, or anywhere else you might want it. Just not workspace, it’s not prepared for such habitat without extra code sprinkling.
6. Setting up the controller to equip the weapon
We’ll make the gun equip itself using keys on the keyboard. I stole some code from my main game:
-- input controller
-- The fps module we're about to add
local weaponHandler = require(game.ReplicatedStorage.modules.fps)
--Custom input service. Do this how you want, i just couldn't ever remember how to use other services consistently.
local velocity = require(game.ReplicatedStorage.modules.Velocity):Init(true)
local inputs = velocity:GetService("InputService")
--Current weapon and translating binds from integer to enum. You don't need to understand that.
local curWeapon = nil
local enumBinds = {
[1] = "One";
[2] = "Two";
[3] = "Three";
}
-- Server security. We don't need this right now, so i'll just comment it.
-- local weps,primaryAmmo,secondaryAmmo = game.ReplicatedStorage.weaponRemotes.New:InvokeServer()
--[[
This means if you add a second weapon it'll automatically go under the bind 2,3, etc.
This is a bad thing, at least for later stages. The above commented line shows that we
should get the weapons from the server on-spawned. Here's how it looks in my game:
local defaultWeapons = {
[1] = "UMP45";
-- [2] = "JESUS"; lol railgun
}
--]]
local weps = game.ReplicatedStorage.weapons:GetChildren()
local weapon = weaponHandler.new(weps)
-- clearing viewmodels we could have kept in the camera because of script errors and stuff
local viewmodels = workspace.Camera:GetChildren()
for i,v in pairs(viewmodels) do
if v.Name == "viewmodel" then
v:Destroy()
end
end
-- equip code
for i,v in pairs(weps) do
-- cooldown for spammy bois
local working
-- we will bind this per-weapon
local function equip()
-- if cooldown active, then don't execute the function. for less experienced scripters, this is just the equivalent of:
--[[
local function brug()
if working == false then
-- do stuff
end
end
--]]
if working then return end
working = true
-- if the current equipped weapon is different from the one we want right now (also applies to the weapon being nil)
if curWeapon ~= v then
if weapon.equipped then
weapon:remove()
end
weapon:equip(v.Name)
curWeapon = v
else
-- if it's the same, just remove it
spawn(function()
weapon:remove()
end)
curWeapon = nil
end
working = false
end
-- This means you can have 3 different weapons at once.
inputs.BindOnBegan(nil,enumBinds[i],equip,"Equip : "..i)
end
local function update(dt)
weapon:update(dt)
end
-- marking the gun as unequippable
game.Players.LocalPlayer.Character:WaitForChild("Humanoid").Died:Connect(function() weapon:remove() weapon.disabled = true end)
game:GetService("RunService").RenderStepped:Connect(update)
This should be enough to have the gun nicely equip when we want it to. Now, let’s get to the real magic!
7. Making the guns equip
Now, we’re going to set up some code to make the input controller above not cry about things not existing. i hate him too.
Add a spring named spring to keep the FPS module from screaming about it too. Change name at will.
-- This is game.replicatedstorage.fps
-- Coolio module stuff
local handler = {}
local fpsMT = {__index = handler}
local replicatedStorage = game:GetService("ReplicatedStorage")
local spring = require(replicatedStorage.modules.spring)
-- Functions i like using and you will probably too.
-- Bobbing!
local function getBobbing(addition,speed,modifier)
return math.sin(tick()*addition*speed)*modifier
end
-- Cool for lerping numbers (useful literally everywhere)
local function lerpNumber(a, b, t)
return a + (b - a) * t
end
function handler.new(weapons)
local self = {}
return setmetatable(self,fpsMT)
end
function handler:equip(wepName)
-- we'll be using this soon
end
function handler:remove()
-- shalt thou use this soon
end
function handler:aim(toaim)
-- we'll be using this soon
end
function handler:update(deltaTime)
-- renderstepped, we'll be using this boi soon
end
--[[
Used in my real game for framerate counting. I kept it in for fun.
(decomissioned since like 3 months ago, don't use this)
spawn(function()
local fps = 0
game:GetService("RunService").RenderStepped:Connect(function()
fps = fps + 1
end)
while wait(1) do
local gui = game.Players.LocalPlayer.PlayerGui:FindFirstChild("wepGui")
if gui then
gui.FPScount.Text = string.format("FPS: %s",fps)
end
fps = 0
end
end)
--]]
return handler
Alright. To recap, we’re making a new gun handler inside the control script:
local weaponHandler = require(game.ReplicatedStorage.modules.fps)
local weapon = weaponHandler.new(weps)
This means we can now do weapon:equip() or weapon:remove(). This is metatable stuff, i don’t understand it much but you should only know that you can add any new function that follows function:funcName() end
and you will be able to do weapon:funcName()
too.
we call .new() to prepare some stuff inside the module, like springs. Real life example;
Now, we will need to compile a viewmodel and kill it with no mercy based on input. This means putting stuff in :equip(weapon)
and :remove()
.
function handler:equip(wepName)
-- Explained how this works earlier. we can store variables too!
-- if the weapon is disabled, or equipped, remove it instead.
if self.disabled then return end
if self.equipped then self:remove() end
-- get weapon from storage
local weapon = replicatedStorage.weapons:FindFirstChild(wepName) -- do not cloen
if not weapon then return end -- if the weapon exists, clone it, else, stop
weapon = weapon:Clone()
--[[
Make a viewmodel (easily accessible with weapon.viewmodel too!)
and throw everything in the weapon straight inside of it. This makes animation hierarchy work.
--]]
self.viewmodel = replicatedStorage.viewmodel:Clone()
for i,v in pairs(weapon:GetChildren()) do
v.Parent = self.viewmodel
if v:IsA("BasePart") then
v.CanCollide = false
v.CastShadow = false
end
end
-- Time for automatic rigging and some basic properties
self.camera = workspace.CurrentCamera
self.character = game.Players.LocalPlayer.Character
-- Throw the viewmodel under the map. It will go back to the camera the next render frame once we get to moving it.
self.viewmodel.rootPart.CFrame = CFrame.new(0,-100,0)
-- We're making the gun bound to the viewmodel's rootpart, and making the arms move along with the viewmodel using hierarchy.
self.viewmodel.rootPart.weapon.Part1 = self.viewmodel.weaponRootPart
self.viewmodel.left.leftHand.Part0 = self.viewmodel.weaponRootPart
self.viewmodel.right.rightHand.Part0 = self.viewmodel.weaponRootPart
-- I legit forgot to do this in the first code revision.
self.viewmodel.Parent = workspace.Camera
--[[
Real life example:
self.loadedAnimations.idle = self.viewmodel.AnimationController:LoadAnimation(self.settings.anims.viewmodel.idle)
self.loadedAnimations.idle:Play()
self.tweenLerp("equip","In")
self.playSound("draw")
--]]
self.equipped = true -- Yay! our gun is ready.
end
function handler:remove()
-- Not much to see here yet. even the real life function for removing a weapon takes like 30 lines ***on the client***.
self.viewmodel:Destroy()
self.viewmodel = nil
self.equipped = false -- Nay! your gun is gone.
end
Now, if the gun is equipped it obviously won’t move along with the camera. That’s sad. I’m sad.
Why not make it?
Here’s where a bigger part of future-proofing comes in. We will move our weapon around based on offsets. For now, though, let’s just move it to the camera every render-step.
function handler:update(deltaTime)
-- IF we have a gun right now. We're checking the viewmodel instead for "reasons".
if self.viewmodel then
self.viewmodel.rootPart.CFrame = self.camera.CFrame
end
end
Now, you might notice something’s not right. The gun is implanted straight into your face.
We will fix this by adding an offset.
We do this by adding CFrame value into Offsets. name it idle or something.
Furthermore, let’s move the gun based on the input from it.
-- it'll be final for a reason. You'll see!
local finalOffset = self.viewmodel.offsets.idle.Value
--ToWorldSpace basically means rootpart.CFrame = camera CFrame but offset by xxx while taking rotation into account. I don't know. You'll see how it works soon enough.
self.viewmodel.rootPart.CFrame = self.camera.CFrame:ToWorldSpace(finalOffset)
7.1. Editing offsets
Let’s run the game. At this point, the command bar will surely be handy.
You might notice the gun is still inside your face. It won’t leave by itself, after all.
Let’s input some commands that will fix it!
workspace.Camera.viewmodel.offsets.idle.Value = CFrame.new(rightLeft,upDown,frontBack) * CFrame.Angles(rotUpdown,rotLeftright,rotRoll)
This is a command line job. replace the variables with numbers to get a nice looking offset, and eventually we will get something like this:
workspace.Camera.viewmodel.offsets.idle.Value = CFrame.new(0.7,-1.2,-1.1) * CFrame.Angles(0.005,math.pi / 2 + 0.005,0)
At this point, COPY THE IDLE OBJECT, stop the game, and throw it in the place of the old idle offset.
Now, let’s animate the arms since they look like they were removed from your body and the gun is floating using magic.
There’s not much to it, just run the game, copy the viewmodel, stop the game, and paste it in. It should animate nicely. Now, if we have animations, why would you need to use offsets? This is for aiming, and getting a better preview of how it will look ingame. Other offsets too. Notice how many there are in the video below.
Not getting backtracked, let’s make an animation. Don’t include idle movement, you can do that procedurally.
Set whatever priority your heart desires, and loop it.
Upload it, and throw an animation object into the gun’s animations folder with the appropiate ID.
Time to start using settings, although you can just do self.viewmodel.animations.idle
.
Here’s how I do it:
Now, we can:
-add a self.loadedAnimations table inside .new()
(wow! i’m using 2 separate editors!)
-load the settings in the equip function
-load and throw the animation inside it
Basically:
--bla bla bla
self.viewmodel.Parent = workspace.Camera
--load settings. you might want to do this earlier depending on your needs
self.settings = require(self.viewmodel.settings)
--load animation from settings
self.loadedAnimations.idle = self.viewmodel.AnimationController:LoadAnimation(self.settings.animations.viewmodel.idle)
self.loadedAnimations.idle:Play(0) --no lerp time from default pos to prevent stupid looking arms for no longer than 0 frames
self.equipped = true -- Yay! our gun is ready.
--blablabla
The arms look dumb dumb, but we can change that anyway, so i don’t care.
8. Aiming and springs
Now, why not make it look pretty? Everyone likes pretty things.
To do this, we’re gonna make the gun sway around based on spring input, and add a walk cycle.
Let’s do some preparation and coolio math!
Here’s where fps unlockers break things. If the run cycle is rigged to run 60 times a second, it will have trouble running at 100, or even 300!
To fix this top 10th shocking thing you need to know about, we will multiply the movement by deltaTime. this means, if the game is running slowly (30 frames a second), the movement will still be similiar to 140 FPS, or even more. Don’t skim out on this, It’s very important.
Inside the .new() function we create some springs:
self.springs = {}
self.springs.walkCycle = spring.create();
self.springs.sway = spring.create()
Now, let’s do some update math too:
-- IF we have a gun right now. We're checking the viewmodel instead for "reasons".
if self.viewmodel then
-- get velocity for walkCycle
local velocity = self.character.HumanoidRootPart.Velocity
-- it'll be final for a reason. You'll see!
local finalOffset = self.viewmodel.offsets.idle.Value
-- Let's get some mouse movement!
local mouseDelta = game:GetService("UserInputService"):GetMouseDelta()
self.springs.sway:shove(Vector3.new(mouseDelta.x / 200,mouseDelta.y / 200)) --not sure if this needs deltaTime filtering
-- speed can be dependent on a value changed when you're running, or standing still, or aiming, etc.
-- this makes the bobble faster.
local speed = 1
-- modifier can be dependent on a value changed when you're aiming, or standing still, etc.
-- this makes the bobble do more. or something.
local modifier = 0.1
-- See? Bobbing! contruct a vector3 with getBobbing.
local movementSway = Vector3.new(getBobbing(10,speed,modifier),getBobbing(5,speed,modifier),getBobbing(5,speed,modifier))
-- if velocity is 0, then so will the walk cycle
self.springs.walkCycle:shove((movementSway / 25) * deltaTime * 60 * velocity.Magnitude)
-- Sway! Yay!
local sway = self.springs.sway:update(deltaTime)
local walkCycle = self.springs.walkCycle:update(deltaTime)
--ToWorldSpace basically means rootpart.CFrame = camera CFrame but offset by xxx while taking rotation into account. I don't know. You'll see how it works soon enough.
self.viewmodel.rootPart.CFrame = self.camera.CFrame:ToWorldSpace(finalOffset)
self.viewmodel.rootPart.CFrame = self.viewmodel.rootPart.CFrame:ToWorldSpace(CFrame.new(walkCycle.x / 2,walkCycle.y / 2,0))
-- Rotate our rootpart based on sway
self.viewmodel.rootPart.CFrame = self.viewmodel.rootPart.CFrame * CFrame.Angles(0,-sway.x,sway.y)
self.viewmodel.rootPart.CFrame = self.viewmodel.rootPart.CFrame * CFrame.Angles(0,walkCycle.y,walkCycle.x)
end
And now, we have sway. How magnificent.
9. Aiming (for real this time)
Now, you might have noticed nothing happens when we press MouseButton2. I need to congratulate you for your achievement in life, but more importantly explain how to do it.
What we’re going to do:
create a number value
make an aim offset
tween the value when aiming in/out
lerp offsets based on that value
check if you’re aiming and sway less if you are
remove the mouse when aiming
Alright. let’s make it look easy.
add another table inside .new() for easy access and add some values to it:
local self = {}
self.loadedAnimations = {}
self.springs = {}
self.lerpValues = {}
self.lerpValues.aim = Instance.new("NumberValue")
self.springs.walkCycle = spring.create();
self.springs.sway = spring.create()
return setmetatable(self,fpsMT)
Now, jump in the game and have another go at editing the idle value, this time to make it look as if you’re aiming. Info on how-to is above. After you’re done, copy it and throw it into offsets as “aim”
Once you’re done, let’s do some more lerping in the update() function:
-- you can add priorities here! for example, equip offset for procedural equipping would be below aimOffset to overwrite it when removing the gun.
-- here, aim overwrites idle.
local idleOffset = self.viewmodel.offsets.idle.Value
local aimOffset = idleOffset:lerp(self.viewmodel.offsets.aim.Value,self.lerpValues.aim.Value)
-- it'll be final for a reason. You saw!
local finalOffset = aimOffset
“Obviously”, the gun doesn’t aim just yet. To make it, let’s add some code:
inputs.BindOnBegan("MouseButton2",nil,function() weapon:aim(true) end,"AimPewPew")
inputs.BindOnEnded("MouseButton2",nil,function() weapon:aim(false) end,"AimPewPew")
This makes the function aim trigger when holding RMB. Obviously this input approach is straight up unclean and stupid and i hate it, but it’s simple. IRL code:
This does cool stuff. Don’t steal it please, though.
now,
function handler:aim(toaim)
will be used! Let’s edit it!
For the future: i wrote a simple function to make this much easier. self.tweenLerp("aim","In")
is the function in question. This is because the in/out tween times are based on simple values we can grab autonomously instead of copy pasting code, and take my word for it: this process repeats itself many times. I’m not going to feature it in this tutorial; just know that it’s very possible indeed.
Here is our aiming code. Just basic tweening:
function handler:aim(toaim)
-- we'll be using this soon
-- We used it! ha!
-- add a tweenService variable at the top that references TweenService yourself, thanks
if self.disabled then return end
if not self.equipped then return end
self.aiming = toaim
-- This is an easy to make approach
if toaim then
-- customize speed at will.
local tweeningInformation = TweenInfo.new(1, Enum.EasingStyle.Quart,Enum.EasingDirection.Out)
local properties = { Value = 1 }
tweenService:Create(self.lerpValues.aim,tweeningInformation,properties):Play()
else
local tweeningInformation = TweenInfo.new(0.5, Enum.EasingStyle.Quart,Enum.EasingDirection.Out)
local properties = { Value = 0 }
tweenService:Create(self.lerpValues.aim,tweeningInformation,properties):Play()
end
end
Be proud of yourself. Now, let’s do the 2 remaining things.
Reducing movement is just a factor of reducing what shove does:
Removing the mouse is also just one function (use not toaim
, i screwed up)
10. Pew Pewing
Get some quick muzzle flash ready inside the gun barrel. We need to make it look instant and punchy and stuff, and set Transparency to 1, then .Enabled = true. add a numberValue named transparency inside of the muzzle flash, and set it to the transparency it should have when firing.
Alright. let’s make it pew pew. You might also want a sound inside the receiver, since i heard guns make sounds.
set up a fire function the same as aim, just with a different name and bound to LMB.
We’re going to assume the weapon is automatic for now.
Add a table inside the settings named firing, and add a property called rpm, set it to 800 or something. whatever your gun spews bullets out at.
last thing is self.canFire = true inside the .new() function, so the gun is allowed to fire.
function handler:fire(tofire)
-- wall of requirements :(
if self.reloading then return end
if self.disabled then return end
if not self.equipped then return end
if self.firing and tofire then return end
if not self.canFire and tofire then return end
-- this makes the loop stop running when set to false
self.firing = tofire
if not tofire then return end
-- while lmb held down do
local function fire()
-- It's better to replicate the change to other clients and play it there with the same code as here instead of using SoundService.RespectFilteringEnabled = false
local sound = self.viewmodel.receiver.pewpew:Clone()
sound.Parent = self.viewmodel.receiver
sound:Play()
game:GetService("Debris"):AddItem(sound,5)
self.loadedAnimations.fire:Play()
-- Muzzle flash
for _, v in pairs(self.viewmodel.receiver.barrel:GetChildren()) do
if v:IsA("ParticleEmitter") then
v:Emit(v.Rate)
end
end
wait(60/self.settings.firing.rpm)
end
repeat
self.canFire = false
fire()
self.canFire = true
until not self.firing
end
Oh nice! sound that you can’t hear, but it shoots nicely enough. I heard that these things tend to push into your hip a bit, and also heard that it’s called recoil, so we will do that right now.
I will make the most basic recoil and make it reset. Do whatever you want here. IRL example:
We’re going to add another spring in .new(), call it fire, make it rotate the camera whenever updated
-- Sway! Yay!
local sway = self.springs.sway:update(deltaTime)
local walkCycle = self.springs.walkCycle:update(deltaTime)
local recoil = self.springs.fire:update(deltaTime)
-- RecoillllL!!!!!
self.camera.CFrame = self.camera.CFrame * CFrame.Angles(recoil.x,recoil.y,recoil.z)
and shove to it when shooting
self.springs.fire:shove(Vector3.new(0.03,0,0))
spawn(function()
wait(.15)
self.springs.fire:shove(Vector3.new(-0.03,0,0))
end)
The recoil looks terrible because it’s not randomized, but i’m not a very good game designer and i’m also lazy so have fun with it yourself.
11. More stuff (to the base)
These things will be covered with little detail.
Ammo is easily doable
12. Animation
Oh boy! you want to animate this thing in blender! You’re lucky that’s easy.
setup blender with Blender rig exporter/animation importer
use
workspace.Camera.CameraType = "Scriptable" workspace.Camera.CFrame = CFrame.new()
copy the make sure the viewmodel isn’t moving, copy it, drop it after the game has stopped,
make sure it’s still at 0,0,0 in the world,
Make all rootparts/hand markers/etc. transparency 0,
and export it. It should animate fine.
13. Other things
I can’t keep code in the game and the tutorial consistent anymore. Abysmal lag and not being able to log changes to the exact points made this imposible.
- Projectiles can be easily handled with fastcast
- ammo variables should be serversided
- you should add plenty of settings
- the aim lerp is easy to expand and can have a lot of other stuff added to it
- load animations along idle
- you should add a dot in the middle of the screen for testing the aiming offset, It tends to be offset incorrectly even if it looks correct
- rig things to the weaponRootPart, not the receiver. blender doesnt recognize things rigged to the receiver for some reason. this is why the test reload animation doesn’t have any mag movement
- KeyframeReached is usable for sounds
- you should have the server manage damage, ammo, equipped states etc.
14. The Place File
It has all the stuff compiled in. The game is available here:
The code differs from the tutorial to include the new task library and have better code formatting.
15. Rojo place file!
Rojo is an integration tool used to sync roblox and Visual Studio Code, and it’s awesome. Using the default code editor on roblox was already annoying when I was writing this tutorial. Now, it’s nigh impossible because of how much VSC has spoiled me. Here’s an already cleaned up place file for you people:
more info over at rojo.space
ROBLOX_fps_tutorial.zip (59.0 KB)
16. Part 2
Edit 1: idk
Edit 2: added credit for the spring module and velocity. Sorry if i pinged you guys accidentally.
Edit 3: part 2 now at your local thread
Edit 4: bug fixes for the code in the post. may be inconsistent with the main game
Edit 5: bug fixes
Edit 6: updates for formatting, new rojo file
Edit 7: formatting updates
Edit 8: broken comment block fixed
Edit 9: 0.3 in beginning
Thanks for reading