XSF Docs
Everything you need to get XSF into your game, set it up and build on top of it. New here? Read Install, then Your first game. The Scripting part is only for when you start writing your own code.
What XSF is #
XSF is a first person shooter framework for Roblox. You drop it into your place and you get the whole gun side of a shooter in one go: weapons that feel heavy in your hands, real bullets that drop and go through walls, recoil you can learn, positional sound, leaning, crouching, prone, sliding, vaulting, a downed and revive system, explosives, pickups, attachments, skins and the menus that go with all of it. It works on PC, console, phones and tablets, with R6 and R15 characters.
What it doesn't do is make your game for you. There are no maps, rounds, teams, scoreboards or shops built in. That part is yours. XSF gives you the shooting and the feel, plus a server and client API so your own scripts can hand out guns, react to kills, sell skins or run a battle royale on top of it.
Two ways to set it up
You can mix both, and most people do:
- The website dashboard. No code. Pick a game style, a camera, a loadout and tweak sliders. Every game running XSF with your key reads it when a server starts.
- Studio. Open GameConfig and each weapon's Config and change the values directly. Everything is commented, so you rarely need to look anything up.
If the same setting is changed in both places, the Studio value wins in that game. That rule is explained properly in Presets and Studio.
What makes it feel different
- Bullets are real projectiles. They have a speed in meters per second, they drop over distance, they lose power going through walls and they can skip off concrete or metal at a shallow angle. Nothing is a hitscan ray.
- The server checks every shot. Your screen fires the bullet so it feels instant, then the server simulates the same bullet again and decides what it hit. An exploiter can't just tell the server "I hit that guy in the head".
- Lag is accounted for. The server rewinds other players to where they were when you pulled the trigger, so a hit on your screen is a hit on the server, within reason.
- Everything moves on springs. Recoil, sway, bob and inertia are spring simulations, not canned animations. They feel the same at 60 fps and at 240 fps.
- Hands that actually hold the gun. The realistic arms pose every finger against the real shape of each gun when it's drawn, so the hand wraps the grip and the finger sits on the trigger. Custom guns get this too.
How these docs are laid out
The left column is the table of contents, and the search box at the top of it filters by section titles and text. Roughly:
- Start here gets XSF into your place and running, and explains how the pieces fit together.
- Set up your game is everything you can change without writing code: the dashboard, GameConfig, keys, camera, movement and so on.
- Weapons covers every gun setting, attachments, skins, damage, explosives and how to add your own guns.
- Scripting is the API for your own Scripts and LocalScripts, plus copy and paste recipes for common game modes.
- Help is testing, performance, error messages and fixes for the problems people actually run into.
New in 2.3 #
2.3 is about three things: a server that's harder to cheat on and cheaper to run, screens you can restyle without code, and much better phones and controllers. Nothing you set up changes except one default (everything is unlocked, see below). To update from 2.2, see Updating from 2.2 to 2.3.
Anti-cheat and a faster server
- Every request a player sends is rate limited and type checked, and anything that looks like cheating adds points to a suspicion score you can read from your scripts. XSF still never kicks or bans on its own. Movement is checked too: speed, teleports and flying, report only. See Anti-cheat and suspicion.
- Knives can't hit through walls, and every shot, swing and throw starts from where the player really is.
- Big servers: shots are sent to players in batches, poses take a fraction of the data, and hit checks are about three times cheaper. Before 2.3, servers with more than about 20 players could lose pose updates.
Screens you can edit
- Every XSF screen is a ScreenGui in
ReplicatedStorage.XSF.UI. Edit it in Properties like any GUI; XSF finds what it needs by name. A Theme module switches the whole look (Tactical, Arcade, Military, Minimal). The UI code is open source. See Editing XSF's screens. - A crosshair players can tune (style, colour, size, gap, thickness, opacity, outline), hit marker styles, a low ammo warning and real stat bars in the loadout screen. See Crosshair, hit markers and ammo warning.
- Menus open and close with a short motion, and an XSF settings button sits in Roblox's top bar.
Phones and controllers
- Players move, resize, turn and hide every phone button, saved per player. Optional auto fire, gyro aim, swipe the weapon bar, tap ammo to reload. See Phone button layout.
- Hold RB for a weapon wheel, real controller button pictures in menus, a focus ring, and aim assist that can gently pull toward a target. See Controllers and the weapon wheel.
More
- Ping markers on middle mouse, for your team, with thumbs up. See Ping markers.
- Graphics modes Full, Balanced and Performance, picked automatically from each player's frame rate. See Graphics modes.
- Look and scoped sensitivity. The aim sensitivity slider is now called Scoped sensitivity, and it actually works (before 2.3 it did nothing).
- Everything unlocked by default.
Customization.Ownershipnow ships as{ Attachments = false, Skins = false }, so every player can use every attachment and skin until you lock them. See Unlocks. - Performance reports. Servers send a small anonymous summary to the XSF site every 5 minutes so lag can be found and fixed.
PerformanceReports = falseturns it off. See Performance reports.
Fixes
- During a reload the old magazine could pop back into the gun while the hand went to the pouch empty, and during a magazine check (T) it jumped between the hand and the gun. The hand now holds it the whole way.
- The Scoped sensitivity slider did nothing.
- Grenades showed no picture in the backpack.
- The settings menu left a listener behind every time a tab with sliders was opened.
- The gun in your hands looked blurred on maps with depth of field, and the Bodycam grain was heavy. See Grain and blur.
- A hit marker switched on in GameConfig couldn't be turned off by players.
- Values like NaN sent to the server could break a knife swing or a shot.
New in 2.2 #
2.2 is a beta. It may be unstable, and a newer version will be released soon. If you'd rather wait, 2.1.0 is still on your account page in the version box under the download button. Please report anything odd in the Discord or on the support page.
2.2 fixes the launcher and scope reports from the community and adds two things developers asked for when bringing their own guns. Nothing you set up changes. To update from 2.1, see Updates; it's the same steps as 2.0 to 2.1.
Scope zoom
Variable scopes now zoom while you aim: mouse wheel up to zoom in, down to zoom out. The PM II goes from 5.8x to 8.8x and 14x, the M82's own scope from 2.3x to 3.5x and 5.4x, and the scope readout shows the magnification. Bind ZoomIn and ZoomOut for controllers. Your own scopes get steps with Zoom. See Scope zoom.
Skins on your own guns
Guns with their own textures can keep them under a skin (XSF_SkinKeep): the skin tints them and patterns lie on top. You can also choose exactly which parts a skin paints, and as what, with XSF_SkinRole and XSF_SkinOnlyTagged. See Skins on your gun.
Iron sights fold away
Fit a sight and the gun's iron sights get out of the way. On the HK416, MP5 and HK33 they disappear; on your own guns tag them with XSF_IronSight, and add XSF_Fold to have them flip down flat. Customization.FoldIronSights = false keeps them up.
Licensing
The license check is more patient and clearer. It retries with growing gaps when the site doesn't answer, treats an outage differently from a real refusal, and never stops a running server because the site is down. A new server that passed a check in the last 3 days still starts during an outage (live servers only, Studio always asks the site). If the site does refuse the license while a server is running (the license or game was revoked), XSF's weapons, explosives and damage stop on that server: new shots and actions are refused. Nothing is deleted, nobody is kicked, and your own scripts keep running. Every download is also traceable to the account it was made for.
Fixes
- A rocket could keep flying through walls on your screen after it had already hit something on the server. This happened with lag, on maps where the wall hadn't streamed in, or when it hit a moving player. Your copy now ends where the server's did.
- The RPG-7 and M320 now go off at point blank (
Arm = 0), so firing at your own feet hurts you like it should. Before, anything closer than 18 studs (RPG) or 14 studs (M320) was a dud. - The license server no longer refuses real keys from busy games. Roblox servers share a few outgoing addresses, so games with many servers could get "Too many license checks" and switch XSF off, sometimes in the middle of a match. Only wrong keys are limited now. This fix is on the website, so 2.1 games already have it.
New in 2.1 #
2.1 is mostly what developers building on 2.0 asked for: empty hands, menus that never shoot, crouch and slide poses other players can see on R6, and a lot more hooks for your own scripts. Nothing in your game changes unless you turn it on, apart from the fixes. To update from 2.0, see Updating from 2.0 to 2.1.
Empty hands
Press Y, or the number key of the gun you're holding, to put it away. The gun leaves your hands for everyone, and the server won't accept shots until a weapon is out again. In first person you see your hands: relaxed when standing, swinging as you walk and pumping when you run, with every arms style. See Holster.
Crouch and slide on R6
Other players now see an R6 character kneel when it crouches and lean back with its legs out when it slides, and the server lowers its hitboxes to match. R15 already bent at the knees. See Movement and stances.
Menus and clicks
A click on your UI never fires the gun, and the click that closes a menu doesn't either. Hold Left Alt to free the cursor for a moment. Your own menus get SetMenuOpen, which frees the mouse and stops XSF reading input, and a GuiButton with Modal = true works like it does in any Roblox game. Clicking empty space inside the settings menu no longer closes it. See Menus and the mouse.
Quick throw
Two new keybinds, QuickLethal and QuickTactical, throw your first grenade of that kind without switching to it, cook it while held, and go back to your gun. They're unbound by default; bind them in GameConfig or let players set them. See Keys and key names.
New GameConfig options
Movement.SprintReload: keep sprinting through a reload. See Movement.Throwables: the arc, lift, gravity, bounce, friction, spin and roll of grenades, for every throwable or one at a time. See Throwables.Audio.CutFade,Audio.Voices,Audio.StealFade,Audio.ParentGroupandAudio.StepSets(your own footsteps per floor material). Per gun:Sounds.FadeandSounds.Tail, and lists of takes forFireSuppressedandFireThird. See Audio.Holster: empty hands, the number key rule and the hands. See Holster.Customization.Required(slots players can't leave empty) andCustomization.Access = "Off". See Customization.SettingsSave: save player settings on the XSF site, only in your game, or not at all.UseWebsite = falseruns the game on GameConfig alone. See Preset and the website.- New keybinds:
Holster,Pickup,QuickLethal,QuickTactical,FreeCursor. - The GameConfig that ships now has
GiveLoadout,Vault,Pickups,Ragdoll,Damage.ModifyandDebug.LogRejectswritten in, so you don't have to look them up.
More for your scripts
- Server:
GetInventory,GetAmmo,GetAttachments,SetCustomizeCheck, afromandweaponoption onDamage, and theEquipped,HolsteredandReloadedevents. See Server scripts. - Client:
IsDown,GetMovementState,OnMovement,AddSpeedModifier,SetMenuOpen,SetHUDVisible, the kit functions (GetKit,SetKitand friends),SetGunshotHandler,OnReady,GetAllSettings, and signals for input, menus, equip, holster, reloads, new arms and other players' shots. See Client scripts.
Fixes
- A second grenade could be thrown after
LoadCharacterwhile one was cooking. SetLoadoutleft the player with nothing equipped on the server, so shots were refused until they switched weapons.- The settings menu and loadout screen are easier to use with a controller: the D-pad stays inside the menu and moves through tabs and rows in order.
- Clicking empty space in the settings menu closed it and fired the gun. Pressing P could close the menu and open it again straight away.
- Players with nothing in their hands couldn't sprint.
- When every copy of a sound is busy, the oldest one now fades out instead of cutting off with a click.
- If your game removes the character without killing it, XSF's hotbar and scope overlay no longer stay on screen.
New in 2.0 #

2.0 is the biggest update XSF has had. If you're coming from 1.3, read Updating to 2.0 first so you don't lose your changes, then skim the parts below that matter for your game.
59 weapons, up from 20
Rifles, battle rifles, machine guns, SMGs, bolt action and semi auto snipers, pump, semi and full auto shotguns, pistols, a revolver, seven knives, three grenades, C4, a claymore and two launchers. Every one has its own sound, recoil, damage profile and inspect. The full list with every number is in Every weapon's stats.
Explosives
The RPG-7 fires a rocket that leaves the tube slowly and then lights its motor. The M320 lobs a 40mm grenade on a real arc with a range ladder. C4 sticks to whatever it hits and goes off on right click. The claymore sits facing where you put it and fires a fan of steel at the first enemy in front. Blast damage checks for cover, so a wall really saves you. See Explosives.
Pickups
Players can drop the gun in their hands and pick up someone else's, attachments, skin and ammo included. Dead players drop their gun. Your scripts can spawn guns and ammo boxes anywhere, which is most of a battle royale loot system already. See Pickups.
Zeroing, night vision and thermal
Snipers, DMRs and the M320 have range steps you change with ] and [. Two new sights, Night Vision and Thermal, change what you see through the scope. See Zeroing and special optics.
Vault and mantle
Run at a low wall and jump to vault it. Jump at a ledge you can reach and you climb up. The left hand plants on the edge and the camera dips with you. It works on any part in your map with no setup. See Vault and mantle.
108 skins and an unlock system
Skins from plain Matte Black up to Galaxy and Damascus, in five rarities. By default players only get the attachments and skins your game gives them, so you can sell them, put them in crates or tie them to levels with a few lines of code. See Skins and Unlocks.
Proper reloads
Bolt actions work the bolt by hand. The Kar98k and Mosin reload with stripper clips. The revolver swings its cylinder out, punches the cases and uses a speedloader. Pump shotguns load shell by shell. The M320 breaks open, the RPG takes a warhead in the front. See Reloads and feeds.
Every attachment fits every gun
Before 2.0 an attachment only looked right on the guns it was made for. Now XSF measures each attachment and seats it from its real shape, so an ACOG sits on the rail of a SCAR as well as it sits on an M4.
A self test and a test map
Press F6 in a Studio playtest and XSF equips, fires, aims, reloads and inspects every weapon in your game, then prints a pass or fail line for each. The download also has an optional test map with a house, a range, a vault course, penetration walls and an explosives yard. See Testing your game.
Fixes
- The echo after every shot is gone.
- Indoor sound is muffled properly.
- Bare arms no longer glow with layered clothing.
- A flashbang error is fixed.
- Skinned guns no longer show a second copy of themselves (fixed in the current 2.0.0 download, so re-download if you grabbed it on day one).
Install in 3 minutes #
The download is a single .rbxmx file. Inside it everything is already sorted into folders named after where they go, so installing is really just matching names. Do it once and you'll never think about it again.
1. Download
Sign in on your account page and download XSF-2.3.0.rbxmx (older versions are in the version box under the download button). Your license is baked into the file, so there's no key to paste anywhere. Don't share the file: it's tied to your account, and every game that runs it shows up on your dashboard.
The very first time you download, the site offers a one minute setup of your game (four questions). It's optional. Skip it and XSF runs with its shipped defaults, and you can run the setup later from the dashboard. See Website dashboard.
2. Put it in Studio
Open your place in Studio and drag the file into the 3D view. You can also right click Workspace in the Explorer and pick Insert from File. A folder called XSF_Release appears in Workspace. Open it and you'll see this:
XSF_Release
├── ReplicatedStorage (a folder)
│ └── XSF
├── ServerScriptService (a folder)
│ └── XSF
├── StarterPlayerScripts (a folder)
│ └── XSF_Boot
├── ServerStorage (a folder, optional)
│ └── XSF_Tools
├── Workspace (a folder, optional)
│ └── XSF_TestMap
├── _README
└── _ABOUT_THIS_MODEL3. Match the names
Each folder inside XSF_Release is named after a service in your Explorer. Drag what's inside each folder to the service with the same name:
| Open this folder in XSF_Release | Drag what's inside it to | What it is |
|---|---|---|
| ReplicatedStorage | ReplicatedStorage | The XSF folder with GameConfig, Weapons, Assets, Client and Shared. Both players and the server read it. |
| ServerScriptService | ServerScriptService | A different XSF folder, with Main, Server and the server systems. It only ever runs on the server, so players can't see or steal it. |
| StarterPlayerScripts | StarterPlayer, then StarterPlayerScripts | XSF_Boot, the LocalScript that starts XSF for each player. StarterPlayerScripts lives inside StarterPlayer. Click the little arrow next to StarterPlayer to see it. |
| ServerStorage (optional) | ServerStorage | XSF_Tools, the ACS gun importer. Only bring it in if you want to convert ACS guns (see Importing ACS guns). It never runs in a live game. |
| Workspace (optional) | Workspace | XSF_TestMap, a small map for trying everything out: a house to clear, a range with targets out to 150 studs, a vault course, penetration walls and an explosives yard. Skip it if you already have a map. See Testing your game. |
Drag the XSF folder itself, not the folder around it. If you end up with ReplicatedStorage.ReplicatedStorage.XSF, you dragged one level too high. Move the inner XSF up and delete the empty folder.
4. Clean up and turn on HTTP
- Delete the now empty XSF_Release folder from Workspace. Leaving it there doesn't break anything, but it's clutter.
- Open Home > Game Settings > Security and turn on Allow HTTP Requests, then save. XSF uses HTTP to check its license, save player settings and read your dashboard. Without it XSF still runs, but settings fall back to DataStores and the dashboard is ignored.
- If you want player settings to save while testing in Studio without HTTP, also turn on Enable Studio Access to API Services on the same page.
5. Check it
Your Explorer should now have exactly these three things:
ReplicatedStorage
└── XSF (GameConfig, Weapons, Assets, Client, Shared ...)
ServerScriptService
└── XSF (Main, Server, Weapons, Damage ...)
StarterPlayer
└── StarterPlayerScripts
└── XSF_BootPress Play. You should spawn holding the first weapon of the default loadout (the HK416), with the rest on the hotbar along the bottom of the screen. Press 1 to 5 to switch, B to see the whole backpack.
[XSF]. XSF tells you exactly what's wrong and how to fix it. Nine times out of ten it's a folder in the wrong place, and the message names which one. See Error messages and Troubleshooting, or ask in the XSF Discord.Things to remove first
- Other gun systems. ACS, FE Gun Kit, Carbon and so on all want the camera and the mouse. Two frameworks fighting over the camera is a mess. Remove the old one before testing XSF.
- Custom camera scripts that force first person or move the camera every frame. XSF runs its own camera. If you need the camera for a cutscene, use
SetCameraEnabled(false)from the client API. - Scripts that change WalkSpeed every frame. XSF sets WalkSpeed itself for sprinting, aiming, crouching and reloading. Change speeds in GameConfig instead.
What goes where #
Once installed, this is what you're looking at and which parts you're meant to touch.
| Where | What it is | Edit it? |
|---|---|---|
ReplicatedStorage.XSF.GameConfig | Your settings. Every option XSF has, with comments. | Yes, this is the main one. |
ReplicatedStorage.XSF.Weapons | One folder per weapon. Each has a Config (stats) and Anims (animations). | Yes, the Configs. Copy a folder to make a new gun. |
ReplicatedStorage.XSF.Assets.GunModels | The 3D gun models, one per weapon. | Add your own models here. |
ReplicatedStorage.XSF.Assets.AttModels | Attachment models: sights, muzzle devices, grips, lights. | Add your own attachments here. |
ReplicatedStorage.XSF.Assets.GrenadeModels | Grenade, C4 and claymore models. | Rarely. |
ReplicatedStorage.XSF.Assets.Projectiles | The rocket and 40mm round models. | Rarely. |
ReplicatedStorage.XSF.Assets.ArmModel | The first person arms. | No. |
ReplicatedStorage.XSF.Assets.Workbench | The workbench model for Customization.Access = "Workbench". | Copy it into your map. |
ReplicatedStorage.XSF.Client | Everything that runs on each player. API is the module your LocalScripts use. | No, except reading API. |
ReplicatedStorage.XSF.Shared | Code both sides use: ballistics, attachments, skins, the stock copy of every config. | No. |
ReplicatedStorage.XSF.Version | The version string, "2.3.0". | No. |
ServerScriptService.XSF | Server code. Server is the module your own Scripts use. | No, except reading Server. |
StarterPlayerScripts.XSF_Boot | Starts XSF for each player. | No. |
ServerStorage.XSF_Tools | The ACS importer. Optional. | No, just call it. |
Most of the code is protected and can't be read or edited. GameConfig, the weapon Configs and the two API modules are left readable on purpose, so you can set things up and script around XSF without ever needing the inside.
Folders XSF makes at runtime
While a game runs you'll see a few folders appear in Workspace. They're XSF's, so leave them alone and don't parent your own things into them:
| Folder | What's in it |
|---|---|
XSF_Thrown | Grenades, C4, claymores and rockets in flight or on the ground. |
XSF_Pickups | Dropped guns and ammo boxes. |
XSF_ThirdPerson | The guns other players hold, as everyone sees them. |
XSF_Local, XSF_Audio | Client side effects and sounds. These only exist on each player's own screen. |
Your first game #
Here's a full walk through from an empty baseplate to a small team deathmatch you can play with friends. It takes about fifteen minutes and touches most of what you'll use later. If you already know what you want, skip ahead, nothing here is required.
Step 1: install and press Play
Do the install, drop in XSF_TestMap if you don't have a map yet, and press Play. Walk around. Try the basics so you know what the defaults feel like:
- Left click fires, right click aims, R reloads, V switches fire mode.
- Shift sprints, C crouches, C while sprinting slides, Z goes prone, Q and E lean.
- Run at a low wall and press Space to vault it.
- N opens the loadout screen, where you fit attachments. P opens settings.
- Press 1 to 5 to switch weapons, and B to open the backpack with the rest.
It ships with the Bodycam camera and free aim on, which is a strong look. If you want a normal shooter feel, that's two lines in step 2.
Step 2: pick the feel
Open ReplicatedStorage.XSF.GameConfig. For a regular arcade style shooter, change these:
Camera = {
Style = "Standard", -- was "Bodycam"
-- leave the rest of Camera as it is
},
FreeAim = {
Enabled = false, -- gun locked to the centre of the screen
},
Damage = {
FriendlyFire = false,
Hitmarkers = true, -- a small marker when your shots land
},Don't retype whole tables. Find the line and change the value. Anything you don't touch keeps working as before. Press Play again to try it.
Step 3: choose the guns
Still in GameConfig, set Loadout to what each player spawns with. The first five go on the number keys.
Loadout = { "M4A1", "MP5", "G17", "KaBar", "M67" },The names are folder names from XSF.Weapons, spelled exactly the same. The full list is in Choosing the weapons.
Step 4: add teams
XSF uses normal Roblox Teams. Insert two Teams under the Teams service (Model tab, then Service, if you don't see it), name them Red and Blue, give them colours and turn AutoAssignable on for both. Add a SpawnLocation for each team with its TeamColor set to match and Neutral off.
That's all XSF needs. With Damage.FriendlyFire = false, players on the same team can't hurt each other, and with downed on, only teammates can revive.
Step 5: count kills
Add a Script to ServerScriptService. This gives each player a Kills and Deaths counter on the leaderboard and fills them from XSF's events:
local Players = game:GetService("Players")
local XSF = require(game.ServerScriptService.XSF.Server)
Players.PlayerAdded:Connect(function(player)
local stats = Instance.new("Folder")
stats.Name = "leaderstats"
local kills = Instance.new("IntValue")
kills.Name = "Kills"
kills.Parent = stats
local deaths = Instance.new("IntValue")
deaths.Name = "Deaths"
deaths.Parent = stats
stats.Parent = player
end)
XSF.Killed:Connect(function(victim, attacker, weapon)
local victimPlayer = Players:GetPlayerFromCharacter(victim.Parent)
if victimPlayer then
victimPlayer.leaderstats.Deaths.Value += 1
end
if attacker and attacker ~= victimPlayer then
attacker.leaderstats.Kills.Value += 1
end
end)Killed fires for every death caused by XSF damage, including bleeding out after being downed, with the player who did it. You don't have to track who shot whom yourself.
Step 6: make it fair
A few choices that matter a lot for a deathmatch:
- Downed or not? Downed and revive is great for squads and terrible for quick respawn games. For TDM turn it off:
Downed = { Enabled = false }(keep the other lines). - Armour. Without armour everyone takes full damage. If you want body armour, set
XSF_Armoron characters, see How damage works. - Unlocks. By default every player can use every attachment and skin. If you plan to sell or unlock them later, set
Customization.Ownership = { Attachments = true, Skins = true }and players only get what your game gives them. See Unlocks.
Step 7: test with real players
In Studio, open the Test tab, set the player count to 2 or 3 and press Start. You get a server window and one window per player, which is the only proper way to test hits, revives and team rules on your own. When it plays well, publish it, turn on HTTP in the live game's settings if you haven't, and invite people.
Where to go next
- Fine tune everything from the dashboard without republishing.
- Change gun stats in Weapon settings.
- Build a shop, a leveling system or a main menu with the Recipes.
How XSF works #
You don't need any of this to use XSF. It helps when something behaves in a way you didn't expect, or when you start scripting.
Two halves
XSF has a client half and a server half, like every Roblox game.
- The client (in
ReplicatedStorage.XSF.Client, started byXSF_Boot) runs on each player. It handles input, the camera, the first person arms and gun, recoil, sounds, effects, the HUD and menus. It fires bullets on your screen the moment you click, so shooting never feels delayed. - The server (in
ServerScriptService.XSF) is the referee. It owns everyone's inventory and ammo, simulates every bullet again, decides what was hit, applies damage, runs downed and revive, explosions and pickups, and saves settings and unlocks.
The client asks, the server decides. If the client says "I fired", the server checks the player is alive, holding that gun, has ammo, isn't firing faster than the gun can, and that the shot started near where the player actually is. Shots that don't add up are thrown away and fire the Rejected event (see Server scripts).
What happens when you pull the trigger
- Your client spawns the bullet at the muzzle, plays the shot, kicks the camera and starts simulating the round with gravity and speed.
- It tells the server what it fired and where it was pointing.
- The server validates the shot, then rewinds every other player to where they were at the moment you fired (this is lag compensation, it keeps up to about a second of history).
- The server simulates the same bullet through that rewound world, through walls if it has the power, and finds what it hit.
- Damage is worked out from the body part, the gun, the ammo, distance, walls and armour, then applied. See How damage works.
- The shooter gets a hit confirm, the victim gets a hurt effect, and everyone else hears the shot with distance and walls taken into account.
Configs are merged, not replaced
XSF builds its final settings in layers, from the bottom up:
- Built in defaults (in
XSF.Shared.DefaultsandXSF.Shared.WeaponDefaults). - The website preset for this game, if there is one.
- Anything you changed in Studio, in GameConfig and the weapon Configs.
So a weapon Config only needs the lines that differ from the defaults, and GameConfig can lose any line without breaking. Delete Movement.ProneSpeed and XSF quietly uses 3.2. The full rules for how the website and Studio combine are in Presets and Studio.
Players don't have to be the only targets
Anything in Workspace with a Humanoid can be shot, takes damage by body part, ragdolls and fires the same events. XSF finds new ones automatically, so NPCs and dummies just work. See NPCs with guns.
Settings follow the player
A player's settings (sensitivity, FOV, keybinds, attachment choices and presets) are saved on the XSF site, so they carry across every server and every game that uses XSF. If HTTP is off they save to the game's own DataStore instead. A game can change where they are saved, or turn saving off, with SettingsSave in GameConfig (see Preset and the website).
Units
| Thing | Unit |
|---|---|
| Distances, ranges, radii | Studs |
Bullet speed (MuzzleVelocity) | Meters per second. XSF converts with Ballistics.StudsPerMeter, which is 1 / 0.28, so one stud is 28 cm. |
| Angles (recoil, FOV, free aim, lean) | Degrees |
| Times (aim time, fuse, cooldowns) | Seconds |
Weight (Handling.Mass) | Kilograms |
| Fire rate | Rounds per minute |
| Movement speeds | Studs per second, the same as Humanoid.WalkSpeed |
Website dashboard #
Open xsfshooter.com/dashboard (or the dashboard button on your account page) to set XSF up without touching code. Everything you do there is saved on your account. Every game that runs XSF with your key reads it the next time one of its servers starts.
The dashboard is the fastest way to try things. Change walk speed, press Play in Studio, feel it, change it again. No republishing, no editing Lua. When something feels right you can leave it on the website forever or copy it into GameConfig, whichever you prefer.
The quick setup
The first time you download XSF, the site asks four questions before the download starts. It takes about a minute and Skip setup is always there. You can run it again from the Setup tab whenever you like.
- Game style. Picks a starting point for movement, health, aiming and combat. Each card lists what it changes, so nothing happens behind your back.
- Camera and body. Standard or Bodycam camera, and realistic gloved arms or the classic blocky ACS arms. Picking a style also picks the camera it's built around, and you can change it here.
- Loadout. Tap weapons to add or remove them. The numbers show which ones go on the hotbar keys, the rest wait in the backpack. Select all and Clear do what they say.
- Platforms. Computer, console, phone and tablet, as many as you like. Console keeps aim assist and vibration on. Phone and tablet turn on low effects by default and use a 4 slot hotbar that fits small screens.
The review screen lists every setting your answers change, in plain words. Save and download saves it as your Default preset and starts the download. Save and fine tune saves it and opens the Settings tab. Running the setup again replaces Default with the new answers, so if you've hand tuned Default, copy it to a new preset first.
Here's exactly what each game style sets. Anything not listed stays at XSF's default.
| Style | What it is for | What it sets |
|---|---|---|
| Tactical | Slow, deliberate fights. Downed and revive, free aim, real recoil. | Movement.WalkSpeed = 12, Movement.SprintSpeed = 20, Downed.Enabled = true, FreeAim.Enabled = true, Damage.Hitmarkers = false, Malfunctions.Enabled = false |
| Bodycam | Looks like real body camera footage. Wide lens, grain, heavy free aim. | Camera.Style = "Bodycam", Camera.BodycamFov = 102, FreeAim.Enabled = true, FreeAim.Yaw = 11, Damage.Hitmarkers = false, Motion.Inertia = 1.15 |
| Milsim | As real as it gets. Gun jams, long bleed outs, friendly fire, no hitmarkers. | Movement.WalkSpeed = 11, Movement.SprintSpeed = 18, Malfunctions.Enabled = true, Damage.FriendlyFire = true, Damage.Hitmarkers = false, Downed.Enabled = true, Downed.BleedOutTime = 90, Controls.AutoReload = false, Customization.Access = "Workbench" |
| Arcade | Fast and fun. Quick movement, hitmarkers, no downed state, classic aiming. | Camera.Style = "Standard", Movement.WalkSpeed = 16, Movement.SprintSpeed = 26, Movement.Stamina = 15, Downed.Enabled = false, FreeAim.Enabled = false, Damage.Hitmarkers = true, Motion.Inertia = 0.6 |
| Battle royale | Big maps and long range. Downed and revive for squads, more stamina. | Movement.WalkSpeed = 14, Movement.SprintSpeed = 24, Movement.Stamina = 12, Downed.Enabled = true, Downed.BleedOutTime = 60, Damage.Hitmarkers = true, Customization.Access = "Anywhere" |
| Keep XSF defaults | Leave everything as XSF ships it. | Nothing |
The styles are starting points, not modes. Pick the closest one and change what you don't like. Battle royale with hitmarkers off is fine. Tactical with Standard camera is fine.
Settings
Every common GameConfig option, grouped and explained in plain words. Changes save by themselves (watch the save note in the corner), and anything you leave alone keeps XSF's default. Each setting has a reset button. The search box finds a setting by name or description. Settings that only matter for another setting (Bodycam field of view only matters with the Bodycam camera) only show up when they apply.
A small STUDIO tag on a setting means one of your games changed it in Studio, so that game keeps its own value for it. See Presets and Studio.
This is every setting on the website, the GameConfig option it writes, the default and the range the slider allows:
| Group | Setting on the website | GameConfig option | XSF default | Range |
|---|---|---|---|---|
| Camera | Camera style | Camera.Style | Bodycam | Standard, Bodycam |
| Camera | Bodycam field of view | Camera.BodycamFov | 100 | 80 to 120 ° |
| Camera | Field of view | Camera.FieldOfView | 80 | 60 to 110 ° |
| Camera | Camera bob | Camera.Bob | 1 | 0 to 2 x |
| Camera | Strafe tilt | Camera.Tilt | 1 | 0 to 2 x |
| Camera | Screen kick when firing | Camera.RecoilKick | 1 | 0 to 2 x |
| Camera | First person arms | Providers.Arms | Realistic gloves | Realistic gloves, Classic blocky |
| Movement | Walk speed | Movement.WalkSpeed | 12 | 6 to 30 studs/s |
| Movement | Sprint speed | Movement.SprintSpeed | 21 | 10 to 45 studs/s |
| Movement | Speed while aiming | Movement.AimSpeed | 7.5 | 3 to 20 studs/s |
| Movement | Sprint stamina | Movement.Stamina | 7 | 1 to 60 s |
| Movement | Stamina refill time | Movement.StaminaRegen | 4.5 | 0.5 to 30 s |
| Movement | Sliding | Movement.Slide.Enabled | On | On or Off |
| Movement | Slide speed | Movement.Slide.Speed | 30 | 15 to 60 studs/s |
| Movement | Shoot while sliding | Movement.Slide.CanShoot | On | On or Off |
| Aiming | Free aim | FreeAim.Enabled | On | On or Off |
| Aiming | Free aim width | FreeAim.Yaw | 9 | 1 to 25 ° |
| Aiming | Free aim height | FreeAim.Pitch | 5 | 1 to 20 ° |
| Aiming | Gun weight when turning | Motion.Inertia | 1 | 0 to 2 x |
| Aiming | Gun bob | Motion.Bob | 1 | 0 to 2 x |
| Aiming | Breathing sway | Motion.Breath | 1 | 0 to 2 x |
| Health and reviving | Downed state | Downed.Enabled | On | On or Off |
| Health and reviving | Bleed out time | Downed.BleedOutTime | 45 | 5 to 180 s |
| Health and reviving | Revive time | Downed.ReviveTime | 5 | 1 to 20 s |
| Health and reviving | Health after revive | Downed.ReviveHealth | 35 | 1 to 100 hp |
| Health and reviving | Only teammates can revive | Downed.TeamOnly | On | On or Off |
| Health and reviving | Downed players can shoot | Downed.AllowFiring | Off | On or Off |
| Health and reviving | Drag downed teammates | Downed.Drag | On | On or Off |
| Combat | Friendly fire | Damage.FriendlyFire | Off | On or Off |
| Combat | Hitmarkers | Damage.Hitmarkers | Off | On or Off |
| Combat | Gun jams | Malfunctions.Enabled | Off | On or Off |
| Combat | Auto reload | Controls.AutoReload | On | On or Off |
| Combat | Attachments menu | Customization.Enabled | On | On or Off |
| Combat | Where attachments can be changed | Customization.Access | Anywhere | Anywhere, Only at a workbench |
| Stances | Crouching | Stances.Crouch | On | On or Off |
| Stances | Going prone | Stances.Prone | On | On or Off |
| Stances | Leaning | Stances.Lean | On | On or Off |
| Hotbar and screens | Hotbar slots | Backpack.HotbarSize | 5 | 1 to 9 slots |
| Hotbar and screens | Hotbar on screen | Backpack.Hotbar | Always | Always, Only when switching |
| Hotbar and screens | Ammo and health readout | Providers.HUD | XSF minimal | XSF minimal, None |
| Hotbar and screens | Low effects by default | Performance.LowEffects | Off | On or Off |
| Sound | XSF footsteps | Audio.Footsteps | On | On or Off |
| Sound | Background sound | Audio.Ambience | On | On or Off |
| Controller and phone | Aim assist | AimAssist.Enabled | On | On or Off |
| Controller and phone | Aim assist strength | AimAssist.Slowdown | 0.55 | 0.2 to 1 x |
| Controller and phone | Controller vibration | Rumble.Enabled | On | On or Off |
Options that aren't on the website (keybinds, ballistics, slide timings, aim assist angle and a few more) are Studio only. Set them in GameConfig.
Weapons
The top card is the spawn loadout: tap a weapon to add or remove it, and use the arrows to change the order. The first ones in the list go on the hotbar keys. Under it, tap any weapon to change its stats. A stat you change gets a mark, and the reset button puts it back to how XSF ships the gun. Knives only have damage, and grenades have no stats here.
| Stat on the website | Weapon Config field | Range | Shown for |
|---|---|---|---|
| Head damage | Damage.Head | 1 to 1000 | Guns and knives |
| Body damage | Damage.Torso | 1 to 1000 | Guns and knives |
| Arm and leg damage | Damage.Limb | 1 to 1000 | Guns and knives |
| Fire rate | Fire.RPM | 20 to 1500 rounds/min | Guns only |
| Magazine size | Ammo.Magazine | 1 to 200 rounds | Guns only |
| Spare ammo | Ammo.Reserve | 0 to 1000 rounds | Guns only |
| Bullet speed | Fire.MuzzleVelocity | 100 to 2000 m/s | Guns only |
| Time to aim | Handling.AimTime | 0.05 to 1.5 s | Guns only |
| Recoil | Recoil (every kick and climb value, multiplied) | 0 to 3 x | Guns only |
Recoil is one multiplier for the whole recoil of the gun: 1 is normal, 0.5 halves every kick and climb, 2 doubles them. It only applies when that gun's Recoil table hasn't been changed in Studio. If you want to shape recoil (more climb, less sideways, a pattern), do it in the gun's Config, see Recoil and sway.
Presets
A preset is one full setup: settings, weapon stats and loadout together. You can have up to 20. Names can use letters, numbers, spaces, - and _, up to 32 characters. New preset starts empty or as a copy of the one you're editing, and every preset can be copied, renamed or deleted. The Editing preset picker at the top of Settings and Weapons decides which preset your changes go into. Check it before you start changing things. Editing the wrong preset is the most common reason "my change didn't do anything".
Good uses for presets:
- One preset per game, when you run several games on one account.
- One preset per mode in the same game, picked with
Preset = "Name"in a separate place (a TDM place and a hardcore place in the same universe). - A test preset you try in Studio before switching your live game over to it.
Games
Every game that has started XSF with your key shows up here, with when it was last seen and which XSF version it runs. For each game you can:
- Pick which preset it uses. Games you never pick for use Default.
- See how many settings it changes in Studio. Those keep the Studio value in that game.
- Revoke it, which stops XSF working in that game only, the next time a server starts. Restore undoes it. Use this if someone got hold of your file and is running it in a game that isn't yours.
If a game's GameConfig names a preset with Preset = "Name", the picker is locked and shows that name. If no preset has that name, the game shows a warning and uses Default.
When changes reach your game
- A server reads the dashboard once, when it starts, before any player spawns. A round never changes its rules halfway through.
- Servers that are already running keep what they started with. New servers get the new settings. To push a change to everyone, use Restart servers for updates on the game's page in the Roblox Creator Hub, or wait for old servers to empty out.
- In Studio, stop and press Play again.
- If the XSF site can't be reached when a server starts, the game runs on its Studio settings and keeps working. Players won't notice anything except that dashboard only changes are missing on that server.
Presets and Studio #
This is the one rule that matters when you use both the website and Studio. Read it once and you'll never be confused about where a value came from.
Which preset a game uses
- If GameConfig has
Preset = "Name", that preset. - Otherwise, the preset picked for the game on the Games tab.
- Otherwise, the preset called Default.
- If there's no Default either (you skipped the setup and never made one), the game simply uses GameConfig and nothing comes from the website.
return {
-- this place always uses the "Zombies" preset from the dashboard
Preset = "Zombies",
-- ...the rest of GameConfig
}If no preset has that name, Output says GameConfig asks for the website preset "Zombies", but your account has no preset with that name and the game uses Default. Make a preset with that exact name, or fix the spelling. Names are case sensitive.
Studio always wins, but only for what you changed
XSF carries a copy of its own GameConfig and every weapon Config exactly as this version ships them (in XSF.Shared.Stock, which you shouldn't edit). When a server starts, XSF compares your files with that copy, one value at a time:
- A value you changed in Studio is yours. It beats the dashboard, in that game only.
- A value you never touched follows the dashboard.
Example: your GameConfig sets Movement.WalkSpeed = 16, and your preset on the website sets walk speed 20 and sprint speed 30. That game walks at 16 (your Studio value) and sprints at 30 (from the website, because you never changed sprint speed in Studio). The dashboard shows a STUDIO tag on Walk speed, and the Games tab counts it.
Movement = {
WalkSpeed = 16, -- changed in Studio: this game always walks at 16
SprintSpeed = 21, -- still the shipped value: the website decides
},Handing a value back to the website
Set it back to the value XSF ships with (the defaults are listed in Every GameConfig option). Deleting the line works too, because a missing line falls back to the default, which matches the shipped value for almost everything.
Weapon Configs work the same way, one value at a time. Change Damage.Head on the AK47 in Studio and the website can still set its fire rate.
A few things are a single value even though they look like lists:
Loadout. Change it in GameConfig and the website loadout is ignored in that game.- A weapon's
Recoiltable. Change any recoil value in Studio and the website's recoil multiplier no longer applies to that gun.
Scripts see the final values
Once applied, website values are part of the config like anything else. XSF.Config on the server and every client module read the final numbers, so your own scripts never need to know where a value came from.
A workflow that works
- Leave GameConfig as it ships.
- Do all your tuning on the website, testing with Play in Studio.
- Only edit GameConfig for things the website doesn't have (keybinds, ballistics, the hidden options) or things you want locked so nobody on the website can change them.
GameConfig #
Open ReplicatedStorage.XSF.GameConfig. Every option XSF has is already listed in it with its default value and a comment saying what it does, so you can change things without reading anything else.
- Change a value and press Play to try it. There's no restart or rebuild step.
- Delete a line and XSF uses its built in default for it. That's the same as the shipped value except for three:
Loadoutfalls back to just{ "HK416" },Camera.Styleto"Standard"andAudio.Ambiencetofalse. - Options marked (player) are only the starting value. Players can change those for themselves in the settings menu, and their own value wins for them.
- Anything you change here beats the website dashboard for that setting.
- GameConfig is read on both the server and every client, so never put secrets in it (API keys, webhook URLs). Players can read ReplicatedStorage.
Editing it without breaking it
GameConfig is a Lua table. The rules that trip people up:
- Every line inside a table ends with a comma. A comma after the last line is fine too. A missing comma is the number one typo.
- Text goes in double quotes:
"Bodycam", notBodycam. true,falseandnilare lowercase and have no quotes.- Brackets have to match. Every
{needs a}. - Names are case sensitive.
walkspeedis notWalkSpeed, and XSF won't see it.
If you do break it, XSF prints There is a typo in GameConfig on line N in Output and doesn't start. Look at that line and the one above it. Studio's script editor also underlines the problem in red.
-- right
Movement = {
WalkSpeed = 14,
SprintSpeed = 24,
},
-- wrong: missing comma after 14, and "Fast" isn't a number
Movement = {
WalkSpeed = 14
SprintSpeed = "Fast",
},Changing values while the game runs
You can't. GameConfig is read when XSF starts. XSF.Config is there for reading, and changing it at runtime gives you a mix of old and new values on different machines. For things that change during a round (a player's guns, damage, attachments), use the server API instead.
Choosing the weapons #
Loadout is the list of weapons every player spawns with, in slot order. Use the folder names from XSF.Weapons, spelled exactly the same, capitals included.
Loadout = { "HK416", "USP", "M67" },It ships with thirteen: HK416, AK47, SCAR, AWM, M249, Deagle, M870, Knife, Karambit, M67, Flashbang, RPG7, C4. That's a showcase, not a real loadout. For a real game you'll usually want two to five.
All 59 weapons
The first column is the name you write in Loadout. Every number for every gun is in Every weapon's stats.
| Name | Weapon | Class | Notes |
|---|---|---|---|
HK416 | HK416 | Rifle | 5.56. Controllable, gets lively on full auto. |
M4A1 | M4A1 | Rifle | 5.56. Quick, flat and easy to keep on target. |
AK47 | AK-47 | Rifle | 7.62x39. Hits hard and climbs hard. |
AK12 | AK-12 | Rifle | 5.45. A modern AK that stays flatter. |
AKS74U | AKS-74U | Rifle | Short barrel AK. Fast handling, wild up close. |
HK33 | HK33 | Rifle | 5.56 roller delayed, a harder shove per shot. |
SCAR | SCAR-H | Rifle | 7.62 NATO. Big hits, slower rate. |
G36C | G36C | Rifle | Compact 5.56, very steady. |
AUG | Steyr AUG | Rifle | Bullpup 5.56. |
FAMAS | FAMAS | Rifle | Bullpup with a very high rate of fire. |
TAR21 | TAR-21 | Rifle | Bullpup 5.56, short and quick to aim. |
GALIL | Galil | Rifle | Israeli AK pattern rifle. |
FAL | FN FAL | Rifle | 7.62 NATO battle rifle. Heavy punch. |
M14 | M14 | Rifle | 7.62 DMR. Pairs well with zeroing. |
M249 | M249 | Rifle | Belt fed 5.56 machine gun. Big reserve, slow reload. |
PKM | PKM | Rifle | Belt fed 7.62. Heavy and brutal. |
MP5 | MP5 | SMG | 9mm. Light and quick, low kick. |
MP7 | MP7 | SMG | 4.6mm. Tiny and very fast. |
UMP45 | UMP-45 | SMG | .45 ACP. Slower, harder hitting SMG. |
Vector | Vector | SMG | .45 ACP. Very fast fire rate, stays flat. |
P90 | P90 | SMG | 5.7mm with a 50 round magazine. |
BIZON | PP-19 Bizon | SMG | Helical 64 round magazine. |
UZI | Uzi | SMG | 9mm classic. |
MAC10 | MAC-10 | SMG | Spits bullets, hard to control. |
THOMPSON | Thompson | SMG | .45 drum fed classic. |
MP40 | MP40 | SMG | 9mm, slow and steady. |
R700 | Remington 700 | Sniper | Bolt action. Heavy, slow to settle, hits very hard. |
AWM | AWM | Sniper | .338 Lapua. One chest hit drops most targets. |
KAR98 | Kar98k | Sniper | Bolt action, reloads with stripper clips. |
MOSIN | Mosin-Nagant | Sniper | Bolt action, reloads with stripper clips. |
SVD | SVD Dragunov | Sniper | Semi auto 7.62x54R. |
M82 | Barrett M82 | Sniper | .50 BMG anti material rifle. |
M870 | Remington 870 | Shotgun | 12 gauge pump. Eight pellets per shell, damage is per pellet. |
M1014 | M1014 | Shotgun | Semi auto 12 gauge. |
SPAS12 | SPAS-12 | Shotgun | Pump 12 gauge. |
DBS | Double barrel | Shotgun | Two shells, fast and loud. |
AA12 | AA-12 | Shotgun | Full auto 12 gauge. |
G17 | Glock 17 | Pistol | 9x19. Flat shooting and fast to recover. |
USP | USP | Pistol | .45. Snappy flip, quick recovery. |
M1911 | M1911 | Pistol | .45 classic. |
M92 | Beretta M9 | Pistol | 9mm, smooth. |
CZ75 | CZ 75 | Pistol | 9mm, fast follow ups. |
FIVESEVEN | Five-seveN | Pistol | 5.7mm with 20 rounds. |
Deagle | Desert Eagle | Pistol | .50 AE hand cannon. Huge hits, huge kick. |
R686 | S&W 686 | Pistol | .357 revolver with a swing out cylinder and speedloader. |
KaBar | Ka-Bar | Melee | The classic fighting knife. |
M9 | M9 Bayonet | Melee | Longer reach, a little slower. |
Karambit | Karambit | Melee | Short reach, very fast hooking cuts. |
Balisong | Butterfly knife | Melee | Light and quick. |
BOWIE | Bowie knife | Melee | Big blade, heavy cuts. |
KUKRI | Kukri | Melee | Forward weighted chopper. |
Knife | Combat knife | Melee | The original XSF knife. |
M67 | M67 frag | Grenade | Fragmentation grenade. |
M18 | M18 smoke | Grenade | Smoke grenade. |
Flashbang | M84 flashbang | Grenade | Stun grenade. |
C4 | C4 | Explosive | Thrown, sticks to what it hits, set off with right click. |
Claymore | Claymore | Explosive | Placed mine that fires a fan at the first enemy in front. |
RPG7 | RPG-7 | Launcher | Rocket with back blast. Reloads from the muzzle. |
M320 | M320 | Launcher | 40mm grenade launcher on a real arc. |
How slots work
- The first
Backpack.HotbarSizeweapons (5 by default) go on the number keys. The rest wait in the backpack (B). See Hotbar and backpack. - The player spawns holding the first one.
- Each grenade type takes one slot. The M67 ships with one in hand and one in reserve, so that slot holds two frags.
- A name that doesn't match a folder is skipped, and Output says
There is no weapon called "X" in XSF.Weapons. The rest of the loadout still works.
Different weapons for different players
Everyone gets the same Loadout. For classes, teams, a shop or a main menu, give weapons from a script instead with XSF.SetLoadout, which replaces everything the player carries:
local XSF = require(game.ServerScriptService.XSF.Server)
game.Players.PlayerAdded:Connect(function(player)
player.CharacterAdded:Connect(function()
task.wait() -- let XSF set up the character first
if player.Team and player.Team.Name == "Snipers" then
XSF.SetLoadout(player, { "R700", "USP", "M18" })
else
XSF.SetLoadout(player, { "HK416", "USP", "M67" })
end
end)
end)If you give weapons from a script, you probably don't want XSF to also hand out Loadout on spawn. Add GiveLoadout = false to GameConfig. It isn't in the file by default, see Hidden options. The full API is in Server scripts, and there's a class picker in Recipes.
Removing weapons from your game
You don't have to. Weapons that aren't in anyone's loadout cost nothing at runtime. If you want a smaller place file anyway, delete the weapon's folder from XSF.Weapons and its model from XSF.Assets.GunModels. Leave the others alone.
Every GameConfig option #
This is the full list, taken straight from the GameConfig that ships with 2.0. The Default column is the value it ships with. Paths use dots for nesting, so Movement.Slide.Speed means the Speed line inside Slide inside Movement. Anything marked (player) is only the starting value, because players can change it for themselves in the settings menu.
Options that work but aren't written in the file are in More options. Keybinds have their own page, Keys and key names.
Providers
Which version of a system runs. The XSF... names are the built in ones. See Swapping parts out.
| Option | Default | What it does |
|---|---|---|
Providers.Inventory | "XSF" | "XSF" is XSF's own hotbar and backpack. "RobloxTools" turns each weapon into a normal Roblox Tool in the default backpack bar, for games built around Tools. |
Providers.Arms | "XSFRealistic" | "XSFRealistic" is gloved arms with posed fingers and sleeves that match the player's shirt. "ACSClassic" is the blocky ACS style arms. |
Providers.HUD | "XSFMinimal" | "XSFMinimal" is XSF's small readouts that only show when they matter. "None" turns off the XSF HUD completely so you can build your own with the client API. |
Preset and the website
| Option | Default | What it does |
|---|---|---|
Preset | nil | nil uses the preset picked for this game on the dashboard, or the one called Default. A name like "TDM" always uses that preset. See Presets and Studio. |
UseWebsite | true | false cuts the dashboard out completely. The game ignores every website setting and preset and runs only on GameConfig and the weapon Configs, and the "newer version" notice in Output is skipped too. The license check still runs, so Allow HTTP Requests has to stay on. Player settings are then saved in this game's own DataStore instead of on the XSF site (the same as SettingsSave = "Game"). Handy when you'd rather keep everything in Studio and never wonder which side a value came from. |
SettingsSave | "Shared" | Where each player's XSF settings are saved: sensitivity, FOV, keybinds, HUD options, and the attachments and presets they picked in the loadout screen. "Shared" saves them on the XSF site, so they follow the player into every game that uses XSF (a copy is also kept in this game's DataStore, used when the site can't be reached). "Game" saves them only in this game's DataStore (XSF_Settings_v1), so your game has its own settings and doesn't share them with other XSF games. "Off" saves nothing: every player starts from the defaults each time they join, and changes only last until they leave. With UseWebsite = false, "Shared" acts like "Game"; "Off" still means off. DataStores need Enable Studio Access to API Services to work in Studio. |
Loadout
| Option | Default | What it does |
|---|---|---|
Loadout | { "HK416", "AK47", "SCAR", "AWM", "M249", "Deagle", "M870", "Knife", "Karambit", "M67", "Flashbang", "RPG7", "C4" } | The weapons every player spawns with, in slot order. Names are folder names in XSF.Weapons, capitals included. See Choosing the weapons. |
Movement
All speeds are studs per second, the same unit as Humanoid.WalkSpeed. Roblox's own default walk speed is 16, so XSF ships a bit slower and heavier. See Movement and stances for how they combine.
| Option | Default | What it does |
|---|---|---|
Movement.WalkSpeed | 12 | Normal walking speed. |
Movement.SprintSpeed | 21 | Sprinting speed. Sprinting lowers the gun, so you can't fire mid sprint. |
Movement.AimSpeed | 7.5 | Speed while aiming down sights. |
Movement.ReloadSpeed | 10 | Speed while reloading. |
Movement.CrouchMultiplier | 0.55 | Crouched speed is whatever speed you'd have, times this. Crouch walking is 12 x 0.55 = 6.6. |
Movement.ProneSpeed | 3.2 | Crawling speed when prone. |
Movement.Stamina | 7 | Seconds of sprint from a full bar. |
Movement.StaminaRegen | 4.5 | Seconds to refill the bar from empty. |
Movement.SprintReload | false | true lets players keep sprinting through a reload. The legs run at sprint speed and stamina drains as normal, while the hands carry on with the reload instead of dropping to the sprint carry. Off, a reload slows you to ReloadSpeed. |
Movement.Slide.Enabled | true | Turns sliding on or off. With it off, crouch while sprinting just crouches. |
Movement.Slide.Speed | 30 | Speed at the start of the slide. |
Movement.Slide.EndSpeed | 7 | Speed it slows down to by the end. |
Movement.Slide.Duration | 0.8 | How long a slide lasts, in seconds. |
Movement.Slide.Cooldown | 0.7 | Seconds before you can slide again. |
Movement.Slide.CanShoot | true | Allow firing mid slide. Recoil is multiplied by Stances.Brace.Slide (1.25) while sliding. |
Camera
See Camera styles for what the two styles look like and when to use them.
| Option | Default | What it does |
|---|---|---|
Camera.Style | "Bodycam" | "Standard" or "Bodycam" (wide lens, vignette, grain, bleached colour). Delete the line and it falls back to Standard. |
Camera.BodycamFov | 100 | Field of view used by the Bodycam style. Players can't change this one. |
Camera.SharpWeapon | true | Not written in the GameConfig. Keeps the gun in your hands sharp in first person by turning your map's DepthOfField near blur off while the player is alive. false leaves your DepthOfField exactly as you set it. See Grain and blur. |
Camera.FieldOfView | 80 | (player) Field of view in the Standard style. |
Camera.Sensitivity | 1 | (player) Mouse sensitivity multiplier. 1 matches Roblox's own feel. |
Camera.AimSensitivity | 0.85 | (player) Scoped sensitivity: the multiplier while aiming down any sight. Players change it as Scoped sensitivity in the CAMERA tab. |
Camera.GamepadSpeed | 260 | (player) Controller look speed, degrees per second at full stick. |
Camera.GamepadCurve | 1.8 | Controller stick curve. Higher gives finer control near the centre and faster turns at the edge. 1 is linear. |
Camera.TouchSpeed | 0.28 | Mobile look speed, degrees per pixel dragged. |
Camera.EyeHeight | nil | Camera height above the HumanoidRootPart, in studs. nil works it out from the character, which is right for normal R6 and R15 rigs. Set it if you use unusual character sizes. |
Camera.Tilt | 1 | (player) The view leans into strafes. 0 is off, 1 normal, above 1 stronger. |
Camera.Bob | 1 | (player) The view bobs as you walk. |
Camera.RecoilKick | 1 | (player) The view kicks when you fire. This is only the camera, the gun still recoils. |
Camera.Landing | 1 | (player) The view dips when you land from a jump. |
Motion (gun sway)
How much the gun moves in your hands. All four are player settings, and the Comfort motion preset in the settings menu lowers them for people who get motion sick.
| Option | Default | What it does |
|---|---|---|
Motion.Inertia | 1 | The gun lags behind when you turn and settles after. Heavier guns lag more. |
Motion.Lead | 1 | The muzzle swings ahead into the direction you're turning, like you're leading with your arms. |
Motion.Bob | 1 | The gun bobs as you walk and run. |
Motion.Breath | 1 | Slow breathing sway while standing still. |
FreeAim (gun movement)
Bodycam style aiming: small mouse movements swing the gun first, and the body only turns once the gun nears the edge of a box. Only you can change this, players can't. The full explanation is in Gun movement.
| Option | Default | What it does |
|---|---|---|
FreeAim.Enabled | true | On or off. Off locks the gun to the centre of the screen like a normal shooter. |
FreeAim.Yaw | 9 | How far the gun can swing left or right before the body turns, in degrees. |
FreeAim.Pitch | 5 | How far it can swing up or down, in degrees. |
FreeAim.Return | 0.4 | How fast the body squares up behind the gun when you hold still. 0 is never. |
Customization (loadout screen and unlocks)
The loadout screen (N, Y on controller, the KIT button on mobile), where players fit attachments, pick ammo and put skins on each gun or knife. Every change is checked on the server and saved with the player's settings, with three presets per gun. See Loadout screen and Unlocks.
| Option | Default | What it does |
|---|---|---|
Customization.Enabled | true | Turns the loadout screen on or off. Off hides it (the Customize key does nothing and the KIT button on phones is gone) and the server refuses every change, including SetKit from the client API, so each gun keeps the attachments in its Config. Server scripts can still change attachments with XSF.SetAttachments. |
Customization.Slots | { Sight = true, Barrel = true, UnderBarrel = true, Other = true, Ammo = true, Skin = true } | Which slots players may change. Set a slot to false to lock it to whatever the gun comes with. |
Customization.Allowed | nil | Per gun lists of what players may pick, attachments and skins together, like { AK47 = { "Acog", "Woodland" } }. A listed gun only offers those. Guns you leave out aren't limited. |
Customization.Blocked | nil | A list of attachment or skin names nobody can pick on any gun, like { "PM II", "Gold Plated" }. |
Customization.Ownership | { Attachments = false, Skins = false } | By default everyone can use everything. true means players only get what you give them with XSF.GrantAttachment and XSF.GrantSkin. Ownership = false turns both off. A gun's own default attachments are always free. |
Customization.Starter | { "FMJ" } | Items that are free for everyone. A plain name frees it on every gun ("Reflex"). "AK47:Acog" frees one item on one gun. Skins work here too. |
Customization.SaveOwnership | false | true: XSF remembers unlocks in its own DataStore. false: you save XSF.GetOwned(player) yourself and load it with XSF.SetOwned. |
Customization.Access | "Anywhere" | "Anywhere" opens the loadout screen any time with the Customize key. "Workbench" only opens it at a workbench while holding the gun. Place benches by copying ReplicatedStorage.XSF.Assets.Workbench into your map, as many as you like. "Off" is the same as Enabled = false. |
Customization.Required | nil | A list of slots players can't leave empty, like { "Sight" } or { "Sight", "Skin" }. The loadout screen drops the None choice for those slots, and the server refuses a kit that leaves one empty with "Pick something for the Sight slot." It only counts on guns that have at least one choice for that slot the player can use, so a pistol with no sights is never stuck. It only checks changes players make; a gun's own default kit and XSF.SetAttachments from your scripts aren't checked. Slot names: Sight, Barrel, UnderBarrel, Other, Skin (a required Skin removes the Factory choice). Ammo is never empty, since no pick means the normal rounds, so listing it does nothing. nil or an empty list means every slot can be empty. |
Customization.FoldIronSights | true | When a sight is fitted, the gun's iron sights fold away so they don't block the view. A part counts as an iron sight when it is named IS, ISa, IronSight or IronSights, or has the attribute XSF_IronSight = true. It is hidden, or, with an XSF_Fold attribute (degrees), tipped forward flat about its bottom edge. Of XSF's own guns the HK416, MP5 and HK33 have separate iron sights; the others have them built into the receiver. false keeps iron sights up under every optic. See Adding a gun. |
Animations
| Option | Default | What it does |
|---|---|---|
Animations | "XSF" | "XSF" uses XSF's own draw, reload, jam clearing and inspect on magazine guns, fitted to the arms and worked out from each gun's own magazine. "Imported" keeps the clips that came with each gun (handy for imported ACS guns with hand made reloads). Bolt actions, revolvers, shotguns and launchers always use their own reloads. |
Malfunctions (jams)
| Option | Default | What it does |
|---|---|---|
Malfunctions.Enabled | false | Turns jams on. A jammed gun shows JAMMED and won't fire until the player reloads. See Jams, sound and effects. |
Malfunctions.Chance | 0.003 | Chance per shot before heat. 0.003 is about one jam every 300 rounds. Long bursts and suppressors raise it a little. |
Throwables
How grenades, smokes, flashbangs and C4 fly and land. These are the defaults for every throwable. Any weapon can set the same names inside its own Grenade table, which wins over these (see Grenades).
| Option | Default | What it does |
|---|---|---|
Throwables.Arc | 0.12 | How far above the crosshair a normal throw leaves. 0 throws flat along the crosshair, 0.3 is a high arc. |
Throwables.LobArc | 0.25 | The same for an underarm lob (holding aim while you let go). |
Throwables.Lift | 0.12 | Extra upward speed on top of the aim, as a share of the throw speed. |
Throwables.Gravity | 1 | Gravity on the grenade, as a share of workspace.Gravity. 0.5 floats twice as long, 1.5 drops fast. C4 always uses normal gravity so it sticks where you expect. |
Throwables.Bounce | 0.18 | Elasticity. 0 lands dead, 0.6 bounces around a room. |
Throwables.Friction | 1.2 | Grip on the ground, 0 to 2. Low values slide on smooth floors. |
Throwables.Density | 3 | How heavy it is. Heavier grenades shove loose props harder. |
Throwables.Spin | 12 | How much it tumbles on the way out. 0 flies without spinning. |
Throwables.RollDrag | 0 | Share of its speed a grenade loses every second while it's on the ground. 0 rolls freely (down a slope it keeps going), 3 stops within a second or so. |
-- floatier, higher throws that stop rolling quickly
Throwables = { Arc = 0.2, Gravity = 0.7, RollDrag = 3 },Performance
| Option | Default | What it does |
|---|---|---|
Performance.LowEffects | false | From 2.2 and older, still works: true starts players in the Performance graphics mode. For new games use Graphics.Default instead. See Graphics modes. |
Stances
| Option | Default | What it does |
|---|---|---|
Stances.Crouch | true | Allow crouching. |
Stances.Prone | true | Allow going prone. |
Stances.Lean | true | Allow leaning left and right. |
Stances.LeanAngle | 22 | How far the view tilts when leaning, in degrees. |
Stances.LeanOffset | 1.1 | How far the head moves sideways when leaning, in studs. This is what lets you peek around a corner. |
Stances.Brace | { Crouch = 0.8, Prone = 0.62, Bipod = 0.42, Slide = 1.25 } | Recoil multiplier per stance. Lower is steadier. Bipod applies when prone with a bipod fitted, Slide while sliding. Standing is always 1. |
Downed
When a player would die they go down first and a teammate can revive them. See Downed and revive.
| Option | Default | What it does |
|---|---|---|
Downed.Enabled | true | Players go down before dying and can be revived. Off means they die straight away. |
Downed.BleedOutTime | 45 | Seconds before a downed player dies on their own. |
Downed.ReviveTime | 5 | Seconds a teammate holds the revive prompt. |
Downed.ReviveHealth | 35 | Health after being revived. |
Downed.DownedHealth | 40 | Extra damage a downed player can take before dying. Shooting a downed player finishes them. |
Downed.TeamOnly | true | Only teammates can revive. Ignored in games without teams. |
Downed.CrawlSpeed | 2.5 | Crawling speed while downed. |
Downed.AllowFiring | false | Let downed players shoot. |
Downed.NPCs | false | NPCs go down too, instead of dying straight away. |
Downed.Drag | true | Teammates can drag a downed player to cover (H on keyboard, D-pad up on controller). |
Downed.DragSpeed | 6 | Walking speed while dragging someone. |
Backpack (hotbar)
The hotbar along the bottom of the screen and the backpack panel. See Hotbar and backpack.
| Option | Default | What it does |
|---|---|---|
Backpack.Hotbar | "Always" | "Always" keeps the hotbar on screen, slightly faded until you switch. "Fade" only shows it for a moment when you switch weapons or hold Check gear. |
Backpack.HotbarSize | 5 | How many slots the hotbar has, from 1 to 9. Number keys 1 to this pick them. Everything else waits in the backpack. |
Damage
| Option | Default | What it does |
|---|---|---|
Damage.FriendlyFire | false | Teammates can hurt each other, with bullets and explosions. Players with no team, or on a neutral team, can always be hurt. |
Damage.Hitmarkers | false | Show a small marker when your shots land. This is also the default for the player's own Hitmarkers setting. |
Audio
| Option | Default | What it does |
|---|---|---|
Audio.Footsteps | true | XSF's own footstep sounds, which change with the floor material. |
Audio.StepVolume | 1 | Footstep volume multiplier. |
Audio.RobloxSteps | false | Also keep Roblox's default running sound. You'll hear both, so leave this off unless you turned XSF footsteps off. |
Audio.Ambience | true | Quiet wind and distant traffic outdoors, room tone indoors. Turn it off if your game plays its own ambience. |
Audio.CutFade | nil | Seconds a gunshot takes to fade out once it reaches its Cut point. nil works it out from the sound's length (0.05 to 0.18 seconds). Raise it to 0.3 or so if shots sound clipped, lower it for a tighter, drier crack. A gun can set its own with Sounds.Fade in its Config. |
Audio.Voices | nil | How many copies of the same sound can play over each other. nil is 8. Very fast guns in a big firefight can run out and cut older shots short, so try 12 to 16 there. Each voice is a Sound kept around, so don't go wild. When every copy is busy, the oldest one is reused and fades out over Audio.StealFade instead of stopping dead. |
Audio.StealFade | 0.08 | Seconds a sound fades out when its copy is reused because all Audio.Voices copies are busy. Without it, long recordings with the echo baked in (shots with a 2 second tail) get cut off with an audible click during long bursts. Raise it to 0.15 or so for very long, wet recordings. 0 stops the old sound straight away like 2.0 did. |
Audio.ParentGroup | nil | A SoundGroup, or the name of one in SoundService, that XSF's three sound groups (XSF_World, XSF_Own, XSF_Body) are put under. Your master volume slider, compressor or limiter on that group then covers XSF too. nil leaves them in SoundService. A name that doesn't exist after 10 seconds prints a warning and changes nothing. |
Audio.StepSets | nil | Your own footstep sounds per floor material, keyed by Enum.Material name (Grass, Concrete, Metal, WoodPlanks and so on). Default covers every material you don't list. Each entry is { Ids = { ... }, Volume = 1, Pitch = 1 } or just a list of ids. A random id is picked every step. Volume and pitch are optional; leave them out to keep XSF's own (quieter on soft ground, higher on metal). Ids can be numbers or "rbxassetid://123". Materials with no entry and no Default keep XSF's sounds. |
Audio = {
-- XSF goes through your master group, so your volume slider covers it
ParentGroup = "Master",
StepSets = {
Grass = { Ids = { 1111111111, 2222222222 }, Volume = 0.7 },
Metal = { Ids = { 3333333333 }, Volume = 1.1, Pitch = 1.05 },
Default = { 4444444444, 5555555555 }, -- every other floor (your own sound ids)
},
},Holster
Empty hands. The Holster key (Y by default), or the number key of the gun in your hands, puts the gun away and takes the same gun back out. Other players see the gun leave your hands too, and nothing is equipped on the server, so shots are refused until a weapon is out again. XSF won't hand a gun back by itself while you're holstered, even when your inventory changes. A new life starts armed as normal.
| Option | Default | What it does |
|---|---|---|
Holster.Enabled | true | false turns the Holster key off. Your scripts can still holster with XSF.Unequip() (client). |
Holster.Reselect | true | Pressing the number key of the gun you're holding puts it away too, and pressing it again takes it back out. false leaves only the Holster key. |
Holster.Hands | true | Shows first person hands while nothing is held: relaxed in the lower corners standing still, swinging in step when walking, and bent arms pumping with loose fists when running. They also lift when jumping, reach forward in a slide and hide when prone or downed. Works with every arms style (XSFRealistic, ACSClassic, XSFSimple and your own Providers.Arms module, as long as it has the usual update function). false shows nothing with empty hands. |
A grenade being cooked or thrown can't be put away halfway. Games using Providers.Inventory = "RobloxTools" holster the normal Roblox way, by clicking the tool again, and the gun now leaves the character for other players there too.
AimAssist
Controller only, mouse and touch are never affected. Looking slows down while an enemy is near the barrel, and while the player moves the stick the view eases gently toward them. It never snaps or locks on, and does nothing while the stick is still. Players scale all of it with Controller aim assist in the CAMERA tab (0 turns it off for them).
| Option | Default | What it does |
|---|---|---|
AimAssist.Enabled | true | Turns the slowdown on or off. |
AimAssist.Slowdown | 0.55 | Look speed multiplier over a target. Lower is stickier. |
AimAssist.Pull | 0.35 | How strongly the view eases toward a target while the stick moves. 0 keeps only the slowdown. |
AimAssist.Angle | 2.5 | How close to the barrel a target has to be, in degrees. |
AimAssist.Range | 300 | Furthest a target can be, in studs. |
Rumble
| Option | Default | What it does |
|---|---|---|
Rumble.Enabled | true | Controller vibration when firing. |
Rumble.Strength | 1 | Scales the vibration. |
Controls
| Option | Default | What it does |
|---|---|---|
Controls.AimToggle | false | (player) Aim is a toggle instead of hold. |
Controls.SprintToggle | false | (player) Sprint is a toggle instead of hold. Sprint always toggles on controller. |
Controls.LeanToggle | false | (player) Lean is a toggle instead of hold. |
Controls.CrouchToggle | true | Crouch is a toggle. Set false for hold to crouch. |
Controls.CrouchHoldProne | true | (player) Controller and touch: tap crouch to crouch, hold it to go prone. |
Controls.AutoReload | true | Guns start reloading by themselves a moment after the last round is fired, as long as there are spare rounds. Off means the player has to press reload. |
Ballistics
World scale for bullets. Leave these alone unless your map is built at an unusual scale. See Bullets and walls.
| Option | Default | What it does |
|---|---|---|
Ballistics.StudsPerMeter | 1 / 0.28 | How many studs make a meter. 1 / 0.28 means one stud is 28 cm, which fits standard Roblox characters. Bullet speed and drop are worked out in meters and converted with this. |
Ballistics.Gravity | 9.81 | Bullet gravity in m/s². Separate from workspace.Gravity, so a low gravity map doesn't make bullets float. |
Ballistics.MaxLife | 3 | Seconds a bullet is simulated before it's removed. At 880 m/s that's about 2.6 km, far more than any map. |
Debug
| Option | Default | What it does |
|---|---|---|
Debug.Overlay | false | Shows a performance readout on screen for everyone, in live games too. In Studio you can toggle it with F3 instead. |
Debug.LogRejects | false | true prints a line in Output every time the server throws away a shot that didn't add up. |
UI
| Option | Default | What it does |
|---|---|---|
UI.TopbarButton | true | A round XSF settings button in Roblox's top bar, next to Roblox's own buttons. false removes it (players still have P and D-pad right). |
UI.Crosshair.Default | "Off" | The crosshair new players start with: "Off", "Dot", "Cross", "Cross + dot", "Circle" or "T". Players change it in Settings > HUD. |
UI.Crosshair.Allowed | true | false removes the crosshair from the game, whatever players picked. See Crosshair. |
UI.LowAmmoWarning | true | RELOAD, LOW AMMO or NO AMMO under the crosshair when the magazine runs low. false turns it off for everyone; players can also turn it off for themselves. |
The look of every screen is in ReplicatedStorage.XSF.UI, not in GameConfig. See Editing XSF's screens.
Ping
| Option | Default | What it does |
|---|---|---|
Ping.Enabled | true | Ping markers on or off. |
Ping.TeamOnly | true | Only teammates see a player's markers. In a game without teams that means only the player. false shows them to everyone. |
Ping.RemoveOnDeath | true | A player's marker goes away when they die. |
Ping.Lifetime | 12 | Seconds a marker stays. |
Ping.MaxDistance | 1000 | How far away a player can ping, in studs. The server checks it. |
Graphics
| Option | Default | What it does |
|---|---|---|
Graphics.Default | "Auto" | Where new players start. Auto measures each player's frame rate after they spawn and picks Full, Balanced or Performance. "Full", "Balanced" or "Performance" starts everyone there. Players can always change it. See Graphics modes. |
Graphics.AdaptDown | true | Auto drops one mode when the frame rate stays under 35 for 6 seconds. |
Mobile
| Option | Default | What it does |
|---|---|---|
Mobile.AutoFire | true | Lets phone players turn on auto fire (fires while the crosshair is on an enemy). It is off until a player turns it on. false removes the option. |
Mobile.Gyro | true | Lets players turn on gyro aim. false removes the option. |
PerformanceReports
| Option | Default | What it does |
|---|---|---|
PerformanceReports | true | Each live server sends the XSF site a small anonymous performance summary every 5 minutes. false turns it off. See Performance reports for exactly what is sent. |
Security
| Option | Default | What it does |
|---|---|---|
Security.MovementChecks | true | Reports speed, teleports and flying beyond what XSF allows. Turn off for games with jetpacks or client driven vehicles. |
Security.SuspicionDecay | 1 | Suspicion points drained per second. |
Security.LogSuspicion | false | Prints every report in Output, for testing. |
Keys and key names #
Default keys live in Keybinds in GameConfig. Every action takes a list, and you can mix keyboard keys, mouse buttons and controller buttons in the same list. Players can rebind every action in the settings menu, and their binds are saved with their settings, so your defaults only matter for new players.
| Action | Default | What it does |
|---|---|---|
Fire | MouseButton1, ButtonR2 | Shoot. Hold for automatic fire. On grenades, hold to get ready (and cook a frag) and release to throw. On knives, slash. |
Aim | MouseButton2, ButtonL2 | Aim down sights. Hold or toggle, the player picks in settings. With a grenade, hold as you release to lob it underhand. With C4 out, sets off your charges. |
Reload | R, ButtonX | Reload. With rounds still in the magazine it's a quicker reload that keeps the old magazine and one in the chamber. Also clears a jam. |
Sprint | LeftShift, ButtonL3 | Sprint. Always a toggle on controller. |
Crouch | C, ButtonB | Crouch. While sprinting it slides. On controller and touch, holding it goes prone. |
Prone | Z | Go prone. Press again to get up. |
LeanLeft / LeanRight | Q / E | Lean around corners. Hold or toggle, the player picks. |
FireMode | V, DPadUp | Switch between the gun's fire modes (auto, burst, semi). |
CheckMag | T, DPadLeft | Look at the magazine. The HUD says roughly how full it is (FULL, NEARLY FULL, ABOUT HALF, ALMOST EMPTY) instead of an exact count. |
CheckGear | G, DPadLeft | Show the full HUD for a moment: health, stance, attachments, spare magazines. |
Inspect | I, DPadDown | Turn the gun over to look at it. Knives do their own tricks. |
NextWeapon / PrevWeapon | none / ButtonL1 | Cycle weapons on the hotbar. The mouse wheel also does this when you're not aiming (while aiming it zooms a variable scope) and isn't in Keybinds. Since 2.3 RB is the weapon wheel key, which also cycles on a tap. |
WeaponWheel | ButtonR1 | Tap: next weapon on the hotbar. Hold: the weapon wheel with everything you carry; point with the right stick (or the mouse) and let go. See Weapon wheel. |
Ping | MouseButton3 | Middle mouse. Marks what you look at for your team. See Ping markers. |
Light / Laser | L / K | Turn the weapon light or laser on and off. Everyone sees them. |
GearToggle | ButtonR3 | Controller: cycles light and laser. |
ZeroUp / ZeroDown | RightBracket / LeftBracket | Change the sight zero (or the M320 range ladder) on guns that have zero steps. |
ZoomIn / ZoomOut | none | Step a variable scope in or out while aiming (the PM II and the M82's own scope). The mouse wheel does this while aiming and isn't in Keybinds. Bind these for controllers or if you want keys too. |
Settings | P, DPadRight | Open the settings menu. |
Customize | N, ButtonY | Open the loadout screen. |
Backpack | B, ButtonSelect | Open the backpack panel. See Hotbar and backpack. |
Pickup | F, ButtonY | Pick up a gun or take ammo from a box on the ground. Rebindable like everything else, and the prompt on the ground shows whatever key the player picked. Only one keyboard key and one controller button are used, the first of each in the list. |
Drop | X | Drop the gun in your hands. On a controller, holding D-pad down (Inspect) also drops it, or give Drop a button of its own. See Pickups. |
Holster | Y | Put the gun away (empty hands), and press again to take it back out. See Holster. |
QuickLethal | not bound | Throws the first frag, C4 or claymore the player carries without switching to it first, then goes back to what was in hand (or back to empty hands). Hold to cook a frag, let go to throw. Bind it yourself, for example { "G", "ButtonL1" }, or let players bind it in the settings menu. |
QuickTactical | not bound | The same for a flashbang or smoke. A weapon's Config can move it between the two with Grenade.Quick = "Lethal" or "Tactical". |
FreeCursor | LeftAlt | Hold to free the mouse cursor. Clicks then go to your UI and never shoot. See Menus and the mouse. |
Slot1 to Slot9 | One to Nine | Jump straight to a hotbar slot. Slots above Backpack.HotbarSize do nothing. |
Two keys are fixed because they belong to prompts that pop up in the world: revive (hold F, D-pad down) and drag (H, D-pad up). Roblox draws those prompts, so players see the key on screen.
Writing key names
- Keyboard keys use the Roblox
Enum.KeyCodename without theEnum.KeyCode.part:"F","LeftShift","LeftControl","LeftAlt","Space","Tab","CapsLock","One","Two". - Mouse buttons are
"MouseButton1"(left),"MouseButton2"(right) and"MouseButton3"(middle). - Controller buttons are
"ButtonA","ButtonB","ButtonX","ButtonY","ButtonL1","ButtonR1","ButtonL2","ButtonR2","ButtonL3","ButtonR3","DPadUp","DPadDown","DPadLeft","DPadRight","ButtonSelect","ButtonStart". These are Xbox names, and PlayStation controllers map to them automatically (Cross is ButtonA, Circle is ButtonB, and so on). - A misspelled name doesn't break anything. XSF skips it and Output says
"X" is not a Roblox key name, with which action lost its key. - Two actions can share a key.
CheckMagandCheckGearboth use D-pad left on controller by default. - Avoid keys Roblox already uses: Escape (menu), the slash key (chat) and F9 (developer console).
Keybinds = {
Crouch = { "LeftControl", "ButtonB" }, -- crouch on Ctrl instead of C
Prone = { "X" },
Drop = { "G", "DPadDown" }, -- G, or D-pad down on a controller
-- actions you leave out keep their default keys
},You only need to list the actions you change. Everything else keeps its default.
Camera styles #
XSF has two camera looks, picked with Camera.Style. Both are first person, and both use the same arms, recoil and sway. What changes is the lens and the picture.
| Standard | Bodycam | |
|---|---|---|
| Field of view | Camera.FieldOfView (80), and players can change it | Camera.BodycamFov (100), fixed for everyone |
| Picture | Clean, Roblox's normal colours | Wide lens, vignette at the edges, film grain and bleached colour, like real body camera footage |
| Works well with | Arcade, competitive, anything where players need to read the map clearly | Tactical, horror, realism, short clips people share |
| Shipped default? | No (it's the fallback if you delete the line) | Yes |
Bodycam looks best with free aim on, because the gun moving inside the frame is half of what sells the look. Standard with free aim off feels like a normal modern shooter.
-- clean competitive look
Camera = { Style = "Standard", FieldOfView = 85 },
FreeAim = { Enabled = false },
-- heavy bodycam look
Camera = { Style = "Bodycam", BodycamFov = 104 },
FreeAim = { Enabled = true, Yaw = 11, Pitch = 6 },Only change the lines you care about. These snippets show the important values, keep the rest of each table as it ships.
Camera effects
Four effects run on top of either style. Each is a multiplier: 0 turns it off, 1 is normal, 2 doubles it. They're all player settings too, so players can turn them down for themselves.
Camera.Tiltleans the view a little into strafes.Camera.Bobbobs the view as you walk.Camera.RecoilKickkicks the view up when you fire. The gun's own recoil still happens with this at 0, so the gun still climbs.Camera.Landingdips the view when you land from a jump or a fall.
Players also get a Lens and grain switch and a Stress effects slider (blur, tunnel vision and ringing when hurt or flashed) in the settings menu. See Player settings.
Looking around
- Mouse speed is Roblox's own feel times
Camera.Sensitivity, and timesCamera.AimSensitivitywhile aiming. - Controller speed is
Camera.GamepadSpeeddegrees per second at full stick, shaped byCamera.GamepadCurveso small stick movements stay precise. - Touch speed is
Camera.TouchSpeeddegrees per pixel dragged on the right side of the screen.
Taking the camera away for a moment
Cutscenes, spectating and menus need the camera. Call SetCameraEnabled(false) from the client API: XSF puts the gun away and hands the camera back to Roblox. SetCameraEnabled(true) takes it back. Don't fight XSF by setting CameraType every frame.
Movement and stances #
XSF takes over WalkSpeed, sprinting, crouch, prone, lean, slide and vault. You set the numbers in Movement and Stances, and XSF picks the right speed every frame.
Which speed applies
XSF works out a base speed first, then the stance changes it:
- Base speed. Sprinting uses
Movement.SprintSpeed(21). If not sprinting, aiming usesMovement.AimSpeed(7.5). If not aiming, reloading usesMovement.ReloadSpeed(10). Otherwise it'sMovement.WalkSpeed(12). - Stance. Crouching multiplies the base speed by
Movement.CrouchMultiplier(0.55). Prone replaces it withMovement.ProneSpeed(3.2). A slide starts atSlide.Speed(30) and eases down toSlide.EndSpeed(7). Downed players crawl atDowned.CrawlSpeed(2.5). - Dragging a downed teammate caps the speed at
Downed.DragSpeed(6).
Don't set Humanoid.WalkSpeed from your own scripts, XSF will overwrite it on the next frame. For a slow zone, a speed boost or a tactical sprint, add a speed modifier from a LocalScript with AddSpeedModifier. It gets the final speed after all of the steps above and returns your own. See Movement state and speed.
Sprint and stamina
- Sprinting only works while moving forward on the ground and not reloading. The gun drops to a low carry and can't fire while you sprint. With
Movement.SprintReload = trueplayers can keep running through a reload: the legs sprint, the hands keep reloading. - A full bar lasts
Movement.Staminaseconds (7). - The bar starts refilling about 0.8 seconds after you stop sprinting and takes
Movement.StaminaRegenseconds (4.5) to fill from empty. - Run it to zero and you're winded: you can't sprint again until the bar is back to 30%. You'll hear the breathing.
- Players pick hold or toggle sprint in settings. On controller it's always a toggle (click L3).
For an arcade game, raise both: Stamina = 15 and StaminaRegen = 3. To effectively remove stamina, set Stamina = 9999.
Crouch and prone
- Crouch is a toggle by default (
Controls.CrouchToggle). Prone has its own key (Z) on keyboard. On controller and touch, tap crouch to crouch and hold it to go prone. - You can't stand up under something low. XSF checks for headroom first: about 2.6 studs to stand from a crouch, 3.2 from prone. From prone with no room to stand, you go to a crouch instead.
- Jumping while crouched stands you up first, if there's room.
- Other players see you crouch, slide and go prone. On R15 the hips lower and the knees bend. R6 has no knees, so since 2.1 an R6 crouch is shown as a kneel (body down about a stud, one leg forward and one back) and a slide as the body low and leaning back with both legs out in front. The server lowers an R6 player's head, torso and arm hitboxes to match, so shots land where the body is seen. NPCs with
XSF_StanceandXSF_Slidingattributes get the same poses. - Crouching makes recoil steadier (x0.8), prone more so (x0.62), and prone with a bipod fitted the most (x0.42). These are the
Stances.Bracenumbers. - Turn any stance off with
Stances.Crouch,Stances.ProneorStances.Lean.
Leaning
Q and E lean the view Stances.LeanAngle degrees (22) and move the head Stances.LeanOffset studs (1.1) to the side. That offset is what lets you peek a corner without stepping out. Other players see you lean. Players choose hold or toggle. Lean isn't bound on controller by default, add a button in Keybinds if you want it there.
Sliding
Press crouch while sprinting. You drop low and slide at Slide.Speed, slowing to Slide.EndSpeed over Slide.Duration seconds, then you can't slide again for Slide.Cooldown seconds. You can shoot during a slide if Slide.CanShoot is on, with recoil times 1.25. Turn sliding off with Movement.Slide.Enabled = false.
Jumping and landing
Jumping uses Roblox's normal jump. XSF adds the landing dip (Camera.Landing) and a vault or climb when you jump at an obstacle you can get over, see Vault and mantle. To change jump height, change StarterPlayer.CharacterJumpPower or CharacterJumpHeight as usual.
Gun movement (FreeAim) #
With FreeAim on, which is how XSF ships, the gun isn't locked to the middle of the screen. Small mouse or stick movements swing the gun inside a box first, and the body only turns once the gun gets near the edge of that box. It's what gives bodycam shooters their heavy, body worn feel. Players can't turn it off, only you can, in GameConfig.
How it moves
- It has weight. The gun doesn't follow the mouse one to one. It swings toward where you point on a spring, so a quick flick eases out and settles instead of jumping.
- The edge is soft. Near the centre the gun takes almost all of the movement. The closer it gets to the edge, the more the body takes over, so there's never a moment where the gun stops dead and the view suddenly starts turning.
- Coming back leads with the gun. Moving back toward the centre, the gun moves first and the body waits, the way you'd re-aim with your arms.
- The body squares up. When you hold still, the body slowly turns in behind the gun until it's centred again.
Returnsets how fast. 0 turns this off. - Aiming centres it. Aiming down sights brings the gun smoothly back to the middle, and it stays there while you aim.
- Bullets go where the barrel points, not to the middle of the screen. With FreeAim on, the crosshair setting shows the centre of the view, so learn to read the gun.
| Option | Default | What it does |
|---|---|---|
FreeAim.Enabled | true | On or off. Off locks the gun to the centre like a normal shooter. |
FreeAim.Yaw | 9 | How far the gun can swing left or right, in degrees. Bigger feels looser and heavier. |
FreeAim.Pitch | 5 | How far it can swing up or down, in degrees. |
FreeAim.Return | 0.4 | How fast the body squares up behind the gun when you hold still. 0 is never, around 1 is quick. |
Suggested setups
-- tactical, slow and heavy
FreeAim = { Enabled = true, Yaw = 12, Pitch = 7, Return = 0.25 },
-- light touch, close to a normal shooter
FreeAim = { Enabled = true, Yaw = 4, Pitch = 2.5, Return = 1 },
-- off: gun locked to the centre
FreeAim = { Enabled = false },Should I turn it off?
Turn it off for anything competitive or fast. Hip fire with free aim takes practice, and new players blame the game when they miss. Keep it for tactical, milsim, horror and anything that wants to look like footage. Free aim also doesn't matter much on mobile, where most people aim down sights anyway.
Downed and revive #
With Downed.Enabled on (the default), a hit that would kill a player puts them on the ground instead. A teammate can pick them up before they bleed out. It changes how fights play a lot: trading a kill no longer ends the fight, and pushing to finish someone is a real decision.
What the downed player can do
- Crawl at
Downed.CrawlSpeed(2.5 studs per second). - Shoot, only if
Downed.AllowFiringis on. - Give up: hold crouch (or G) for about two seconds to bleed out on the spot, so they can respawn instead of waiting.
- Their HUD shows YOU ARE DOWN.
Bleeding out and finishing
- A downed player dies after
Downed.BleedOutTimeseconds (45) if nobody helps. - They can take
Downed.DownedHealthmore damage (40) before dying, so shooting a downed player finishes them fast. - The kill goes to whoever downed them, even if they bleed out later.
XSF.Killedfires with that attacker.
Reviving
- Walk up to them and a Revive prompt appears (within 7 studs). Hold F, or D-pad down on controller, for
Downed.ReviveTimeseconds (5). - They get up with
Downed.ReviveHealth(35). - With
Downed.TeamOnlyon, only players on the same Team see the prompt. In a game without teams anyone can revive.
Dragging
With Downed.Drag on, a Drag prompt sits under the revive one. Press H (D-pad up) to start pulling the downed player behind you at Downed.DragSpeed (6), and press it again to let go. Use it to get someone out of a doorway before reviving them.
NPCs
NPCs die straight away by default. Set Downed.NPCs = true and they go down like players. Your own scripts can revive them with XSF.Revive.
From scripts
local XSF = require(game.ServerScriptService.XSF.Server)
XSF.PlayerDowned:Connect(function(humanoid, info)
print(humanoid.Parent.Name, "went down")
end)
XSF.Revived:Connect(function(humanoid, byPlayer)
if byPlayer then
print(byPlayer.Name, "revived", humanoid.Parent.Name)
-- reward the medic here
end
end)
XSF.BledOut:Connect(function(humanoid)
print(humanoid.Parent.Name, "bled out")
end)
-- a medkit that revives instantly
local function useMedkit(target)
if XSF.IsDowned(target) then
XSF.Revive(target)
end
endDowned characters also carry the XSF_Downed attribute on their Humanoid, so any script (even a LocalScript) can check it with humanoid:GetAttribute("XSF_Downed").
Turning it off
Downed = { Enabled = false } makes every lethal hit kill straight away. That's the right call for deathmatch, gun game and anything with quick respawns.
Common changes #
The quick answers. Everything here is in GameConfig unless it says otherwise.
| I want to... | Change this |
|---|---|
| make everyone faster | Movement.WalkSpeed and Movement.SprintSpeed. Try 16 and 26 for arcade. |
| turn off sliding, prone or leaning | Movement.Slide.Enabled = false, Stances.Prone = false, Stances.Lean = false |
| get a clean normal camera (it ships as Bodycam) | Camera.Style = "Standard" |
| lock the gun to the centre of the screen | FreeAim.Enabled = false |
| stop guns reloading by themselves | Controls.AutoReload = false |
| let teammates hurt each other | Damage.FriendlyFire = true |
| show hitmarkers | Damage.Hitmarkers = true |
| kill players straight away instead of downing them | Downed.Enabled = false |
| make players earn attachments and skins (shops, crates, levels) | Customization.Ownership = { Attachments = true, Skins = true } |
| stop players changing attachments at all | Customization.Enabled = false |
| let players change sights but not ammo | Customization.Slots = { Sight = true, Barrel = true, UnderBarrel = true, Other = true, Ammo = false, Skin = true } |
| only allow attachment changes at a bench | Customization.Access = "Workbench", then copy XSF.Assets.Workbench into your map |
| make guns jam now and then | Malfunctions.Enabled = true |
| start everyone on the lightest graphics | Graphics = { Default = "Performance" } |
| give everyone a crosshair to start with | UI = { Crosshair = { Default = "Cross" } } |
| no crosshair at all (hardcore) | UI = { Crosshair = { Allowed = false } } |
| no ping markers | Ping = { Enabled = false } |
| no auto fire on phones (competitive) | Mobile = { AutoFire = false } |
| use each gun's own reload animations | Animations = "Imported" |
| use the normal Roblox backpack | Providers.Inventory = "RobloxTools" |
| hide XSF's HUD and make my own | Providers.HUD = "None", then use the client API |
| change a default key | the action in Keybinds, for example Crouch = { "LeftControl", "ButtonB" } |
| give guns from a menu instead of on spawn | add GiveLoadout = false, then call XSF.SetLoadout |
| stop dead players dropping guns | add Pickups = { DropOnDeath = false } |
| turn off vaulting | add Vault = { Enabled = false } |
| put everyone in the same sleeve colour | add Arms = { SleeveColor = Color3.fromRGB(60, 70, 50) } |
| make one gun hit harder | Damage in that gun's Config, or the gun's stats on the dashboard |
| make every gun kick less | the Recoil slider per gun on the dashboard, or Stances.Brace for stance based steadiness |
| give everyone body armour | set XSF_Armor on characters from a script, see How damage works |
Controls #
The default controls, as players see them. Everything in Keybinds can be changed by you in GameConfig and by players in the settings menu.
| Action | Keyboard and mouse | Controller |
|---|---|---|
| Fire / Aim | Left / right mouse | R2 / L2 |
| Reload | R | X |
| Sprint | Left Shift | L3 (click to toggle) |
| Crouch / Slide | C (while sprinting to slide) | tap B, or B while sprinting to slide |
| Prone | Z | hold B |
| Lean left / right | Q / E | not bound |
| Jump and vault | Space | A |
| Next / previous weapon | mouse wheel (not aiming) | R1 / L1 |
| Hotbar slots | 1 to 5 (up to 9 with Backpack.HotbarSize) | |
| Backpack | B | Select |
| Fire mode | V | D-pad up |
| Check magazine | T | D-pad left |
| Check gear (full HUD) | G | D-pad left |
| Inspect weapon | I | D-pad down |
| Light / laser | L / K | R3 cycles both |
| Sight zero up / down | ] / [ | |
| Scope zoom in / out | mouse wheel while aiming | bind ZoomIn / ZoomOut |
| Drop gun | X | |
| Holster (empty hands) | Y, or the number key of the gun in hand | |
| Quick throw lethal / tactical | not bound, players can bind them | |
| Free the mouse cursor (hold) | Left Alt | |
| Pick up gun / take ammo | F | Y |
| Loadout screen | N | Y |
| Settings menu | P | D-pad right |
| Revive a teammate (hold) | F | D-pad down |
| Drag a downed teammate | H | D-pad up |
| Give up while downed (hold 2 s) | G or crouch | crouch |
| Set off C4 | right mouse (or left with no charge in hand) | L2 |
Revive, drag and pick up are prompts that show up in the world next to the person or object, so their keys are fixed and not in Keybinds.
Players can rebind everything in the settings menu, clear a bind with the X next to it, and set aim, sprint and lean to hold or toggle. On controller and touch, tapping crouch crouches and holding it goes prone.
Mobile and console #
XSF switches its controls to whatever the player touched last: keyboard and mouse, controller or touch. Someone on a laptop with a controller plugged in can swap between them mid fight.
Phones and tablets
- The left thumbstick moves, dragging on the right side of the screen looks.
- There are on screen buttons for fire, aim, reload, jump, crouch, sprint, swap weapon, gear (light and laser), ping, lean left and right, fire mode, settings, the loadout screen (KIT), drop and inspect. The hotbar stays on screen so players can tap a slot or swipe along it.
- Players can move, resize, turn and hide every button (MOBILE tab, Move, resize and hide buttons), and turn on auto fire and gyro aim. See Phone button layout.
- Tap crouch to crouch, hold it to go prone.
- Graphics modes pick Full, Balanced or Performance for each phone by itself. See Graphics modes.
- The dashboard's phone and tablet platform choice also sets a 4 slot hotbar, which fits small screens better.
Controller (Xbox, PlayStation, PC)
- Look speed and curve come from
Camera.GamepadSpeedandCamera.GamepadCurve, and players can change look speed in the menu. - Aim assist slows looking down near an enemy and eases the view toward them while the stick moves. It never snaps or locks on. See AimAssist.
- Hold RB for the weapon wheel, tap it for the next weapon. Menus show real controller button pictures. See Controllers and the weapon wheel.
- Guns rumble when they fire (
Rumble). - Sprint is always a toggle on L3.
- Lean isn't bound by default because there aren't enough buttons. Add one in
Keybindsif your game needs it.
Testing other devices in Studio
Use Studio's device emulator (Test tab, Device) to try phone layouts, and plug in a controller to test it directly. XSF switches as soon as you touch the stick.
Player settings #
Players open the settings menu with P or D-pad right. Settings save on the XSF site, so a player keeps the same sensitivity, binds and attachment choices in every game that uses XSF. If a game has HTTP requests off, settings save to that game instead. A game can change where they are saved, or turn saving off, with SettingsSave in GameConfig (see Preset and the website).
What's in the menu
| Tab | Settings |
|---|---|
| GAME | See your legs, toggle aim, toggle sprint |
| HUD | HUD (Minimal, Full or Off), a live crosshair preview, crosshair style, colour, length, gap, thickness, opacity, outline and "opens up when moving and shooting", hit markers and their style, low ammo warning. See Crosshair. |
| CAMERA | Motion preset (Full or Comfort), field of view (Standard camera), look sensitivity, scoped sensitivity (used while aiming down sights), controller look speed, controller aim assist, camera tilt, camera bob, recoil view kick, landing dip |
| WEAPON | Weapon inertia, muzzle lead, weapon bob, breathing sway |
| EFFECTS | Stress effects, lens and grain, graphics (Auto, Full, Balanced, Performance; see Graphics modes), volume |
| CONTROLS | Every keybind. Click one and press the new key, or clear it with the X |
| MOBILE | Move, resize and hide buttons (the layout editor), auto fire on enemies, gyro aim (Off, Aiming, Always), gyro sensitivity, swipe the weapon bar to switch, tap the ammo count to reload, and a switch for every on screen button |
The settings menu also opens from the round XSF button in Roblox's top bar.
RESET ALL SETTINGS at the bottom puts everything back to your game's defaults.
The Comfort preset
Comfort is for players who get motion sick. It cuts camera tilt to 0.15, camera bob to 0.1, recoil view kick to 0.45, landing dip to 0.25, gun bob to 0.5, breathing to 0.4, inertia and lead to 0.7 and stress effects to 0.35. Switching back to Full restores your game's defaults.
Which of your settings players can override
Everything marked (player) in Every GameConfig option: field of view, sensitivities, controller speed, the four camera effects, the four motion values, the toggle settings and crouch hold for prone. Plus the graphics mode, hit markers, the crosshair (unless UI.Crosshair.Allowed = false), the low ammo warning and the phone extras. Your GameConfig value is what a new player starts with.
Things players can never change: camera style, Bodycam FOV, free aim, movement speeds, damage, downed rules and anything about weapons.
Sensitivity
There are two: Look sensitivity for normal looking and Scoped sensitivity while aiming down the sights, on every scope and red dot (one value for all of them, not per scope). Your defaults are Camera.Sensitivity and Camera.AimSensitivity. Phones use Look sensitivity for dragging. In 2.2 and older the aim slider did nothing; since 2.3 it works.
Reading and changing settings from code
Your LocalScripts can read and change a player's settings with GetSetting and SetSetting from the client API, for example to build your own settings screen. Changes save like normal.
| Key | Type |
|---|---|
FieldOfView, Sensitivity, AimSensitivity, GamepadSpeed | number |
CameraTilt, CameraBob, CameraRecoilKick, CameraLanding | number, 0 to 2 |
MotionInertia, MotionLead, MotionBob, MotionBreath | number, 0 to 2 |
MotionPreset | "Full" or "Comfort" |
AimToggle, SprintToggle, LeanToggle, CrouchHoldProne | boolean |
StressEffects | number, 0 to 1 |
LensEffects, ShowLegs, Hitmarkers, LowAmmoWarning | boolean |
Graphics | "Auto", "Full", "Balanced" or "Performance" |
AimAssistStrength | number, 0 to 1 (controller aim assist for this player) |
CrosshairStyle | "Off", "Dot", "Cross", "Cross + dot", "Circle" or "T" |
CrosshairColor | "White", "Green", "Cyan", "Yellow", "Red" or "Pink" |
CrosshairSize, CrosshairGap, CrosshairThickness | number, pixels (2 to 24, 0 to 20, 1 to 6) |
CrosshairOpacity | number, 0.2 to 1 |
CrosshairOutline, CrosshairDynamic | boolean |
HitmarkerStyle | "Cross", "Small" or "Ring" |
AutoFire, SwipeHotbar, TapAmmoReload | boolean (phones) |
Gyro | "Off", "Aiming" or "Always" |
GyroSensitivity | number, 0.2 to 3 |
LowEffects, Crosshair | boolean, from 2.2 and older. Use Graphics and CrosshairStyle instead. |
HudMode | "Minimal", "Full" or "Off" |
MasterVolume | number, 0 to 1 |
Touch_FIRE, Touch_AIM, Touch_RELOAD and the other Touch_ keys | boolean, whether that mobile button shows |
local XSF = require(game.ReplicatedStorage:WaitForChild("XSF").Client.API)
-- a "streamer mode" button in your own menu
local function streamerMode()
XSF.SetSetting("LensEffects", false)
XSF.SetSetting("StressEffects", 0.3)
XSF.SetSetting("CrosshairStyle", "Dot")
end
XSF.SettingChanged:Connect(function(key, value)
if key == "*" then
print("all settings loaded")
else
print(key, "is now", value)
end
end)Editing XSF's screens #
Since 2.3 every XSF screen is a normal ScreenGui you can open and edit in Studio, and all the code that draws them is open source. You don't need to write any code to restyle the HUD, hotbar, backpack, settings menu, loadout screen or death screen: select an element, change it in Properties, play.
Where they are
UI
Theme ModuleScript: colours, fonts, animation speed, presets
XSF_HUD ammo, health, stance, crosshair, hit marker, damage markers, scope readout, downed panel
XSF_Hotbar the weapon bar at the bottom
XSF_Backpack the backpack panel (B)
XSF_Settings the settings menu (P)
XSF_Loadout the loadout screen (N)
XSF_Death the fade when you dieWhen a screen starts, XSF clones its template into the player's PlayerGui and looks up every element it needs by name. That is the only rule: keep the names. Move things, resize them, recolour them, change fonts, add your own frames, images, gradients and strokes. XSF fills the elements it knows and leaves everything else alone.
How to edit
- Open
ReplicatedStorage.XSF.UIin Explorer and select a ScreenGui, for exampleXSF_HUD. - To see it while you work, drag a copy into
StarterGui, edit it there, then drag it back intoXSF.UI(replacing the old one) and delete the copy. Templates inside ReplicatedStorage don't draw on your Studio screen. - Change any property you like. Text that XSF fills (ammo count, weapon name) is replaced while playing, so its Text in the template is only a placeholder.
- Play. Your version is what players see.
The Theme
XSF.UI.Theme sets colours, fonts and animation speed for every screen at once.
-- "Tactical" | "Arcade" | "Military" | "Minimal"
Theme.Preset = "Tactical"
-- single changes on top of the preset
Theme.Overrides = {
Colors = { Amber = Color3.fromRGB(255, 200, 40) },
Fonts = { Display = Font.new("rbxasset://fonts/families/Bangers.json") },
}
-- 1 is normal, 0.5 twice as fast, 0 turns XSF's UI animations off
Theme.AnimationSpeed = 1| Preset | Look |
|---|---|
Tactical | The XSF look: warm off white on near black, amber accents, Oswald headings. The default. |
Arcade | Bright and bold for casual shooters: white on deep blue, yellow and pink accents, Fredoka One. |
Military | Olive and khaki with Michroma headings. |
Minimal | Plain white on black, thin Gotham type, nothing loud. |
The colour names and what they're used for:
| Name | Used for |
|---|---|
Bone | Main text and bright lines. |
Dim | Secondary text. |
Faint | Hints and empty bars. |
Ink | The darkest backgrounds. |
Panel | Menu and card backgrounds. |
Line | Borders and dividers. |
Amber | Warnings, low ammo, highlights, the RELOAD warning, head shot hit markers. |
Red | Damage, danger, kills, NO AMMO. |
Green | Good, ready, owned, stat improvements. |
Fonts: Display (titles and weapon names), DisplayBold (big numbers), Mono (small labels and readouts), Body (longer text in menus).
How the Theme and your edits work together: an element whose colour or font you never touched follows the Theme. As soon as you change one in Properties, your value stays, whatever the Theme says. So you can switch to Arcade and still keep the red health bar you picked by hand. A preset name that doesn't exist prints one warning in Studio and falls back to Tactical.
Repeating items (examples)
Lists that grow (hotbar slots, backpack tiles, settings rows, loadout tabs) use one hidden example inside the list. XSF copies it for every item, so restyle the example and every item follows. Examples carry the attribute XSF_Proto = true and stay hidden.
| Screen | Example | Pieces XSF fills by name |
|---|---|---|
| XSF_Hotbar | Holder.Bar.Slot | Num (slot number), IconArea (the weapon picture goes inside), Label, Ammo. InHand is the bigger slot for the weapon in your hands. |
| XSF_Backpack | Backpack.Box.HotRow.HotSlot, Backpack.Box.List.Tile | Tile: Label, Sub (class and ammo), Stroke (a UIStroke, amber on the held weapon), KeyTag and Key (the hotbar number). |
| XSF_Settings | Panel.Tabs.Tab, Panel.Content.Row, Panel.Content.Note | Tab: Bar (shows on the open tab, attributes OnColor, OffColor, TextPrefix). Row: Label, Right (where the switch, slider or value goes), Line, attributes HoverTransparency and FocusTransparency. |
| XSF_Loadout | Panel.Right.SlotTabs.Tab | Bar under the open slot. |
Every element XSF looks for
| Screen | Elements |
|---|---|
| XSF_HUD | WeaponPanel.Weapon (CanvasGroup) with WeaponName, FireMode, Divider, Ammo, Reserve. InspectPanel.Inspect.Attachments. VitalsPanel.Vitals with ConditionLabel, Condition, HealthBar (HealthLost, HealthFill) and Stamina (StaminaTrack, StaminaFill). VitalsPanel.Stance with Rung1 to Rung3 and StanceText. Crosshair, LowAmmo, Hitmarker (Arm1 to Arm4, optional Ring), DamageRing (attribute Radius, hidden Marker), ScopePanel.Scope (ScopeInfo, ScopeHint), Downed (Title, TimerTrack.TimerFill, Hint). CenterDot is the old 2.2 centre dot, kept hidden. |
| XSF_Hotbar | Holder, Holder.Bar, the Slot example and InHand. |
| XSF_Backpack | Backpack.Box with Title, Help, HotbarLabel, HotRow, ListLabel, List. |
| XSF_Settings | Backdrop, Panel with Header (Logo, Title, Subtitle, Close), HeaderLine, Tabs, TabsLine, Content, Version. |
| XSF_Loadout | Backdrop, Panel with Header (Logo, Title, Prev, Next, WeaponName, WeaponClass, Close), Left (Preview, StatsTitle, Stats, Presets), Right (SlotTabs, TabsLine, Options, Status, Apply, Default), ControllerHint. |
| XSF_Death | Fade. Anything else you add fades in with it. |
Screens without a template
A few screens are built from code because they are small or appear rarely: XSF_Wheel (weapon wheel), XSF_Touch (phone buttons), XSF_Topbar (the settings button in Roblox's top bar), XSF_Pings (ping markers), XSF_Notice (the graphics notice) and XSF_TouchEditorBar. You can still restyle them the same way: press Play, find the screen in Players.YourName.PlayerGui, copy it, stop the game, paste it into ReplicatedStorage.XSF.UI and edit it. XSF uses your copy from then on. The pieces each one looks for:
| Screen | Pieces |
|---|---|
| XSF_Wheel | Wheel (CanvasGroup) with Ring (any GuiObject, optional attribute Radius in pixels; without it the ring grows with how many weapons you carry), a hidden Item example inside Ring (pieces Label, IconArea, Stroke, Grow UIScale), and Center.Name. |
| XSF_Touch | A hidden Button example (any GuiButton). Every touch button is a copy of it. |
| XSF_Topbar | Settings (the round button). |
| XSF_Pings | A hidden BillboardGui called Marker with Icon (its colour shows the kind), Label, Distance and Thumbs. |
Fail safes
- A missing element never breaks XSF. If you delete or rename something XSF needs, that piece simply doesn't show, and Output says once which element is missing, where it should be and how to bring it back, for example:
XSF_HUD.WeaponPanel.Weapon.Ammo is missing (the ammo count). XSF keeps working without it. To bring it back, add a TextLabel named Ammo inside XSF_HUD.WeaponPanel.Weapon, or copy it from the stock template in the XSF download. - The wrong kind of element (a Frame where XSF needs a TextLabel) is left alone and Output says what it needs to be.
- A missing example (say you deleted
Tile) gives a warning and XSF uses the stock look for those items. - No template at all: delete a whole ScreenGui from
XSF.UIand XSF builds the stock screen from code, exactly as it looks out of the box. - On screen in Studio: problems that matter (a missing element, a broken setting, a server warning) also appear in a small panel at the top of your screen while you test in Studio, so you don't have to watch Output. Players in live games never see it.
From your scripts
The client API has XSF.UI:
local XSF = require(game.ReplicatedStorage:WaitForChild("XSF").Client.API)
-- add your own bits to an XSF screen once it is built (and again if it is rebuilt)
XSF.UI.OnBuilt("XSF_HUD", function(hud)
local tag = Instance.new("TextLabel")
tag.Text = "SEASON 1"
tag.FontFace = XSF.UI.Fonts.Display
tag.TextColor3 = XSF.UI.Colors.Amber
tag.BackgroundTransparency = 1
tag.Size = UDim2.fromOffset(200, 24)
tag.Position = UDim2.new(0, 34, 0, 20)
tag.Parent = hud
end)
-- hide the hotbar during a cutscene, whatever XSF wants to do with it
XSF.UI.SetVisible("XSF_Hotbar", false)
task.wait(5)
XSF.UI.SetVisible("XSF_Hotbar", true)
-- the live ScreenGui, or nil if it hasn't been built yet
local settingsGui = XSF.UI.Get("XSF_Settings")| Function | What it does |
|---|---|
XSF.UI.Get(name) | The live ScreenGui of that name in the player's PlayerGui, or nil if it hasn't been built yet. |
XSF.UI.OnBuilt(name, fn) | Runs fn(screenGui) once the screen is built (straight away if it already is), and again whenever it is rebuilt. |
XSF.UI.SetVisible(name, on) | false keeps a screen hidden whatever XSF does with it, until you call it with true. |
XSF.UI.Colors, XSF.UI.Fonts | The colours and fonts in use, after the Theme and its overrides. |
Animations
The settings menu, loadout screen and backpack rise a little and grow into place while the dimmed background fades in, and do the reverse when they close. The weapon wheel pops in. Only position, scale and transparency move, so they stay smooth at any frame rate. Theme.AnimationSpeed = 0 turns them off.
Controller focus
When a controller selects a button in an XSF menu it gets one shared focus ring instead of Roblox's default box. To use your own, put a Frame called FocusRing directly in ReplicatedStorage.XSF.UI; XSF uses it on every menu. Controller button pictures (A, B, X, Y, LB, RB, triggers) come from Roblox itself, so players see the right glyphs for their own controller.
Updating later
When you update XSF, keep your edited templates: copy them out before deleting the old XSF and put them back into the new XSF.UI. New versions may add elements; anything your old template doesn't have is listed once in Output, and you can copy just those pieces from the new stock template.
Crosshair, hit markers and ammo warning #
Players tune all of this in Settings > HUD, with a live preview at the top of the tab that shows the crosshair over a light and a dark background (so they can check colour and outline) and opens and closes it in a loop when "opens up" is on.
The crosshair
It shows while you hip fire with a weapon out, and hides when you aim down the sights, when you have nothing in your hands, and when the HUD is off.
| Setting | Options | Default |
|---|---|---|
Crosshair (CrosshairStyle) | Off, Dot, Cross, Cross + dot, Circle, T (a cross without the top line) | from UI.Crosshair.Default, normally Off |
Crosshair colour (CrosshairColor) | White, Green, Cyan, Yellow, Red, Pink | White |
Crosshair length (CrosshairSize) | 2 to 24 pixels | 6 |
Crosshair gap (CrosshairGap) | 0 to 20 pixels from the centre | 4 |
Crosshair thickness (CrosshairThickness) | 1 to 6 pixels | 2 |
Crosshair opacity (CrosshairOpacity) | 0.2 to 1 | 0.9 |
Crosshair outline (CrosshairOutline) | a thin black edge so it reads on bright walls | on |
Opens up when moving and shooting (CrosshairDynamic) | the lines spread with speed (up to about 8 pixels at a sprint), 5 more in the air, 3 more per shot (up to 9), and close again when you stop | on |
XSF bullets come out of the barrel, so the crosshair is a guide to where the barrel points, not a cone of fire. With free aim on (Bodycam style) the gun can drift a little from the centre; that's why the default is Off for realistic games.
For you: UI.Crosshair = { Default = "Off", Allowed = true } in GameConfig. Default is the style new players start with ("Off", "Dot", "Cross", "Cross + dot", "Circle" or "T"). Allowed = false removes the crosshair from your game completely: the crosshair rows disappear from the menu and nothing shows whatever players had saved. Players who had the 2.2 "Centre dot" switch on get the Dot style.
Your own crosshair: the Crosshair frame in XSF_HUD holds Top, Bottom, Left, Right, Dot and Ring, which XSF sizes from the player's settings. To draw your own instead (an image, a chevron), give Crosshair the attribute Custom = true and put whatever you like inside. XSF then only shows and hides it at the right moments and never touches what's inside.
Hit markers
A small mark at the centre when your shot lands on someone. It pops out and fades in about a third of a second. Players turn it on in Settings > HUD > Hit markers; Damage.Hitmarkers in GameConfig is what new players start with.
Style (HitmarkerStyle) | Look |
|---|---|
| Cross | Four short diagonal lines (the classic). Default. |
| Small | The same, tighter and shorter. |
| Ring | A circle that pops. |
Colour tells you what happened: red for a kill (it also pops bigger), amber for a head shot, bone (off white) for any other hit. The colours follow the Theme. To draw your own, give Hitmarker in XSF_HUD the attribute Custom = true: XSF then shows it as you made it and only recolours it.
Low ammo warning
A word under the crosshair when it matters, pulsing gently:
| Text | Colour | When |
|---|---|---|
RELOAD | amber | A quarter of the magazine or less is left (at least one round) and you have spare rounds. Hidden while you reload. |
LOW AMMO | red | The same, but you have no spare rounds. |
NO AMMO | red | The magazine and your spares are both empty. |
It only shows for guns, not while jammed (the HUD says JAMMED instead) and not with the HUD off. Players turn it off in Settings > HUD > Low ammo warning; you turn it off for everyone with UI.LowAmmoWarning = false. Restyle it with the LowAmmo label in XSF_HUD.
Damage direction
When you're hit, a red mark around the centre points to where the shot came from and fades over about a second. DamageRing in XSF_HUD has a Radius attribute (pixels from the centre, 120 by default) and a hidden Marker example: restyle Marker (an image works too) and every hit uses your look.
Graphics modes #
Three modes, picked for each player's device so phones stay smooth and good PCs get everything. They only change what that one player sees. Your map is untouched for everyone else, and nothing is sent to the server.
| Mode | What's on |
|---|---|
| Full | Everything: your map's bloom, sun rays and depth of field, shadows, lens effects and film grain, every particle. |
| Balanced | No film grain and no sun rays, about 60% of the particles. Shadows and your other post effects stay. |
| Performance | No shadows, no post effects except colour correction (your map would look wrong without it), XSF's own bloom off, about 35% of the particles, no brass or muzzle smoke, fewer bullet holes. |
Auto
Every player starts on Auto. Five seconds after they first spawn (so the map has streamed in), XSF measures their frame rate for six seconds and picks a mode: 75 fps or more gets Full, 45 or more gets Balanced, anything lower gets Performance. It uses the middle value of those frames, so a single hitch can't drag a good PC down.
If the game then stays under 35 fps for 6 seconds in a row while they're alive (a big fight, a heavy part of the map), Auto drops one mode and shows a short note at the top of the screen: Graphics set to BALANCED to keep the game smooth. Change it in Settings. It waits at least 30 seconds before dropping again, and never steps back up on its own in the same session, so it can't flicker between modes.
Players
Settings > EFFECTS > Graphics: Auto, Full, Balanced or Performance. A mode they pick stays picked and Auto never changes it. Players who had the old Low effects switch on are moved to Performance.
You
| GameConfig | What it does |
|---|---|
Graphics.Default = "Auto" | Where new players start. "Full", "Balanced" or "Performance" starts everyone there instead and skips the measuring (players can still change it). |
Graphics.AdaptDown = true | Auto steps down when the frame rate stays low. false keeps the first pick for the whole session. |
Performance.LowEffects | Still works (2.2 and older): true starts players in Performance. Use Graphics.Default = "Performance" for new games. |
Grain and blur
If your game looks soft, it's usually the map's own DepthOfField in Lighting, which blurs anything past a set distance, plus the Bodycam grain. Since 2.3 XSF keeps the gun in your hands sharp in first person by turning the map's near blur off while you're alive (it goes back to your value when you die, and the far blur is never touched). Turn that off with Camera.SharpWeapon = false. The grain is lighter, redraws less often and only shows in Full. To lose the far blur too, lower FarIntensity on your DepthOfField or delete it, and players can turn off Lens and grain in EFFECTS.
Frame rate cap
XSF itself never caps the frame rate. Players set their own cap in Roblox's menu (Escape, Settings, Maximum Frame Rate), up to 240. Everything in XSF is timed by real time, so recoil, sway and animations play the same at 30, 60, 144 or 240 fps.
Ping markers #
Press middle mouse (the Ping key) to mark what you're looking at for your team, like in Fortnite or Apex. On phones it's the PING button.
| You ping | What happens |
|---|---|
| An enemy | An ENEMY marker on them. |
| Anywhere else | A spot marker on that place. |
| Your own marker | It goes away. |
| A teammate's marker | It gets a thumbs up, so they know you saw it. |
Each player has one marker at a time; a new ping replaces the old one. Markers show who placed them and how far away they are, stay for 12 seconds, and stay on screen through walls.
GameConfig
Ping = {
Enabled = true,
TeamOnly = true, -- only teammates see them (in a game without teams, only you)
RemoveOnDeath = true, -- your marker goes away when you die
Lifetime = 12, -- seconds a marker stays
MaxDistance = 1000, -- how far you can ping, in studs
},The client only says where the player looked. The server checks the spot is real and within MaxDistance of the player, limits pings to about 3 a second, and decides who sees it. When a player changes team their marker is removed for everyone. A rebind is like any other key: Keybinds.Ping = { "MouseButton3" }, and players can change it in CONTROLS.
From your scripts
local XSF = require(game.ServerScriptService.XSF.Server)
-- kind is "Spot" or "Enemy"
XSF.Pinged:Connect(function(player, position, kind)
print(player.Name, "pinged", kind, "at", position)
end)
-- clear everyone's markers at the end of a round
for _, p in ipairs(game.Players:GetPlayers()) do
XSF.ClearPing(p)
endRestyle the marker with an XSF_Pings template, see Editing XSF's screens.
Phone button layout #
Players on phones and tablets can rearrange every on screen button, like the HUD editors in PUBG Mobile and Fortnite. Open Settings > MOBILE > Move, resize and hide buttons. It also works with a mouse in Studio, so you can try it without a phone.
- Drag a button to move it. It snaps to half a percent of the screen so rows line up.
- Tap a button to select it, then use the bar at the top: SMALLER and BIGGER (50% to 250%), TURN LEFT and TURN RIGHT (15 degrees at a time), HIDE or SHOW, and RESET (that button back to where XSF puts it).
- Pinch with two fingers to resize the selected button.
- RESET ALL puts every button back. CANCEL leaves without keeping anything. SAVE keeps it.
The layout is saved with the player's settings, so it follows them to every server and every XSF game. It's applied once when it changes, never every frame, so it costs nothing while playing. A button nobody moved stays exactly where XSF puts it, so a new XSF version can still improve the default layout for those.
The buttons: FIRE (also looks while held), AIM, + and - (range, on guns with zero steps), RELOAD, JUMP (vaults in front of a ledge), CROUCH (hold for prone), SPRINT, SWAP, GEAR, PING, L and R (lean), MODE (fire mode), MENU (settings), KIT (loadout), DROP and LOOK (inspect). The hotbar stays on screen too. Each one can also be hidden with its own switch further down the MOBILE tab.
Phone extras
| Setting (MOBILE tab) | What it does | Default |
|---|---|---|
| Auto fire on enemies | Holds the trigger while the centre of the screen is on a living enemy within 600 studs (never a teammate or a downed player). Checked 20 times a second. Guns only. | off |
| Gyro aim | Turning the phone turns the view. Aiming: only while aiming down the sights. Always: all the time. Works in either landscape direction. | Off |
| Gyro sensitivity | 0.2 to 3. | 1 |
| Swipe the weapon bar to switch | Swipe left or right along the hotbar to change weapon. | on |
| Tap the ammo count to reload | Tap the ammo readout at the bottom right to reload. | on |
For you: Mobile.AutoFire = false or Mobile.Gyro = false in GameConfig takes those options away (competitive games often turn auto fire off). The HUD and buttons stay clear of notches and rounded corners (Roblox safe areas).
Restyle the buttons with an XSF_Touch template holding a hidden Button, see Editing XSF's screens.
Controllers and the weapon wheel #
Weapon wheel
Tap RB to switch to the next weapon on your hotbar, as before. Hold RB for a moment (0.28 seconds) and a wheel opens with everything you carry, hotbar and backpack. Point at a weapon with the right stick and let go of RB to take it out. Let go without pointing anywhere new and you keep what you had. On keyboard and mouse you can bind it too (Keybinds.WeaponWheel) and point with the mouse.
Your weapon stays in your hands while the wheel is open but you can't shoot, and the wheel won't open while you're downed, in a menu, or carrying fewer than two weapons. With many weapons the ring grows so the pictures never overlap.
Button pictures and menus
- The CONTROLS tab and the hints in XSF's menus show the real button pictures for the player's controller (Xbox or PlayStation), not text.
- In menus the selected button gets a clear focus ring. LB and RB change tabs in the settings menu and slots in the loadout screen. B steps back, and closes from the tab list.
- D-pad left and right change the value of the selected row (switches, choices, sliders).
Aim assist
Two parts, both controller only. Mouse and touch are never affected. Slowdown: looking slows while an enemy is close to the barrel. Pull: while you move the stick, the view eases gently toward that enemy. It never snaps or locks on, and it does nothing if you don't touch the stick. Players scale both with Settings > CAMERA > Controller aim assist (0 turns it off for them). You tune it with AimAssist in GameConfig, see AimAssist.
Settings in the top bar
On every device there's a round XSF button in Roblox's top bar, right after Roblox's own buttons, that opens the settings menu. It never covers the Roblox menu or chat buttons and moves when they do. On PC in first person, hold Left Alt to free the cursor first, same as for Roblox's own buttons. UI.TopbarButton = false removes it.
Weapon settings #
Each weapon folder in XSF.Weapons has a Config ModuleScript with that weapon's stats. You only write what's different from the defaults. Anything you leave out comes from XSF.Shared.WeaponDefaults, so a working gun Config can be five lines long.
-- HK416, 5.56. Controllable, but it gets lively on full auto.
return {
Class = "Rifle",
Fire = { RPM = 800, Modes = { "Auto", "Semi" }, MuzzleVelocity = 880, TracerEvery = 3 },
Ammo = { Magazine = 30, Reserve = 90, Chamber = true },
Damage = { Head = 150, Torso = 60, Limb = 38 },
Handling = { Mass = 3.6, AimTime = 0.3, AimFov = 60 },
SlideTravel = CFrame.new(0, 0, -0.4),
Attachments = { Sight = "EOTech", UnderBarrel = "Vertical Grip", Other = "AN PEQ" },
}Units: distances in studs, angles in degrees, bullet speed in meters per second, time in seconds, mass in kilograms.
Damage, fire rate, magazine size, spare ammo, bullet speed, aim time and overall recoil can also be changed per preset on the dashboard. Everything else is Studio only. A value you edit in a Config beats the website for that one value.
Top level
| Setting | Default | What it does |
|---|---|---|
Class | "Rifle" | The kind of weapon: "Pistol", "SMG", "Rifle", "DMR", "Shotgun", "Sniper", "Launcher", "Melee" or "Grenade". It picks the default gunshot sound and how far bullets go through walls (see Bullets and walls). Pistols also mount lights and lasers under the barrel instead of on the side. |
Kind | "Gun" | "Melee" for knives and "Grenade" for anything thrown or placed. Leave it out for guns and launchers. |
Model | the folder name | The name of the model in XSF.Assets.GunModels. Only needed when it doesn't match the folder name. |
Feed | magazine | How it reloads, when it isn't a normal magazine: "Stripper" (Kar98k, Mosin), "Revolver" (686), "Breech" (M320), "Muzzle" (RPG-7). See Reloads and feeds. |
ShellInsert | false | true loads one shell at a time, like the pump shotguns. The HUD then counts spare shells instead of magazines. |
SlideTravel | CFrame.new(0, 0, -0.4) | How far the slide, bolt or charging handle moves back when the gun cycles. Pistols use small values like -0.05. |
SlideLock | true | The slide or bolt stays back when the gun runs empty, like a real pistol. false for guns that don't do that (revolvers, some SMGs). |
ShellColor, ShellSize | brass | Colour and size of the ejected case. Shotguns use red shells. |
Attachments | {} | What the gun comes with, by slot: { Sight = "Acog", Barrel = "Suppressor" }. These are always free, even with unlocks on. See Attachments. |
Sounds | class sounds | The gun's own sounds. See Sounds below. |
Fire
| Setting | Default | What it does |
|---|---|---|
Fire.RPM | 700 | Rounds per minute. 600 is 10 shots a second. The server enforces this, so a modified client can't fire faster. |
Fire.Modes | { "Semi" } | Fire modes the player can switch between with V: "Auto", "Semi", "Burst", "Pump", "Bolt". The first one is where it starts. Pump and Bolt make the player cycle the action between shots. |
Fire.Burst | 3 | Rounds per trigger pull in Burst mode. |
Fire.MuzzleVelocity | 850 | Bullet speed in meters per second. Faster bullets drop less and arrive sooner. Pistols are around 250 to 470, rifles 700 to 970. |
Fire.Dispersion | 0.04 | Random spread in degrees for every shot, even when aiming. Snipers use about 0.008. |
Fire.Pellets | 1 | Projectiles per shot. Shotguns use 8. Damage is per pellet. |
Fire.PelletSpread | 0 | Extra spread in degrees for each pellet, added on top of Dispersion. The 870 uses 3.2. |
Fire.TracerEvery | 3 | Every Nth round is a visible tracer. 1 makes every round a tracer, 0 turns tracers off (snipers, pistols and shotguns ship with 0). |
Fire.Zero | 100 | The range in studs the sights are zeroed to. Bullets cross the line of sight at this distance. |
Fire.ZeroSteps | nil | A list of zero ranges the player can step through with ] and [, like { 100, 200, 300, 400 }. See Zeroing. |
Fire.Penetration | from Class | Overrides how much wall the bullet can go through. See Bullets and walls. |
Ammo
| Setting | Default | What it does |
|---|---|---|
Ammo.Magazine | 30 | Rounds in a full magazine (or tube, belt, cylinder). |
Ammo.Reserve | 90 | Spare rounds the player spawns with, on top of the full magazine. |
Ammo.Chamber | true | Reloading before the magazine is empty keeps one round in the chamber, so a 30 round gun holds 31. Closed bolt rifles and pistols use this. Bolt actions, revolvers and open bolt guns don't. |
Damage
| Setting | Default | What it does |
|---|---|---|
Damage.Head | 120 | Damage for a head hit. |
Damage.Torso | 45 | Damage for a hit to the upper or lower torso (or Torso on R6) and the root part. |
Damage.Limb | 30 | Damage for arms, hands, legs and feet. |
Damage.FalloffStart | 150 | Distance in studs where damage starts to drop. |
Damage.FalloffEnd | 600 | Distance where it reaches its lowest. |
Damage.MinMultiplier | 0.6 | What damage drops to at FalloffEnd and beyond. 0.6 means 60% of the base. |
Characters have 100 health by default (Roblox's normal MaxHealth), so a Torso value of 34 is a three shot kill and 50 is a two shot kill. Shotgun values are per pellet, so the 870's 28 torso with 8 pellets is 224 if every pellet lands. The whole damage chain is in How damage works.
Handling
| Setting | Default | What it does |
|---|---|---|
Handling.Mass | 3.5 | Weight in kilograms. Heavier guns swing slower, lag more when you turn and settle slower after recoil. |
Handling.AimTime | 0.28 | Seconds to raise the sights. Magnified sights and grips change this (see Attachments). |
Handling.AimFov | 60 | Field of view while aiming with iron sights or a red dot. Lower zooms in more. Magnified sights replace this with their own. |
Handling.EyeRelief | 0.85 | How far your eye sits behind the sight when aiming, in studs. Lower brings the sight closer to your face. |
Handling.Scoped | false | true makes the gun's own sight work like a scope (scope view and zoom) with no sight attachment. The AUG, SVD and M82 use this for their built in optics. |
Handling.Zoom | nil | For a gun with its own scope (Scoped = true): zoom steps the player moves through with the mouse wheel while aiming, as fields of view from wide to narrow, like { 30, 20, 13 } on the M82. Aiming starts at the step closest to AimFov. Only add steps narrower than AimFov: the scope picture is fitted at AimFov, and a wider step would need the eye closer to the sight than it can go, so the picture shrinks. A sight attachment's own Zoom wins over this. See Zeroing and special optics. |
Handling.ScopeFill | 0.8 | For scopes, how much of the screen the scope picture fills when aimed. |
Handling.HipConvergence | 60 | At the hip, the barrel is angled so bullets cross the centre of the view this many studs away. |
Handling.HipOffset | CFrame.new(0.12, -0.22, -0.55) | Where the gun sits on screen when not aiming. X is right, Y is up, negative Z is forward. |
Handling.HipPivot, Handling.AimPivot | near the grip | The point the gun turns around for sway and recoil, at the hip and when aiming. Move it back toward the stock for long guns. |
Handling.ReloadSpeed | 1 | Speed multiplier for reload animations. 0.75 is a quarter slower (the revolver), 1.2 is 20% faster. |
Handling.Inspect | generic | The gun's own inspect (I), as keyframes. See below. |
Handling.Bolt | nil | Bolt actions: where the hand grabs the bolt and how it turns and pulls. See Reloads and feeds. |
Handling.LeftHandShift | nil | Moves where the support hand grabs the gun, in studs along the gun. Vector3.new(0, 0, 0.3) moves it 0.3 back toward you, a negative Z moves it forward. Use it when the hand covers the muzzle or grabs a part it shouldn't. |
Handling.LeftHand | found automatically | false takes the support hand off the gun completely. A CFrame places it exactly, in the gun's Handle space. |
Recoil and Sway
These two tables have the most numbers and the most effect on feel, so they have their own page: Recoil and sway.
Inspect keyframes
The inspect animation is a short list of poses the gun moves through. Each keyframe has a rotation (Rot, degrees of pitch, yaw and roll), a move (Pos, studs) and how long to take getting there and holding (Hold, seconds). The last keyframe should always be all zeros, so the gun ends back where it started.
Handling = { Mass = 2.7, AimTime = 0.22, AimFov = 62,
-- the Vector is short: the support hand holds the front of the frame, behind the muzzle
LeftHandShift = Vector3.new(0, 0, 0.42),
-- inspect keyframes: turn (degrees pitch, yaw, roll), move (studs) and hold (seconds)
Inspect = {
{ Rot = Vector3.new(-8, 52, 12), Pos = Vector3.new(-0.26, 0.18, -0.42), Hold = 0.9 },
{ Rot = Vector3.new(8, 36, -6), Pos = Vector3.new(-0.22, 0.26, -0.45), Hold = 0.5 },
{ Rot = Vector3.new(14, -26, -40), Pos = Vector3.new(-0.12, 0.22, -0.36), Hold = 0.95 },
{ Rot = Vector3.new(0, 0, 0), Pos = Vector3.new(0, 0, 0), Hold = 0.4 },
},
},A good inspect is three or four keyframes: turn the gun to show its side, tilt it the other way, roll it to show the top or the ejection port, then return. Copy one from a gun of similar size and adjust. Longer guns need more negative Z in Pos so they don't clip into the camera.
Sounds
Every gun gets a gunshot from its class, plus generic handling sounds (mag out, mag in, slide, bolt, click when empty). Override any of them in Sounds.
| Key | What it is |
|---|---|
Fire | The shot. One sound id, or a list of takes like { 111, 222, 333 }. XSF picks a random take every shot, so a burst never repeats the same recording. Everyone hears it, not just the shooter. |
FireSuppressed | Replaces Fire while a suppressor is fitted. One id or a list of takes, picked at random like Fire. |
FireThird | What other players hear, if you want it different from what the shooter hears. One id or a list of takes. |
Tail | The echo XSF adds on top of the shot: outdoors for the shooter, and for anyone hearing the shot from more than 40 studs away. false turns it off for this gun, which you want when your recording already has its own echo baked in (otherwise you hear it twice). A number scales it, like 0.5 for half. Leave it out to keep XSF's. Rooms still add their own reverb either way. |
Pitch, Volume | Multiply the shot (1 is unchanged). |
Cut | Stops each take after this many seconds, for samples with a long tail. |
Fade | Seconds the shot fades out over once it reaches Cut, instead of the automatic 0.05 to 0.18. Beats Audio.CutFade in GameConfig for this gun. |
Draw, MagOut, MagIn, AimUp, AimDown, Click, SlidePull, SlideRelease, BoltOpen, BoltClose, ClipIn, CylinderOut, CylinderIn, Eject | Handling sounds. Each is a table: { Id = 123, Volume = 0.6, Pitch = 1, Cut = 0.9 }. Only Id is required. |
Sounds = {
Fire = { 9118163044, 9118163100 }, -- two takes, picked at random
FireSuppressed = 9118164000,
Pitch = 0.9, Volume = 1.1, Cut = 0.45,
Tail = false, -- these takes already have their own echo
MagIn = { Id = 873135562, Volume = 0.8, Pitch = 0.9 },
},Every gun added in 2.0 already has its own shot. Use sounds you own or free sounds from the Creator Store, otherwise Roblox may block them in your game.
Knife and grenade settings
Knives use Melee (see Knives), grenades and placed explosives use Grenade (see Grenades and Explosives), and launchers use Projectile (see Explosives).
Every weapon's stats #
Every number below is read straight from the Configs that ship with 2.3.0. Use it to balance your game, or to pick guns for a loadout without opening 59 files.
- Body shots to kill assumes 100 health, no armour, FMJ, close range (before falloff) and, for shotguns, every pellet landing.
- Falloff: "150 to 500 (x0.6)" means full damage up to 150 studs, dropping in a straight line to 60% at 500 studs, and 60% beyond that.
- Kg (mass) and aim time decide how heavy the gun feels and how fast it gets on target.
- Ships with is the attachments the gun comes with, which are always free.
Rifles and machine guns
| Name | RPM | Modes | m/s | Mag / spare | Head / body / limb | Body shots to kill | Falloff | Kg | Aim time | Ships with |
|---|---|---|---|---|---|---|---|---|---|---|
FAL | 650 | Auto, Semi | 840 | 20 / 80 | 185 / 72 / 46 | 2 | 150 to 500 (x0.6) | 4.3 | 0.3 s | nothing. Zero steps: 100, 200, 300, 400, 500, 600 |
M14 | 700 | Semi, Auto | 850 | 20 / 80 | 185 / 72 / 46 | 2 | 150 to 500 (x0.6) | 4.5 | 0.3 s | nothing. Zero steps: 100, 200, 300, 400, 500, 600 |
SCAR | 600 | Auto, Semi | 714 | 20 / 80 | 185 / 72 / 46 | 2 | 150 to 500 (x0.6) | 3.9 | 0.33 s | nothing |
AK47 | 600 | Auto, Semi | 715 | 30 / 90 | 170 / 68 / 42 | 2 | 150 to 600 (x0.6) | 3.9 | 0.32 s | nothing |
PKM | 650 | Auto | 825 | 100 / 200 | 165 / 64 / 42 | 2 | 150 to 500 (x0.6) | 7.5 | 0.45 s | nothing |
HK33 | 750 | Auto, Semi | 870 | 25 / 75 | 150 / 60 / 33 | 2 | 150 to 600 (x0.6) | 3.9 | 0.32 s | Acog |
HK416 | 800 | Auto, Semi | 880 | 30 / 90 | 150 / 60 / 38 | 2 | 150 to 600 (x0.6) | 3.6 | 0.3 s | EOTech, Vertical Grip, AN PEQ |
M4A1 | 850 | Auto, Semi | 880 | 30 / 120 | 145 / 58 / 36 | 2 | 150 to 600 (x0.6) | 3.3 | 0.28 s | EOTech, AN PEQ |
AUG | 700 | Auto, Semi | 970 | 30 / 90 | 145 / 54 / 36 | 2 | 150 to 600 (x0.6) | 3.6 | 0.3 s | nothing |
FAMAS | 1000 | Auto, Burst, Semi | 960 | 25 / 100 | 140 / 52 / 35 | 2 | 150 to 600 (x0.6) | 3.6 | 0.3 s | nothing |
G36C | 750 | Auto, Semi | 850 | 30 / 120 | 140 / 52 / 35 | 2 | 150 to 600 (x0.6) | 2.8 | 0.3 s | nothing |
GALIL | 650 | Auto, Semi | 950 | 35 / 140 | 140 / 52 / 35 | 2 | 150 to 600 (x0.6) | 4 | 0.3 s | nothing |
TAR21 | 850 | Auto, Semi | 910 | 30 / 120 | 140 / 52 / 35 | 2 | 150 to 600 (x0.6) | 3.3 | 0.3 s | Reflex |
AK12 | 650 | Auto, Burst, Semi | 880 | 30 / 120 | 138 / 50 / 34 | 2 | 150 to 600 (x0.6) | 3.3 | 0.3 s | nothing |
AKS74U | 700 | Auto, Semi | 735 | 30 / 120 | 138 / 50 / 34 | 2 | 150 to 600 (x0.6) | 2.7 | 0.3 s | nothing |
M249 | 800 | Auto | 915 | 100 / 200 | 130 / 50 / 34 | 2 | 120 to 400 (x0.6) | 7.5 | 0.45 s | Bipod |
SMGs
| Name | RPM | Modes | m/s | Mag / spare | Head / body / limb | Body shots to kill | Falloff | Kg | Aim time | Ships with |
|---|---|---|---|---|---|---|---|---|---|---|
MAC10 | 1090 | Auto | 280 | 30 / 120 | 118 / 40 / 28 | 3 | 45 to 170 (x0.6) | 2.8 | 0.2 s | nothing |
THOMPSON | 700 | Auto, Semi | 285 | 30 / 120 | 118 / 40 / 28 | 3 | 45 to 170 (x0.6) | 4.9 | 0.26 s | nothing |
UMP45 | 600 | Auto, Semi | 285 | 25 / 100 | 118 / 40 / 28 | 3 | 45 to 170 (x0.6) | 2.5 | 0.23 s | nothing |
Vector | 1100 | Auto, Semi | 290 | 25 / 100 | 110 / 38 / 26 | 3 | 50 to 180 (x0.6) | 2.7 | 0.22 s | nothing |
MP40 | 550 | Auto | 400 | 32 / 128 | 100 / 34 / 24 | 3 | 50 to 180 (x0.6) | 4 | 0.24 s | nothing |
MP5 | 800 | Auto, Semi | 400 | 30 / 120 | 100 / 34 / 24 | 3 | 60 to 200 (x0.6) | 2.6 | 0.24 s | Suppressor, Flashlight |
UZI | 600 | Auto, Semi | 400 | 32 / 128 | 100 / 34 / 24 | 3 | 50 to 180 (x0.6) | 3.5 | 0.22 s | nothing |
MP7 | 950 | Auto, Semi | 735 | 40 / 160 | 96 / 33 / 23 | 4 | 70 to 230 (x0.6) | 1.9 | 0.2 s | nothing |
P90 | 900 | Auto, Semi | 715 | 50 / 150 | 92 / 31 / 22 | 4 | 65 to 220 (x0.6) | 2.6 | 0.22 s | nothing |
BIZON | 680 | Auto, Semi | 320 | 64 / 192 | 92 / 30 / 21 | 4 | 45 to 160 (x0.6) | 2.7 | 0.22 s | nothing |
Snipers
| Name | RPM | Modes | m/s | Mag / spare | Head / body / limb | Body shots to kill | Falloff | Kg | Aim time | Ships with |
|---|---|---|---|---|---|---|---|---|---|---|
M82 | 120 | Semi | 853 | 10 / 30 | 500 / 190 / 120 | 1 | 1000 to 2500 (x0.9) | 13 | 0.6 s | Bipod. Zero steps: 100, 200, 300, 400, 600, 800, 1000, 1500 |
AWM | 40 | Bolt | 936 | 5 / 25 | 400 / 150 / 100 | 1 | 800 to 2000 (x0.85) | 6.5 | 0.5 s | PM II, Bipod. Zero steps: 100, 200, 300, 400, 600, 800, 1000 |
KAR98 | 45 | Bolt | 760 | 5 / 30 | 300 / 115 / 75 | 1 | 500 to 1200 (x0.8) | 4.1 | 0.42 s | nothing. Zero steps: 100, 200, 300, 400, 600, 800 |
MOSIN | 45 | Bolt | 865 | 5 / 30 | 300 / 112 / 74 | 1 | 500 to 1200 (x0.8) | 4.2 | 0.42 s | nothing. Zero steps: 100, 200, 300, 400, 600, 800 |
R700 | 45 | Bolt | 850 | 10 / 30 | 300 / 110 / 80 | 1 | 600 to 1500 (x0.8) | 4.4 | 0.42 s | PM II, Bipod. Zero steps: 100, 200, 300, 400, 600, 800 |
SVD | 280 | Semi | 830 | 10 / 40 | 290 / 105 / 70 | 1 | 600 to 1500 (x0.8) | 4.3 | 0.42 s | nothing. Zero steps: 100, 200, 300, 400, 600, 800, 1000 |
Shotguns
| Name | RPM | Modes | m/s | Mag / spare | Head / body / limb | Body shots to kill | Falloff | Kg | Aim time | Ships with |
|---|---|---|---|---|---|---|---|---|---|---|
DBS | 400 | Semi | 400 | 2 / 24 | 66 / 31 / 18 x8 pellets | 1 | 18 to 70 (x0.2) | 3.4 | 0.3 s | nothing |
M870 | 70 | Pump | 400 | 7 / 21 | 60 / 28 / 16 x8 pellets | 1 | 25 to 90 (x0.25) | 3.6 | 0.3 s | nothing |
AA12 | 300 | Auto | 400 | 8 / 32 | 54 / 25 / 15 x8 pellets | 1 | 22 to 80 (x0.25) | 5.2 | 0.3 s | nothing |
SPAS12 | 75 | Pump | 400 | 8 / 24 | 54 / 25 / 15 x8 pellets | 1 | 22 to 80 (x0.25) | 4.4 | 0.3 s | nothing |
M1014 | 220 | Semi | 400 | 7 / 28 | 52 / 24 / 14 x8 pellets | 1 | 22 to 80 (x0.25) | 3.8 | 0.3 s | nothing |
Pistols
| Name | RPM | Modes | m/s | Mag / spare | Head / body / limb | Body shots to kill | Falloff | Kg | Aim time | Ships with |
|---|---|---|---|---|---|---|---|---|---|---|
Deagle | 200 | Semi | 470 | 7 / 35 | 190 / 70 / 45 | 2 | 50 to 200 (x0.6) | 2.0 | 0.24 s | nothing |
R686 | 150 | Semi | 440 | 6 / 36 | 180 / 64 / 40 | 2 | 45 to 170 (x0.6) | 1.2 | 0.22 s | nothing |
M1911 | 380 | Semi | 253 | 7 / 42 | 140 / 46 / 30 | 3 | 40 to 150 (x0.6) | 1.1 | 0.2 s | nothing |
CZ75 | 460 | Semi | 370 | 16 / 64 | 100 / 34 / 24 | 3 | 50 to 180 (x0.6) | 1 | 0.2 s | nothing |
FIVESEVEN | 480 | Semi | 650 | 20 / 80 | 96 / 34 / 24 | 3 | 60 to 200 (x0.6) | 1 | 0.2 s | nothing |
M92 | 450 | Semi | 381 | 15 / 60 | 100 / 34 / 24 | 3 | 50 to 180 (x0.6) | 1 | 0.2 s | nothing |
USP | 450 | Semi | 270 | 12 / 60 | 90 / 32 / 22 | 4 | 40 to 150 (x0.6) | 0.9 | 0.18 s | nothing |
G17 | 480 | Semi | 375 | 17 / 68 | 80 / 28 / 19 | 4 | 35 to 140 (x0.6) | 0.75 | 0.16 s | nothing |
Knives
| Name | Head / body / limb | Range | Time between slashes | Hits after | Notes |
|---|---|---|---|---|---|
KUKRI | 100 / 58 / 40 | 6.2 studs | 0.85 s | 0.24 s | Chops harder than any other knife, and swings slowest. |
BOWIE | 85 / 50 / 35 | 6 studs | 0.72 s | 0.2 s | Long blade, heavy cuts. |
M9 | 78 / 47 / 33 | 5.8 studs | 0.7 s | 0.18 s | Longer reach, a little slower. |
KaBar | 75 / 45 / 32 | 5.5 studs | 0.65 s | 0.18 s | The classic fighting knife. |
Knife | 75 / 45 / 32 | 5.5 studs | 0.65 s | 0.18 s | The original XSF knife. |
Karambit | 70 / 40 / 30 | 5 studs | 0.5 s | 0.18 s | Short reach, very fast hooking cuts. |
Balisong | 65 / 38 / 28 | 5 studs | 0.45 s | 0.18 s | The fastest knife. |
Every knife does four times damage from behind, so any knife is a one hit kill in the back. See Knives.
Grenades and explosives
| Name | Type | Damage | Radius | Fuse | Carried | Notes |
|---|---|---|---|---|---|---|
M67 | Frag | 180 | 28 studs | 4.5 s, cooks in hand | 2 | Thrown at 95, lobbed at 45. |
Flashbang | Flash | none | 45 studs | 1.8 s after the throw | 2 | Blinds anyone looking toward it and deafens everyone nearby. |
M18 | Smoke | none | 22 studs | 2 s after the throw | 2 | Smoke lasts 35 seconds. |
C4 | Charge | 300 | 22 studs | when you set it off | 3 | Up to 4 out at once. |
Claymore | Mine | 240 | 20 studs, 60° fan | arms after 2 s, trips at 8 studs | 2 | Up to 2 out at once. Behind it takes 10%. |
RPG7 | Rocket | 260 blast, 120 direct | 24 studs | arms after 18 studs | 1 + 3 | Speeds up from 45 to 110 m/s. |
M320 | 40mm | 190 blast, 90 direct | 16 studs | arms after 14 studs | 1 + 6 | 76 m/s on a high arc. Range ladder 50 to 300. |
Blast damage is the most it does at the centre. It falls off with distance and stops behind cover. See Explosives.
Recoil and sway #
Recoil is what makes guns feel different more than any other setting. XSF's recoil has three separate parts, and once you know which is which, tuning a gun takes minutes instead of hours.
- The gun's kick (
Pitch,Yaw,Roll,Back,Rise,Shake): how the gun model jumps in your hands each shot. It looks violent but springs back, so it doesn't move where you're aiming. - The screen kick (
Kick): a quick jolt of the camera that also springs back. - The climb (
Climb,ClimbYaw,Recovery,Pattern): how far your aim actually moves each shot. This is what players have to pull down against, and it's what decides how controllable a gun is.
All three get stronger as the gun heats up during a long burst, and all three are scaled by stance (crouch, prone, bipod) and attachments.
Every recoil setting
| Setting | Default | What it does |
|---|---|---|
Recoil.Pitch | { 2.6, 3.4 } | How far the gun model tips up each shot, in degrees. A random number between the two. |
Recoil.Yaw | { -1.2, 1.2 } | How far it twists left or right each shot. |
Recoil.YawBias | 0.25 | Pushes the sideways kick and climb toward one side. Positive drifts right, negative left. |
Recoil.Roll | { -2.5, 2.5 } | How much it rolls on its side each shot. |
Recoil.Back | 0.16 | How far it pushes back into your shoulder, in studs. |
Recoil.Rise | 0.03 | How far it lifts up, in studs. |
Recoil.Shake | 0.9 | A random shake on top, for buzz on fast firing guns. |
Recoil.RotSpring, PosSpring, ShakeSpring | { 6.5, 0.36 }, { 9, 0.45 }, { 15, 0.2 } | The springs that bring the gun back: { speed, damping }. Higher speed snaps back faster. Lower damping bounces more, higher damping settles without bouncing. |
Recoil.Kick | 1.2 | How hard the camera jolts each shot. |
Recoil.KickSpring | { 8, 0.5 } | The spring for the camera jolt. |
Recoil.Climb | { 0.45, 0.7 } | How far your aim climbs per shot, in degrees. The number that matters most for control. |
Recoil.ClimbYaw | { -0.22, 0.28 } | How far your aim drifts sideways per shot. |
Recoil.Recovery | 0.3 | How fast the aim settles back down after you stop firing. |
Recoil.HeatPerShot | 0.07 | How much each shot heats the gun. Each point of heat adds to every part of the recoil, so shot 10 of a burst kicks harder than shot 1. |
Recoil.HeatMax | 1.6 | The most heat it can build. At max, recoil is 2.6 times the first shot. |
Recoil.HeatDecay | 2.2 | How fast heat drains when you stop firing, per second. When it's fully drained the pattern starts again from shot one. |
Recoil.Aim | { Weapon = 0.42, Shake = 0.55, Kick = 1, Climb = 0.85 } | Multipliers while aiming down sights. By default aiming cuts the visible gun kick to 42% and the climb to 85%. |
Recoil.Pattern | generated | The climb pattern, shot by shot. See below. |
Patterns
Every gun climbs in the same pattern every time, so players can learn to pull against it, like in CS. You don't have to make one. If a gun has no Pattern, XSF generates one from the gun's model name: the first three shots go straight up, then a side to side drift grows in over the next few shots and weaves from there. Two guns with different names get different patterns, and the same gun always gets the same one.
To write your own, give a list of { up, sideways } per shot. up multiplies the gun's average climb (1 is normal), sideways is a share of the gun's widest ClimbYaw (1 is all the way right, -1 all the way left). The last entry repeats for longer bursts. Three quarters of the climb follows the pattern and a quarter stays random, so it never feels robotic.
Recoil = {
Climb = { 0.5, 0.7 },
ClimbYaw = { -0.3, 0.3 },
-- up, then pull right, then snake left
Pattern = {
{ 1, 0 }, { 1.1, 0 }, { 1.1, 0.2 }, { 1, 0.5 }, { 0.9, 0.8 },
{ 0.9, 0.4 }, { 0.8, -0.2 }, { 0.8, -0.7 }, { 0.8, -0.9 }, { 0.8, -0.4 },
},
},What lowers recoil
| Source | Effect |
|---|---|
| Crouching | All recoil x0.8 (Stances.Brace.Crouch) |
| Prone | x0.62 |
| Prone with a bipod | x0.42 |
| Sliding | x1.25, so it gets worse |
| Aiming | The Recoil.Aim multipliers |
| Vertical Grip | Up kick and climb x0.88 |
| Angled Grip | Sideways kick and climb x0.88 |
| Compensator | Sideways x0.8 |
| Muzzle Brake | Up x0.85, push back x0.9 |
| Suppressor | Up x0.9, push back x0.85 |
| Subsonic ammo | Up x0.9, push back x0.9 |
| Dashboard Recoil slider | Everything times the slider value |
They all multiply together. A crouched player with a Muzzle Brake and a Vertical Grip has 0.8 x 0.85 x 0.88, about 60% of the standing climb.
Tuning a gun in five minutes
- In a Studio playtest, hold the gun and press F4.
- Fire full auto at a wall from 50 studs and watch where the bullet holes land.
- Too hard to control? Lower
Climbfirst. Too easy? Raise it. This is 80% of the work. - Drifts too much sideways? Narrow
ClimbYawor moveYawBiastoward 0. - Gets out of hand too quickly in long bursts? Lower
HeatPerShotorHeatMax. - Looks too tame? Raise
Pitch,BackandKick. These change how it looks and feels without changing how hard it is to control. - Press PRINT TO OUTPUT and paste the result into the gun's Config.
Sway
Sway is how the gun moves when you turn, walk and breathe, separate from shooting. Players can scale it with the Motion settings, you set the per gun shape here.
| Setting | Default | What it does |
|---|---|---|
Sway.InertiaMax | 7 | The most the gun can lag behind when you turn fast, in degrees. |
Sway.InertiaGain | 0.018 | How much turning speed turns into lag. |
Sway.InertiaSpring | { 4.2, 0.42 } | How the lag settles. |
Sway.InertiaRoll | 0.9 | How much the gun rolls into turns. |
Sway.LeadMax, LeadGain, LeadPeek, LeadSpring | 6, 0.03, 1.6, { 2.2, 0.85 } | The muzzle leading ahead into a turn: how far, how quickly, how much it peeks out, and how it settles. |
Sway.MoveSpring | { 5, 0.5 } | How the gun reacts to starting and stopping. |
Sway.BreathAmount | 0.35 | Breathing sway while still. Snipers use 0.5. |
Sway.AimMultiplier | 0.35 | All sway is multiplied by this while aiming. |
Heavy guns (snipers, machine guns) ship with a higher InertiaMax and InertiaGain. If a light gun feels too floaty, lower Handling.Mass first, it affects everything at once.
Adding a gun #
Adding your own gun is mostly a model and a copied folder. XSF fits the hands, the attachments and the animations to it by itself.
What the model needs
Put the model in XSF.Assets.GunModels. Inside it, XSF looks for these by name:
| Name | Type | Required? | What it's for |
|---|---|---|---|
Handle | Part | Yes | Where the right hand holds the gun. Everything is measured from it. Its front face should point down the barrel. |
AimPart | Part | Yes | Where your eye lines up when aiming with iron sights. Put it right behind the rear sight, centred on the bore line. |
Muzzle | Attachment inside Handle | Yes | Where bullets, flash and smoke come out. |
Chamber | Attachment inside Handle | No | Where the ejection port is, if you want to place it exactly. |
Mag | Part (or model) | For XSF reloads | The magazine. XSF's own reload animation grabs this part, drops it and brings a new one in. Without it the gun uses its imported reload. |
Slide or Bolt | Motor6D inside Handle | No | A Motor6D named Slide (or Bolt) that connects the Handle to the moving part. XSF drives it back when the gun cycles, and SlideTravel in the Config sets how far. |
Nodes | Folder of parts | For attachments | One part per slot the gun takes attachments in: Sight, Barrel, UnderBarrel, Other. Place each where that attachment should sit (on the rail, at the muzzle, under the handguard, on the side rail). A gun without a node for a slot simply doesn't offer that slot. |
ACS gun models already have Handle, AimPart, Muzzle and Nodes, which is why they drop in so easily.
The steps
- Put the model in
XSF.Assets.GunModels. - Copy the weapon folder in
XSF.Weaponsthat's closest to your gun (an SMG for an SMG, a bolt action for a bolt action) and rename it. The folder name is what goes inLoadout. - If the model's name is different from the folder name, add
Model = "YourModelName"to the Config. - Change the Config: damage, fire rate, magazine, mass, recoil. Delete anything you don't need, like the copied
Inspect, if it doesn't suit the new shape. - Add the folder name to
Loadoutand press Play. The hands find the grip on their own. - Press F6 to run the self test on it. It catches a missing Handle, a missing Anims module and bad Config values.
Model attributes
Set these on the gun model itself (select it, then Attributes at the bottom of Properties) when the defaults don't suit it.
| Attribute | Type | What it does |
|---|---|---|
XSF_AttFit | boolean | true seats attachments from their real shape instead of snapping their centre to the node. Sights sit on top of the rail, grips hang under it, muzzle devices thread onto the end. This is what lets one ACOG fit every gun. Turn it on for your own guns unless your nodes are already placed exactly. |
XSF_AttScale | number | Scales every attachment on this gun. Use it when your gun is built bigger or smaller than XSF's guns. 1.2 makes attachments 20% bigger. |
XSF_SightRiser | number | Extra height under sights, in studs (0.015 by default), for guns with tall front sights that would block the view. |
XSF_Class | string | Picks a matching set of attachment models from XSF.Assets.AttModels.<Class> if you make one (for example a smaller pistol suppressor in AttModels.Pistol). |
XSF_GripWrap | boolean | true closes the shooting hand into a fixed fist. See Hands and grips. |
XSF_GripReach | number | How far XSF may slide the hand looking for the grip. See Hands and grips. |
XSF_SkinKeep | boolean | true keeps your gun's own textures under a skin. See Skins on your gun below. |
XSF_SkinOnlyTagged | boolean | true means skins only paint parts with an XSF_SkinRole attribute. See below. |
And on single parts of the gun:
| Part attribute | Type | What it does |
|---|---|---|
XSF_SkinRole | string | What a skin paints this part as: Body, Furniture, Metal, Mag or Blade. None means skins never touch it. Beats the part name. |
XSF_IronSight | boolean | true marks the part as an iron sight, so it folds away when a sight is fitted (Customization.FoldIronSights). Parts named IS, ISa, IronSight or IronSights count without it. |
XSF_Fold | number | Degrees an iron sight tips forward about its bottom edge when it folds, like 90 for a flip up sight. Without it the iron sight is hidden instead. Put the part's bottom face on the hinge line for it to fold in place. |
Animations
Every weapon folder needs an Anims ModuleScript next to its Config. When you copy a folder you copy that too. With Animations = "XSF" (the default), magazine guns use XSF's own draw, reload, inspect and jam clearing, built from the gun's real Mag part, so you don't need to animate anything. Use the ACS importer if you have hand made ACS animations you'd rather keep.
Skins on your gun
Skins paint parts by name. Parts called Polymer, Wood or Leather count as furniture, Steel and Bolt as metal, Mag as the magazine, Blade as a knife blade, and BlackSteel, Slide, Stainless and Tan as the body. Anything else counts as body, except glass and neon parts, parts called Rubber and see through parts, which are left alone. See Skins.
If your parts have other names, give them an XSF_SkinRole attribute instead of renaming them. To paint only some parts (a gun where the sights, rails or a scope should never change colour), set XSF_SkinOnlyTagged = true on the model and tag just the parts that should take the skin.
Normally a skin replaces a part's look completely: its TextureID is cleared and any SurfaceAppearance is removed, so XSF's plain colours and patterns come out clean. If your gun has its own textures you want to keep (wear, engraving, a PBR finish), set XSF_SkinKeep = true on the model. Then:
- A part with a
SurfaceAppearancekeeps it, and the skin's colour tints it (itsColoris set). The material stays as you made it. - A MeshPart with a
TextureIDkeeps it, and the skin colour shows only where the texture is see through. - Pattern skins (camo and so on) lie on top as textures, over your own.
- Parts with no texture of their own are painted as normal.
local gun = game.Selection:Get()[1]
gun:SetAttribute("XSF_SkinKeep", true) -- keep its textures under skins
gun:SetAttribute("XSF_SkinOnlyTagged", true) -- only paint the parts tagged below
gun.Receiver:SetAttribute("XSF_SkinRole", "Body")
gun.Stock:SetAttribute("XSF_SkinRole", "Furniture")
gun.Magazine:SetAttribute("XSF_SkinRole", "Mag")
gun.RearSight:SetAttribute("XSF_IronSight", true)
gun.RearSight:SetAttribute("XSF_Fold", 90) -- flips down flat under an opticUse the part names of your own model. Attributes save with the model, so set them once.
Common problems
- The gun points the wrong way or sits sideways. The Handle's front face isn't pointing down the barrel. Rotate the Handle (not the whole model) so its LookVector runs along the barrel, then re-weld.
- Aiming looks off to the side. Move
AimPartso it's centred on the sights, or raise it if you're looking at the top of the gun. - Bullets come out of the wrong place. Move the
Muzzleattachment to the end of the barrel. - Attachments float or sink. Turn on
XSF_AttFit, or move the part inNodes. - The hand doesn't close on the grip. Set
XSF_GripWrap = true.
Importing ACS guns #
If you have an ACS gun with its animations, XSF can convert it into an XSF weapon folder for you, animations included. You need the optional XSF_Tools folder from the download in ServerStorage. Run it in Studio while the game is stopped, not during Play.
- Put the gun model in
XSF.Assets.GunModels. - Put its
ACS_Animationsmodule anywhere in ServerStorage. - Open the command bar (View > Command Bar) and paste this, changing the path and the name:
require(game.ServerStorage.XSF_Tools.ImportACS).weapon(game.ServerStorage.ACS_Animations, "YourGun")This creates XSF.Weapons.YourGun with the converted animations and a starting Config. Open the Config, set the real stats (damage, RPM, magazine, class) and add it to your loadout.
After importing
- With
Animations = "XSF", XSF's own reload is used for magazine guns if the model has aMagpart, and the imported clips are used for everything else. SetAnimations = "Imported"to always use the ACS clips. - ACS guns are often built at a different size. If attachments look too big or small, set
XSF_AttScaleon the model. - Delete the ACS scripts that came inside the model (anything that isn't a part, a weld or an attachment). XSF doesn't need them, and old ACS scripts can error.
- You can delete
XSF_Toolsagain when you're done. It never runs in a live game either way.
Attachments #
Guns take one attachment per slot. There are five attachment slots plus skins:
| Slot | Included |
|---|---|
| Sight | Reflex, EOTech, Aimpoint, Acog, TA33 Acog, PM II, Night Vision, Thermal |
| Barrel | Suppressor, Flash Hider, Compensator, Muzzle Brake |
| UnderBarrel | Vertical Grip, Angled Grip, Bipod |
| Other | Laser, Flashlight, AN PEQ (on pistols these mount under the barrel) |
| Ammo | FMJ, Hollow Point, Armor Piercing, Subsonic |
| Skin | 108 skins, see Skins |
What each one does
| Attachment | Slot | Effect |
|---|---|---|
| Reflex | Sight | Small open red dot. No zoom, keeps the gun's own aim speed. |
| EOTech | Sight | Holographic sight with a wide window. No zoom. |
| Aimpoint | Sight | Tube red dot. No zoom. |
| Acog | Sight | Aim FOV 22 (about 3x). Aiming takes 15% longer. |
| TA33 Acog | Sight | Aim FOV 26 (about 2.5x). Aiming takes 10% longer. |
| PM II | Sight | Aim FOV 12 (about 6x) with a full scope view. Variable: the mouse wheel steps it to 8.8x and 14x while aiming. Aiming takes 25% longer. |
| Night Vision | Sight | Aim FOV 26. Image intensifier: dark rooms and night maps turn readable in green. Aiming takes 15% longer. |
| Thermal | Sight | Aim FOV 22. White hot thermal: people glow through smoke and shadow, the world goes grey. Aiming takes 20% longer. |
| Suppressor | Barrel | Quiet shot for everyone, no muzzle flash. 10% less climb, 15% less push back, 5% less damage and bullet speed. |
| Flash Hider | Barrel | Hides the muzzle flash. No other change. |
| Compensator | Barrel | 20% less sideways recoil. |
| Muzzle Brake | Barrel | 15% less climb and 10% less push back. |
| Vertical Grip | UnderBarrel | 12% less climb. The support hand holds the grip. |
| Angled Grip | UnderBarrel | 12% less sideways recoil and 8% faster aiming. The support hand holds the grip. |
| Bipod | UnderBarrel | While prone, recoil drops to the Stances.Brace.Bipod value (0.42) instead of 0.62. |
| Laser | Other | Visible laser beam and dot, seen by everyone. Toggle with K. |
| Flashlight | Other | Weapon light, seen by everyone. Toggle with L. |
| AN PEQ | Other | Laser and light in one. |
Ammo types are in Ammo types.
Effects stack across slots by multiplying. A Muzzle Brake and a Vertical Grip together give 0.85 x 0.88 climb, so about 25% less.
Who gets which attachment
Three things decide whether a player can pick an attachment on a gun:
- The gun has to take it. It needs a node for that slot.
- Your rules allow it.
Customization.Slots,AllowedandBlockedin GameConfig. - The player owns it. With
Customization.Ownershipturned on (it is off by default since 2.3), players only get attachments you give them, plus anything inCustomization.Starter, plus whatever the gun comes with in its Config. See Unlocks.
Setting them from a script
Server scripts can fit anything with XSF.SetAttachments, skipping all of the rules above. Good for classes, events and NPCs:
local XSF = require(game.ServerScriptService.XSF.Server)
XSF.SetAttachments(player, "HK416", {
Sight = "Acog",
Barrel = "Suppressor",
UnderBarrel = "Angled Grip",
Ammo = "Armor Piercing",
})Slots you leave out of the table are emptied, so include everything you want the gun to keep.
Adding your own attachment
- Put the model in
XSF.Assets.AttModels, named exactly what it'll be called in game. It needs a part calledMain. - Sights also need
AimPos(where the eye goes) andSightMark(the reticle). Muzzle devices needBarrelPos(the new muzzle). Lights needFlashPoint, lasersLaserPoint. - Give it a line in the attachment list (the
Listtable at the top ofXSF.Shared.Attachments) with its slot and effects, the same way the built in ones are written. That module is protected, so only edit inside theListtable and keep a backup. The fields areSlot,AimFov,AimTime,Velocity,Damage,Recoil = { Vertical, Horizontal, Kick },Suppressed,NoFlash,Bipod,Laser,LightandGrip. - It shows up in the loadout screen for every gun with a matching node, and works in
SetAttachmentsand weapon Configs.
Ask in the Discord if you get stuck on step 3, it's the fiddly one.
Loadout screen #
Players press N (Y on controller, KIT on mobile) to open the loadout screen. It shows the gun they're holding, turning slowly, with every attachment that fits it. The STATS bars show the gun's real numbers measured against every gun in your game (damage, fire rate, range, bullet speed, recoil control, stability and aim speed), so players can compare guns at a glance. Hovering an option shows exactly what it changes: the part it adds turns green, the part it takes away red, and the number turns into the change in percent. Zoom, suppression, subsonic ammo, wall penetration and armour effects are listed under the bars.
- Apply fits the choice straight away, on the gun in their hands and on everyone else's screen.
- Default goes back to what the gun comes with in its Config.
- Presets: three per gun. Save the current setup into one and load it any time.
- Items the player doesn't own show as LOCKED with "Locked. This game decides how you unlock it.", so players can see what's out there to earn or buy.
- Skins have their own row, with rarity colours: grey Common, green Uncommon, blue Rare, purple Epic, gold Legendary.
- Knives show up here too, for skins. Grenades don't.
Choices and presets are saved with the player's settings, so they follow the player to every server. The server checks every change, so nothing can be fitted that the gun, your rules or the player's unlocks don't allow.
Customization = {
Enabled = true,
Slots = { Sight = true, Barrel = true, UnderBarrel = true, Other = true, Ammo = true, Skin = true },
Allowed = { HK416 = { "Acog", "Suppressor", "Vertical Grip", "Woodland" } }, -- optional, per gun
Blocked = { "PM II" }, -- optional, nobody can pick these
Ownership = { Attachments = true, Skins = true },
Starter = { "FMJ", "Reflex" }, -- free for everyone
SaveOwnership = false,
Access = "Anywhere",
},Workbenches
With Customization.Access = "Workbench", the screen only opens at a workbench while holding the gun. Copy ReplicatedStorage.XSF.Assets.Workbench into your map, as many as you like, anywhere you like. It's a good fit for milsim and survival games where changing attachments mid fight shouldn't be possible.
Turning it off
Customization.Enabled = false hides the screen and the server refuses all changes, so every gun keeps the attachments in its Config. You can still change attachments from server scripts.
Ammo types #
Ammo is picked per gun in the loadout screen, in the Ammo slot. It changes damage, wall penetration, armour and sound.
| Ammo | Damage | Walls | Armour | Other |
|---|---|---|---|---|
| FMJ | x1 | x1 | normal | The standard round. Free for everyone by default (Customization.Starter). |
| Hollow Point | x1.2 | x0.45 | armour counts 1.5 times as much | Best against unarmoured targets in the open. Bad through cover and vests. |
| Armor Piercing | x0.9 | x1.9 | ignores 60% of armour | For armoured enemies and shooting through walls. |
| Subsonic | x0.92 | x1 | normal | Bullets 28% slower (more drop), quieter and heard from much less far away, 10% less climb and push back. Best with a suppressor. |
The worked examples with armour are in How damage works.
Reloads and feeds #
Different guns reload differently in 2.0, the way the real ones do. You pick the style with Feed and ShellInsert in a Config.
Magazine guns (the default)
- Empty reload: the old magazine drops, a new one goes in, and the slide or bolt is released.
- Tactical reload (rounds still left): the old magazine goes back in the pouch instead of dropping, and with
Ammo.Chamber = truethe gun keeps one round in the chamber on top of the new magazine. - With
Animations = "XSF", the hand really grabs the gun'sMagpart. It works out the motion from where the magazine is on each gun. Handling.ReloadSpeedspeeds the animation up or slows it down.
Pump and shell by shell (ShellInsert = true)
The 870 and SPAS-12 load one shell at a time. With Modes = { "Pump" } the player pumps after every shot.
Bolt actions (Modes = { "Bolt" })
The right hand comes off the grip, lifts and pulls the bolt, then pushes it home after every shot. Handling.Bolt describes the bolt so the hand finds it:
Handling = {
-- where the right hand works the bolt (handle space), the bolt's turn and pull
Bolt = { Knob = Vector3.new(0.19, 0.27, 0.04), Axis = 0.33, Lift = 60, Travel = 0.32 },
Mass = 6.5, AimTime = 0.5, AimFov = 45,
},| Field | What it is |
|---|---|
Knob | Where the bolt handle is, relative to the gun's Handle, in studs. |
Axis | How far above the bore the bolt turns around. |
Lift | How far the handle lifts before it pulls back, in degrees. |
Travel | How far the bolt pulls back, in studs. |
Stripper clips (Feed = "Stripper")
The Kar98k and Mosin open the bolt and push five rounds down from a clip.
Revolver (Feed = "Revolver")
The 686 swings its cylinder out, punches all six cases at once and drops a speedloader in. It's slow on purpose (ReloadSpeed = 0.75), so every shot counts. The model needs a part called Mag for the cylinder.
Break open (Feed = "Breech")
The M320's barrel swings out to the side, the old case comes out and a new round goes in.
Front loaded (Feed = "Muzzle")
The RPG-7 takes a new warhead in the front of the tube. The round shows on the launcher while it's loaded.
Reloading and the server
The server tracks every reload. A reload that finishes faster than it possibly could is rejected (the Rejected event fires with the reason fast reload), so modified clients can't skip reload times.
Jams, sound and effects #
Jams
Off by default. Turn them on with Malfunctions.Enabled = true.
- Every shot has a
Malfunctions.Chanceof jamming (0.003, about one in 300). Long bursts make it more likely as the gun heats up, and so do suppressors. - A jammed gun shows JAMMED and RELOAD TO CLEAR, and won't fire.
- Reloading clears it: the player racks the gun if there are rounds left, or swaps the magazine if not.
- Jams are a great fit for milsim, a terrible fit for anything competitive. Leave them off unless your players want them.
Sound
- Distance. Shots get quieter and duller with distance, and far away shots arrive late, the way sound really travels.
- Walls. Every wall between the shot and the listener muffles it more. A firefight in the next building sounds like it.
- Indoors and outdoors sound different, so a shot inside a room doesn't ring out like one in an open field.
- Suppressors and subsonic ammo are quieter for everyone and carry much less far.
- Bullets passing close crack and whiz, and push the suppression effect on the player they passed.
- Footsteps change with the floor material (
Audio.Footsteps). - Ambience (
Audio.Ambience): quiet wind and distant traffic outdoors, room tone indoors.
Getting shot
- Players flinch when hit and stagger from heavy hits, which knocks their aim off for a moment.
- Being shot at (rounds landing or passing close) builds suppression: a bit of blur and a tighter view. Players can tone it down with the Stress effects setting.
- Low health brings in a heartbeat. The HUD shows HEALTHY, HURT, WOUNDED, BADLY WOUNDED or CRITICAL instead of a number when players check their gear.
Impacts
Bullet holes and debris match the surface: splinters from wood, sparks and bright dents on metal, shards from glass, dust from concrete and dirt kicked up from the ground. Unanchored parts get pushed by bullets and blasts, so loose crates and barrels react.
Graphics modes
The Balanced and Performance graphics modes cut particles, and Performance also drops brass and muzzle smoke and keeps fewer bullet holes. Auto picks the mode for each player's device. See Graphics modes.
Knives #
Knives are held in one hand, the way CS2 shows them. Each one has its own draw, idle, inspect and slashes.
- Left click (Fire) slashes. Slashes take turns: forehand, then backhand, then forehand again.
- I (Inspect) shows the knife off. The karambit spins on your finger, the butterfly knife flips open and shut. Attacking cuts the inspect short.
- From behind, a slash does four times the damage. It counts as from behind when the person you hit is facing away from you, within 60 degrees either side of straight away. That's a one hit kill with every knife.
- Knives only make a sound when they're swung, never when drawn or inspected.
- Knives can wear skins, picked in the loadout screen like on guns.
Knife settings
return {
Class = "Melee",
Kind = "Melee",
Melee = { Range = 5.5, Cooldown = 0.65, HitDelay = 0.18 },
Damage = { Head = 75, Torso = 45, Limb = 32 },
Handling = { Mass = 0.4 },
Ammo = { Magazine = 0, Reserve = 0, Chamber = false },
}| Setting | What it does |
|---|---|
Melee.Range | How far the slash reaches, in studs. 5 to 6.2 on the shipped knives. |
Melee.Cooldown | Seconds between slashes. Lower is faster. |
Melee.HitDelay | Seconds into the swing when the hit lands. Bigger feels heavier. |
Melee.Backstab | Damage multiplier from behind. Not written in the shipped Configs, so it's 4. Add Backstab = 2 for a weaker backstab, or Backstab = false to turn it off. |
Damage | Head, torso and limb damage, like guns. Knives have no falloff. |
All seven knives and their numbers are in Every weapon's stats. Knife damage can also be set per preset on the dashboard.
A knife only round
local XSF = require(game.ServerScriptService.XSF.Server)
local function knifeRound()
for _, player in game.Players:GetPlayers() do
XSF.SetLoadout(player, { "Karambit" })
end
endGrenades #
Hold fire to get a grenade ready and release to throw it. Hold aim as you release to lob it underhand instead, slower and shorter, for rolling it round a corner or over a low wall.
| Grenade | What it does | Fuse | Radius |
|---|---|---|---|
| M67 frag | Up to 180 damage, falling off with distance, blocked by cover. Pushes loose objects around. | 4.5 s, and it cooks: the fuse starts when you pull the pin, so holding it before throwing shortens the time the enemy has | 28 studs |
| Flashbang (M84) | Blinds anyone looking toward it and deafens everyone nearby, even through walls. No damage. | 1.8 s after it leaves your hand | 45 studs |
| M18 smoke | A thick cloud that blocks sight for 35 seconds. Thermal sights see through it. | 2 s after it leaves your hand | 22 studs |
Each grenade slot starts with two (one in hand, one in reserve). Grenades bounce off walls and roll on slopes.
Grenade settings
return {
Class = "Grenade",
Kind = "Grenade",
Grenade = { Type = "Frag", Fuse = 4.5, Radius = 28, Damage = 180, ThrowSpeed = 95, LobSpeed = 45, Cook = true },
Ammo = { Magazine = 1, Reserve = 1, Chamber = false },
Handling = { Mass = 0.4 },
}| Setting | What it does |
|---|---|
Grenade.Type | "Frag", "Flash", "Smoke", "C4" or "Claymore". |
Grenade.Fuse | Seconds until it goes off. |
Grenade.Cook | true starts the fuse when the player gets it ready, not when it's thrown. |
Grenade.Radius | Blast, flash or smoke radius in studs. |
Grenade.Damage | Damage at the centre, for frags and charges. |
Grenade.Duration | How long smoke lasts. |
Grenade.ThrowSpeed, LobSpeed | Throw speeds for an overhand throw and an underhand lob. |
Grenade.Arc, LobArc, Lift, Gravity, Bounce, Friction, Density, Spin, RollDrag | How this one flies and lands. Leave them out to use Throwables in GameConfig. For example a bouncy flashbang: Bounce = 0.5, RollDrag = 0, or a heavy frag that stops where it lands: Bounce = 0.05, RollDrag = 4. |
Ammo.Reserve | How many extra the player carries. |
Throwing grenades from a script
For traps, airstrikes and scripted events:
local XSF = require(game.ServerScriptService.XSF.Server)
-- name, start position, velocity, fuse seconds, thrower (optional, gets the kill credit)
XSF.SpawnGrenade("M67", Vector3.new(0, 50, 0), Vector3.new(0, -10, 0), 3)
-- a flashbang dropped from the ceiling when a door opens
XSF.SpawnGrenade("Flashbang", ceiling.Position, Vector3.zero, 1.2)Hotbar and backpack #
Players can carry any number of weapons. The hotbar along the bottom of the screen holds the ones on the number keys, and the backpack holds the rest.
- Number keys 1 to 5 (or up to
Backpack.HotbarSize, at most 9) switch to that slot. The mouse wheel and the controller bumpers walk through the hotbar in order. - B (Select on controller) opens the backpack. Click a weapon to take it out: it shows next to the hotbar with a B while you hold it.
- Drag a weapon onto a numbered slot to put it on the hotbar. Drag a hotbar weapon onto another slot to swap them, or back onto the list to take it off the hotbar. Hovering a weapon and pressing a number key also puts it on that slot.
- On phones and tablets the hotbar is tappable and can be hidden in the player's settings.
Backpack.Hotbar = "Fade"hides the hotbar until the player switches weapons or holds Check gear, for a cleaner screen.
When a game gives a player new weapons with XSF.SetLoadout or XSF.Give, weapons that are still carried keep their slots, and new weapons fill the free slots in order. A slot the player emptied on purpose stays empty.
Games using Providers.Inventory = "RobloxTools" keep Roblox's own backpack bar instead, and none of this applies. Each weapon becomes a Tool, so other systems that look for Tools in the backpack (like some shop kits) keep working.
Hands and grips #
With the realistic arms (Providers.Arms = "XSFRealistic"), every finger is posed against the real shape of each gun when it's drawn, so the hand closes around the grip and the index finger sits on the trigger. Knives are held in a closed fist. The support hand finds the handguard, or the grip if one is fitted.
- Sleeves take the colour of the player's shirt. Players with no shirt (or only a t-shirt) get bare arms in their own skin colour, with a smooth skin texture. Force one colour for everyone with
Arms.SleeveColor, or ignore clothing withArms.PlayerClothing = false(see Hidden options). - Gloves never change colour.
- ACS arms (
Providers.Arms = "ACSClassic") are the classic blocky arms. They don't pose fingers.
Fixing the grip on your own gun
Most guns need nothing. If the hand looks wrong on a gun you added, these fix it:
| Where | Setting | What it does |
|---|---|---|
Attribute on the gun model in Assets.GunModels | XSF_GripWrap (boolean) | true closes the shooting hand into a fixed fist. Use it when the fingers stay open, which happens when the grip has no gap behind the trigger guard. |
| Attribute on the gun model | XSF_GripReach (number) | How far, in studs, XSF may slide the hand along the gun looking for the best grip. Rifles use 0.4 and pistols 0.1. Set 0 to keep the hand exactly where the animation puts it. |
| Weapon Config | Handling.LeftHandShift | Moves the support hand along the gun, in studs. Positive Z moves it back toward you. |
| Weapon Config | Handling.LeftHand = false | One handed: the support hand stays off the gun. |
| Weapon Config | Handling.LeftHand = CFrame.new(...) | Places the support hand exactly, in the Handle's space. |
To move where the hand sits on the gun itself, move the gun model's parts (not the Handle) relative to the Handle. Moving them back, toward the player, puts the hand further forward on the gun.
How damage works #
Every shot is simulated as a real bullet, first on the shooter's screen and then again on the server, which decides what it hit. The server rewinds other players to where they were when the shot was fired (it keeps one second of history), so hits line up even with lag.
Step by step
- Body part. The part that was hit picks the zone.
Headis Head.UpperTorso,LowerTorso,Torso(R6) andHumanoidRootPartare Torso. Everything else, arms, hands, legs and feet, is Limb. - Base damage comes from the gun's
Damagetable for that zone. - Ammo and attachments. Times the ammo's damage (Hollow Point 1.2, Armor Piercing 0.9, Subsonic 0.92) and the suppressor's 0.95 if fitted.
- Falloff. Up to
FalloffStartstuds of bullet travel, damage is full. From there it drops in a straight line toMinMultiplieratFalloffEnd, and stays there. With the defaults (150, 600, 0.6), a hit at 375 studs does 80%. - Walls. A bullet that went through cover keeps a share of its damage based on how much penetration power it has left, never less than half. See Bullets and walls.
- Armour.
XSF_Armoron the character cuts Torso and Limb damage by that share.XSF_Helmetdoes the same for the Head. Armor Piercing ignores 60% of the armour, Hollow Point makes it count 1.5 times. Armour never goes above 100%. - Your modifier. If GameConfig has a
Damage.Modifyfunction, it runs now and can change the number (see below). - Downed. With
Downed.Enabled, a hit that would kill puts the player down instead. After that they can takeDownedHealthmore damage before dying, and bleed out afterBleedOutTimeseconds.
Who can't be hurt
- Teammates, while
Damage.FriendlyFireis off. That means two players on the same Team, where the attacker isn't on a Neutral team. Players with no team can always be hurt. - Anyone with a ForceField. Roblox's spawn ForceField blocks all XSF damage, so spawn protection works the normal way (SpawnLocation.Duration).
- Yourself, from bullets. Your own explosions can hurt you.
- The already dead.
Worked example
An HK416 does 60 to the torso. With Hollow Point that's 72. The target has XSF_Armor = 0.4, and hollow points make armour count 1.5 times, so it stops 60%: the hit does about 29. The same shot with Armor Piercing does 54, and the armour only counts 40% as much, stopping 16%: about 45.
Giving characters armour
-- give every player a vest and a helmet
game.Players.PlayerAdded:Connect(function(player)
player.CharacterAdded:Connect(function(char)
char:SetAttribute("XSF_Armor", 0.35) -- vest: 35% less torso and limb damage
char:SetAttribute("XSF_Helmet", 0.5) -- helmet: 50% less head damage
end)
end)The attributes go on the character model. Change them any time, for armour pickups or armour that breaks after so many hits.
Changing damage with your own rule
For rules XSF doesn't have (double damage weekends, a boss that takes half damage, headshots only), add a Modify function to the Damage table in GameConfig. It gets the Humanoid being hit, the damage after armour, and a table with attacker, zone, weapon and from (where the damage came from). Return the new amount, or 0 to cancel the hit.
Damage = {
FriendlyFire = false,
Hitmarkers = false,
Modify = function(humanoid, amount, info)
-- the boss takes half damage
if humanoid.Parent.Name == "Boss" then
return amount * 0.5
end
-- knives can't hurt anyone wearing the riot shield attribute
if humanoid.Parent:GetAttribute("RiotShield") and info.weapon == "Knife" then
return 0
end
return amount
end,
},Modify only runs on the server. Keep it quick, it runs for every hit.
Kill credit
XSF sets the classic creator ObjectValue on the victim's Humanoid to the attacking player, the same tag most old Roblox leaderboards and kill feeds look for. So older kill scripts usually work as they are. For anything new, use XSF.Killed, which gives you the attacker and the weapon directly.
Bullets and walls #
Bullets in XSF are projectiles that fly at MuzzleVelocity, fall with gravity and can go through things. Here's exactly how.
Flight
- Speed is
Fire.MuzzleVelocityin meters per second, converted to studs withBallistics.StudsPerMeter(one stud is 28 cm). An HK416 bullet at 880 m/s moves about 3,140 studs per second. - Gravity is
Ballistics.Gravity(9.81 m/s²), separate from your map'sworkspace.Gravity. - Each shot gets a random spread of
Fire.Dispersiondegrees (plusPelletSpreadfor shotgun pellets). - Bullets live for
Ballistics.MaxLifeseconds (3) or until they fall below the map. - Over long range, slower bullets visibly drop and take time to arrive. Lead moving targets and use the zero on snipers.
Going through walls
Every bullet starts with a penetration power that comes from its class, times its ammo:
| Class | Power |
|---|---|
| Shotgun (per pellet) | 0.35 |
| Pistol | 0.45 |
| SMG | 0.55 |
| Rifle | 1.3 |
| DMR | 1.8 |
| Sniper | 2.4 |
| Anything else | 1 |
Armor Piercing multiplies it by 1.9, Hollow Point by 0.45. You can set any gun's power directly with Fire.Penetration.
When a bullet hits something, XSF measures how thick it is along the bullet's path (up to 6 studs) and multiplies that by the material's cost:
| Material | Cost per stud |
|---|---|
| ForceField | 0 |
| Glass | 0.15 |
| Fabric | 0.2 |
| Cardboard | 0.25 |
| Foil | 0.4 |
| Plastic, SmoothPlastic | 0.55 |
| Wood, WoodPlanks | 0.6 |
| Ice | 0.9 |
| Brick | 2.4 |
| CorrodedMetal | 2.6 |
| Concrete | 2.8 |
| Grass, Cobblestone | 3 |
| Metal, Slate | 3.2 |
| Granite, Marble | 3.5 |
| Sand, Ground, Rock, DiamondPlate | 4 |
| Anything else | 2 |
If the cost is less than the bullet's remaining power, it goes through, loses that much power and slows down. If not, it stops inside. Parts thicker than 6 studs always stop bullets.
Some quick results with the defaults: a rifle (1.3) goes through about 2 studs of wood but less than half a stud of concrete. A pistol (0.45) gets through 0.75 studs of wood and no real concrete. An AWM (2.4) goes through 4 studs of wood or 0.85 studs of concrete. Glass barely slows anything.
Making a wall bulletproof (or paper thin)
Set the XSF_PenetrationCost attribute (a number) on any part to override its material. 100 makes it bulletproof, 0 makes bullets ignore it entirely. Handy for a glass window that should stop bullets, or a thick prop that should be shoot through.
-- every part in the Bunker model stops bullets
for _, p in workspace.Bunker:GetDescendants() do
if p:IsA("BasePart") then p:SetAttribute("XSF_PenetrationCost", 100) end
endDamage after a wall
A bullet that went through something does its damage times its remaining power share, but never less than half. A rifle round that spent most of its power on a wooden wall does about 50 to 60% on the other side. Glass costs so little that it barely matters.
Ricochets
A bullet that hits a hard surface (anything costing 2.4 or more: brick, concrete, metal, rock) at a shallow angle, less than about 13 degrees from the surface, skips off once instead of stopping. It keeps 45% of its speed and 40% of its power. You'll hear the ricochet whine. Each bullet only ricochets once.
Loose parts
Unanchored parts under 1,500 mass get a push when a bullet hits them, so crates, cans and barrels react to being shot. Characters aren't pushed by bullets.
Explosives #
Four weapons blow things up. Add them to Loadout like any other weapon: "RPG7", "M320", "C4" and "Claymore".
| Weapon | How it works |
|---|---|
| RPG-7 | The rocket leaves the tube slowly (45 m/s), the motor lights after a moment and it speeds up to 110 m/s. There's a back blast behind the shooter, so don't stand behind one. It goes off on anything it hits, even at point blank, so firing it at your own feet hurts you. It blasts for up to 260 in 24 studs, and a direct hit adds 120. Reloading pushes a new warhead into the muzzle. One loaded plus three spare. |
| M320 | Fires a 40mm grenade on a real arc at 76 m/s. Change the range step with ] and [, the same keys as zeroing, from 50 to 300 studs. It goes off on whatever it hits, even point blank, blasting for up to 190 in 16 studs, and a direct hit adds 90. The barrel swings open to reload. One loaded plus six spare. |
| C4 | Throw it and it sticks flat to whatever it hits. Right click sets off every charge you have out, for up to 300 in 22 studs each. You can have four out at once. When you have none left in hand, the clacker stays out and left click works too. Three charges per spawn. |
| Claymore | Set it down facing where you look. It arms after 2 seconds and fires a 60 degree fan at the first enemy who walks within 8 studs in front of it, for up to 240. Standing behind one is mostly safe (10% damage). Two out at once. |
How blast damage works
- XSF checks the head and torso parts of everyone in the radius and uses whichever is most exposed.
- Damage falls off with distance on a curve: full at the centre, about two thirds at a quarter of the way out, a bit under 40% halfway out, and nothing at the edge.
- Cover blocks it. If something solid is between the blast and you, you take nothing, unless you're very close (inside a quarter of the radius), where you still take 35%.
- Directional blasts (the claymore) only do full damage inside their cone. Behind is
Backtimes the damage. - Loose parts in the blast get thrown around.
- Friendly fire rules apply. Your own explosions can always hurt you.
Explosive settings
Class = "Launcher",
Feed = "Muzzle",
Fire = { RPM = 30, Modes = { "Semi" }, MuzzleVelocity = 115, Zero = 150, TracerEvery = 0 },
Ammo = { Magazine = 1, Reserve = 3, Chamber = false },
-- Speed and MaxSpeed in m/s, Arm and Radius in studs. Arm is how far it
-- flies before it can go off (closer, only the direct hit hurts). 0 means it
-- goes off on anything, so firing at your own feet hurts you
Projectile = { Speed = 45, MaxSpeed = 110, Ignite = 0.12, Burn = 0.7, Drop = 0.12, Life = 5,
Arm = 0, Radius = 24, Damage = 260, Impact = 120, Blast = "HE", ShowRound = true },| Projectile field | What it does |
|---|---|
Speed | Launch speed in m/s. |
MaxSpeed, Ignite, Burn | For rockets: the motor lights after Ignite seconds and burns for Burn seconds, speeding up to MaxSpeed. |
Drop | How much gravity affects it, 1 is full gravity. The RPG uses 0.12, the M320 1. |
Life | Seconds before it self destructs in the air. |
Arm | Studs it must travel before it can explode. Hitting something closer is a dud: no blast, only the direct hit (Impact) hurts. The RPG-7 and M320 ship with 0, so they go off point blank and can hurt the shooter. Raise it (the real RPG-7 arms after about 18 studs) if you don't want rockets fired at close range. |
Radius, Damage | Blast radius and damage at the centre. |
Impact | Damage of a direct hit, armed or not. |
ShowRound | Shows the round sitting in the launcher while loaded. |
Model | The projectile model in XSF.Assets.Projectiles. |
C4 and claymore use Grenade settings: Radius, Damage, MaxPlaced (how many can be out at once), and for the claymore Cone, Back, Trigger (trip distance), TriggerCone and Arm (seconds).
Your own explosions
From any server script, for barrels, mines, airstrikes or a car blowing up:
local XSF = require(game.ServerScriptService.XSF.Server)
-- position, settings, attacker (optional, gets kill credit), weapon name (optional, for the kill feed)
XSF.Explode(barrel.Position, { Radius = 20, Damage = 200 }, attackerPlayer, "Barrel")
-- a shaped charge that only blasts forward
XSF.Explode(door.Position, { Radius = 15, Damage = 250, Cone = 70, Back = 0.1 })XSF.Exploded fires for every explosion, with (kind, position, attacker, weaponName). kind is "Frag", "HE" (launchers, C4 and your own calls), "Claymore", "Flash" or "Smoke".
Pickups #
Guns can be dropped and picked up. A dropped gun keeps its attachments, skin and ammo, so picking up someone's gun gives you exactly what they had.
- Drop: X drops the gun in your hands in front of you (
Keybinds.Drop, see Hidden options). - Pick up: walk within 8 studs and a Pick up prompt appears. Press F (Y on controller).
- Already carrying that gun? Picking up another one just takes its ammo into your spare rounds.
- On death the gun in your hands drops where you fell (
Pickups.DropOnDeath). - Clean up: dropped guns disappear after 90 seconds (
Pickups.Lifetime). - Carry limit: with
Pickups.MaxWeaponsset, a full player swaps the gun in their hands for the one on the floor. - Downed players can't pick things up.
All of this can be changed with the Pickups table, see Hidden options.
Spawning guns and ammo from a script
local XSF = require(game.ServerScriptService.XSF.Server)
-- a gun with its own ammo and an ACOG on it
XSF.SpawnPickup("AK47", spot.CFrame, { mag = 30, reserve = 60, atts = { Sight = "Acog" } })
-- an ammo box: each gun gets up to 3 magazines, and the box can be used 4 times
XSF.SpawnPickup("Ammo", crate.CFrame, { Mags = 3, Uses = 4 })
-- a box that never runs out (a resupply point)
XSF.SpawnPickup("Ammo", base.CFrame, { Mags = 2, Infinite = true })| Option | For | What it does |
|---|---|---|
mag | guns | Rounds in the magazine. Defaults to a full magazine. |
reserve | guns | Spare rounds. Defaults to the gun's spawn reserve. |
atts | guns | Attachments and skin, like { Sight = "Acog", Skin = "Woodland" }. Defaults to the gun's own attachments from its Config. If you pass a table, it replaces them. |
Mags | ammo | Magazines given to each gun per use. Default 2. |
Uses | ammo | How many times the box can be used. Default 1. |
Infinite | ammo | true never runs out. |
The position can be a CFrame or a Vector3. SpawnPickup returns the pickup model if you want to move or delete it later.
An ammo box tops up every gun the player carries (not knives or grenades) by up to Mags magazines, but never past what the gun spawns with. A use only counts if it actually gave someone ammo, so a full player walking past doesn't waste a box.
Events
XSF.PickedUp:Connect(function(player, weaponName, model)
print(player.Name, "picked up", weaponName) -- weaponName is "Ammo" for ammo boxes
end)
XSF.Dropped:Connect(function(player, weaponName, model)
-- player is nil for pickups spawned by a script
end)Zeroing and special optics #
Zeroing
Long guns can be zeroed. Bullets drop, so a sight can only be exactly right at one distance. Zeroing picks that distance. Press ] to raise the zero and [ to lower it (ZeroUp and ZeroDown in Keybinds). The HUD shows ZERO and the current range. Set it to the target's distance and the bullet lands where the reticle is.
| Gun | Zero steps (studs) |
|---|---|
| AWM, SVD | 100, 200, 300, 400, 600, 800, 1000 |
| M82 | 100, 200, 300, 400, 600, 800, 1000, 1500 |
| R700, Kar98k, Mosin | 100, 200, 300, 400, 600, 800 |
| FAL, M14 | 100, 200, 300, 400, 500, 600 |
| M320 (range ladder) | 50, 100, 150, 200, 250, 300 |
Add zero steps to any gun with Fire.ZeroSteps and set where it starts with Fire.Zero. Guns without steps are zeroed at Fire.Zero (100 studs) and can't be changed.
Fire = { RPM = 700, Modes = { "Semi" }, MuzzleVelocity = 850, Zero = 200, ZeroSteps = { 100, 200, 300, 400, 600 } },Scope zoom
Variable scopes zoom in and out while you aim: mouse wheel up zooms in and wheel down zooms out. There are no keys by default; bind ZoomIn and ZoomOut in Keybinds or the settings menu, for controllers too. The scope readout shows the magnification next to ZERO and RANGE. Aiming sensitivity follows the zoom like it does for every sight. When you aren't aiming, the wheel changes weapon as before.
| Scope | Steps |
|---|---|
| PM II (R700, AWM, or on any gun) | 5.8x (where it starts), 8.8x, 14x |
| M82's own scope | 2.3x (where it starts), 3.5x, 5.4x |
The scope picture keeps the same size on screen at every step: your eye moves along the tube as you zoom, like a real second focal plane scope. Give your own scopes steps with Zoom, a list of fields of view from wide to narrow. On a sight attachment it goes in its entry in Shared.Attachments; on a gun with its own scope it's Handling.Zoom in the Config. Aiming starts at the step closest to AimFov. Only add steps narrower than AimFov. A wider step would need your eye closer to the sight than it can go, so the scope picture shrinks and the tube shows around it.
Handling = { AimFov = 30, Zoom = { 30, 20, 13 }, Scoped = true, ScopeFill = 0.82 },Night vision and thermal
Two sights change what you see through them:
- Night Vision: an image intensifier. Dark rooms and night maps turn bright green and readable.
- Thermal: white hot. People and NPCs glow bright through smoke and shadow while the world goes grey.
They aren't given out by default. Give them to players like any other unlock (XSF.GrantAttachment(player, "Thermal")), or put them in Customization.Starter to make them free for everyone. Thermal through smoke is strong, so many games keep it rare or expensive.
Vault and mantle #
Run at a low wall and jump to vault over it. Jump at a ledge you can reach and you climb up onto it. The left hand plants on the edge and the camera dips, so it feels physical. On a thin wall you land on the middle of the top, not the far edge. There's nothing to set up: it works on any part in your map.
- Obstacles up to
Vault.VaultHeight(3.6 studs) are vaulted over, and you land on the far side. - Taller ones, up to
Vault.MaxHeight(7 studs), are climbed onto. - XSF looks for something to vault within
Vault.Reach(3.2 studs) in front of you. - Downed players can't vault.
None of these are in GameConfig by default. Add a Vault table to change them, see Hidden options.
-- parkour game: climb higher
Vault = { MaxHeight = 10 },
-- stop players getting onto roofs
Vault = { MaxHeight = 4.5 },
-- no vaulting at all
Vault = { Enabled = false },To stop players climbing one specific thing (an invisible wall, the edge of the map), make it taller than MaxHeight. A tall transparent part with CanCollide on does it.
Skins #
XSF ships with 108 skins that work on every gun and knife, including guns you add yourself. Players put them on in the loadout screen, in the Skin row.
Rarities
| Rarity | Colour | How many | Examples |
|---|---|---|---|
| Common | grey | 18 | Matte Black, Olive Drab, Flat Dark Earth, Woodland, Desert Storm, Arctic, Worn Steel |
| Uncommon | green | 28 | Two Tone Tan, Gunmetal, Tiger Stripe, Digital Woodland, Flecktarn, Rust |
| Rare | blue | 25 | Crimson, Cobalt, Copper, Carbon Fiber, Python, Topographic |
| Epic | purple | 25 | Chrome, Neon Cyan, Plasma, Damascus, Circuit Board, Magma |
| Legendary | gold | 12 | Gold Plated, Black Gold, Hologram, Galaxy, Aurora, Dragon Scale |
XSF.Skins on the server is the full list, keyed by name, with each skin's Rarity and Info. Use it to build a shop or a crate preview:
local XSF = require(game.ServerScriptService.XSF.Server)
for name, skin in pairs(XSF.Skins) do
print(name, skin.Rarity, skin.Info)
endHow skins paint a gun
A skin colours each part of the gun by its role: body, furniture (stocks and grips), metal, magazine and blade. Your own guns can keep their own textures under a skin and choose which parts are painted, see Skins on your gun. Solid skins set colours and materials. Camo and pattern skins (Woodland, Galaxy, Damascus and so on) add a texture on top, scaled to the size of the gun. Attachments keep their own look. To make your own guns take skins nicely, name the parts the way Adding a gun describes.
Who gets which skin
By default (since 2.3) every player has every skin. Turn Customization.Ownership.Skins on and players only have the skins you give them. Skins are given and checked the same way as attachments, see Unlocks.
Crates
XSF.RollSkin picks a random skin by rarity weights. Leave the weights out and it uses 55 Common, 25 Uncommon, 12 Rare, 6 Epic, 2 Legendary. Only rarities you list can come up.
local XSF = require(game.ServerScriptService.XSF.Server)
local function openCrate(player)
local skin = XSF.RollSkin({ Common = 60, Uncommon = 25, Rare = 10, Epic = 4, Legendary = 1 })
XSF.GrantSkin(player, skin)
return skin -- show it in your crate opening UI
end
-- a premium crate that never gives Commons
local function premiumCrate(player)
local skin = XSF.RollSkin({ Rare = 70, Epic = 25, Legendary = 5 })
XSF.GrantSkin(player, skin)
return skin
endPutting a skin on for the player
-- after a purchase, equip it straight away on the gun they're carrying
XSF.GrantSkin(player, "Galaxy")
XSF.EquipSkin(player, "AK47", "Galaxy")EquipSkin returns false if the player doesn't own the skin or isn't carrying that weapon. Pass nil as the skin to take it off.
Unlocks #
By default every player can use every attachment and skin. Turn ownership on when you want players to only get what your game gives them, so you can sell them, put them in crates or tie them to levels. A gun's own default attachments (from its Config) and anything in Customization.Starter are always free.
Turning it on and off
Customization = {
-- everything free for everyone (the default)
Ownership = { Attachments = false, Skins = false },
-- everyone gets every attachment, skins have to be earned
-- Ownership = { Attachments = false, Skins = true },
-- players only get what you give them
-- Ownership = { Attachments = true, Skins = true },
Starter = { "FMJ", "Reflex", "Woodland" }, -- free for everyone, on every gun
-- Starter = { "FMJ", "AK47:Acog" }, -- "Gun:Item" frees one item on one gun
},Giving and taking
| Function | What it does |
|---|---|
XSF.GrantAttachment(player, name, weapon?) | Unlocks an attachment. Leave weapon out to unlock it on every gun, or name one gun ("AK47"). |
XSF.RevokeAttachment(player, name, weapon?) | Takes it away again. A gun using it goes back to its default straight away. |
XSF.OwnsAttachment(player, name, weapon?) | true if the player has it (including through Starter or as a gun default). |
XSF.GrantSkin, XSF.RevokeSkin, XSF.OwnsSkin | The same three, for skins. |
XSF.EquipSkin(player, weapon, skin) | Puts an owned skin on a gun the player is carrying. |
XSF.RollSkin(weights?) | Picks a random skin by rarity, for crates. See Skins. |
XSF.GetOwned(player) | Everything a player owns as one table: { Attachments = { ... }, Skins = { ... } }, each a list of names (or "Gun:Name" for per gun unlocks). |
XSF.SetOwned(player, data) | Replaces everything they own with that table. Use it to load what you saved. |
XSF.OwnershipChanged | Fires when a player gets or loses an item. |
A misspelled name prints [XSF] there is no attachment called "X" (or skin) and does nothing. Granting "*" unlocks every item of that kind.
Saving unlocks
Two choices:
- Let XSF do it. Set
Customization.SaveOwnership = true. XSF saves to its own DataStore (named byCustomization.SaveKey,"XSF_Ownership"by default) when unlocks change and loads it when the player joins. ChangeSaveKeyto start everyone fresh. - Do it yourself. Leave it off and save
XSF.GetOwned(player)in your own DataStore with the rest of your player data, thenXSF.SetOwnedwhen they join. Do this if you already have a data system (ProfileService, ProfileStore, DataStore2), so everything saves and rolls back together.
local DataStoreService = game:GetService("DataStoreService")
local XSF = require(game.ServerScriptService.XSF.Server)
local store = DataStoreService:GetDataStore("PlayerUnlocks")
game.Players.PlayerAdded:Connect(function(player)
local ok, data = pcall(store.GetAsync, store, "u_" .. player.UserId)
if ok and data then
XSF.SetOwned(player, data)
end
end)
game.Players.PlayerRemoving:Connect(function(player)
pcall(store.SetAsync, store, "u_" .. player.UserId, XSF.GetOwned(player))
end)Ideas
- Leveling: grant attachments at certain levels. There's a full example in Recipes.
- Gamepass: check
MarketplaceService:UserOwnsGamePassAsyncon join and grant a set of skins. - Developer products: grant a crate roll in
ProcessReceipt. - Kill streaks: grant the Thermal sight for the rest of the round after 5 kills, and revoke it on death.
Unlocks are stored on the player as the XSF_Owned attribute (JSON), so LocalScripts can read what a player owns for your own shop UI. Never trust it for purchases, always grant from the server.
Server scripts #
Your own server Scripts talk to XSF through one module. Require it at the top of any Script in ServerScriptService. Everything here is checked and replicated for you: a weapon you give shows up for the player, and damage you deal counts for downing, armour and kill credit exactly like a bullet.
local XSF = require(game.ServerScriptService.XSF.Server)
print("Running XSF " .. XSF.Version)Only require it from the server. A LocalScript can't see ServerScriptService. LocalScripts use the client API.
Values
| Name | What it is |
|---|---|
XSF.Version | The version string, like "2.3.0". |
XSF.Config | Your GameConfig as XSF sees it, with defaults and website values filled in. Read it, don't change it at runtime. |
XSF.Attachments | Every attachment and its effects, keyed by name: XSF.Attachments["Acog"].AimFov is 22. |
XSF.Skins | Every skin, keyed by name, with Rarity and Info. |
Weapons
XSF.SetLoadout(player, names)
Replaces everything the player is carrying with this list, in slot order, each with its spawn ammo and the player's saved attachments. Call it after the character has spawned.
| Argument | What to pass |
|---|---|
player | The Player. |
names | A list of weapon folder names, like { "MP5", "USP" }. Unknown names are skipped with a message in Output. An empty list {} takes everything away. |
game.Players.PlayerAdded:Connect(function(player)
player.CharacterAdded:Connect(function()
task.wait() -- let XSF set up the character first
if player.Team and player.Team.Name == "Snipers" then
XSF.SetLoadout(player, { "R700", "USP", "M18" })
else
XSF.SetLoadout(player, { "HK416", "USP", "M67" })
end
end)
end)The task.wait() matters. XSF hands out the normal loadout when the character spawns, and without the wait your loadout can arrive first and get replaced. If you always give weapons yourself, add GiveLoadout = false to GameConfig and you can skip the wait.
XSF.Give(player, name)
Adds one weapon after the player's last slot, with full ammo. Giving a weapon they already carry gives them a second copy, so check first if that matters.
-- a pad that gives each player one shotgun per life
local given = {}
script.Parent.Touched:Connect(function(hit)
local player = game.Players:GetPlayerFromCharacter(hit.Parent)
if player and given[player] ~= player.Character then
given[player] = player.Character
XSF.Give(player, "M870")
end
end)For a pickup that players can see and press, XSF.SpawnPickup is usually nicer, see Pickups.
XSF.Remove(player, name)
Takes one weapon away (the first copy if they have two). If it was in their hands, their hands are left empty.
XSF.AddAmmo(player, name, amount)
Adds spare ammo, not to the magazine.
| Argument | What to pass |
|---|---|
player | The Player. |
name | Weapon name, or nil for the gun in their hands. |
amount | Rounds to add. |
-- an ammo crate: 60 rounds for whatever they are holding
XSF.AddAmmo(player, nil, 60)XSF.GetEquipped(player)
Returns the name of the weapon the player is holding, or nil if their hands are empty (including when they holstered).
XSF.GetInventory(player), XSF.GetAmmo(player, weapon), XSF.GetAttachments(player, weapon)
Read what a player carries, as the server sees it. GetInventory returns weapon names in slot order. GetAmmo returns two numbers, the magazine and the spare rounds, or nil if they don't carry that weapon. GetAttachments returns a copy of that gun's kit (slot to name), or nil.
-- a shop that refills whatever runs low
for _, name in ipairs(XSF.GetInventory(player)) do
local mag, reserve = XSF.GetAmmo(player, name)
if reserve and reserve < 30 then XSF.AddAmmo(player, name, 60) end
endXSF.SetAttachments(player, weapon, attachments)
Changes a gun's attachments, ammo and skin. Everyone sees the change, and the gun in their hands updates straight away. This skips the loadout screen rules and unlocks, so you can fit things players can't pick themselves.
| Argument | What to pass |
|---|---|
player | The Player. |
weapon | Weapon name, or its slot number. |
attachments | A table of slot to name. Slots you leave out are emptied, and names that don't exist or are in the wrong slot are ignored. |
Returns: true if the player carries that weapon, false if not.
XSF.SetAttachments(player, "HK416", {
Sight = "Acog",
Barrel = "Suppressor",
UnderBarrel = "Angled Grip",
Ammo = "Armor Piercing",
Skin = "Woodland",
})XSF.SetCustomizeCheck(fn)
Lets your game refuse a loadout change, for example once a round has started or outside a safe zone. fn(player, weapon, kit) runs on the server every time a player applies a change in the loadout screen (or with SetKit from the client API), after XSF's own checks (the attachment fits, it's unlocked, Customization.Required). Return false to refuse it, with a second value as the reason the player sees. Return anything else (or nothing) to allow it.
| Argument | What it is |
|---|---|
player | The Player. |
weapon | The gun's name. |
kit | The whole new setup as slot to name, like { Sight = "Acog", Ammo = "FMJ" }. Empty when the player took everything off. It's a copy, so changing it does nothing. |
There is one check at a time; calling it again replaces the old one, and XSF.SetCustomizeCheck(nil) removes it. If your function errors, the change is refused, the player sees "Not allowed right now." and the error is printed as a warning. Don't yield in it for long; the player waits for the answer. It doesn't stop XSF.SetAttachments from your own scripts.
local XSF = require(game.ServerScriptService.XSF.Server)
-- no loadout changes once the round is live, and no thermals in this mode
local roundLive = workspace:GetAttribute("RoundLive")
workspace:GetAttributeChangedSignal("RoundLive"):Connect(function()
roundLive = workspace:GetAttribute("RoundLive")
end)
XSF.SetCustomizeCheck(function(player, weapon, kit)
if roundLive then
return false, "You can change attachments between rounds."
end
if kit.Sight == "Thermal" then
return false, "Thermal sights are off in this mode."
end
end)XSF.MountWeapon(model, name, attachments)
Puts a gun in the hands of any character model, for NPCs or display. For NPCs, setting the XSF_Weapon attribute is usually easier (see NPCs with guns).
Damage and downed
XSF.Damage(humanoid, amount, attacker, zone, options)
Hurts a character the same way a bullet would. Armour, downing, hit markers, kill credit, Damage.Modify and the Damaged and Killed events all work.
| Argument | What to pass |
|---|---|
humanoid | The Humanoid to hurt. |
amount | Damage before armour. |
attacker | Optional Player who gets the credit. Friendly fire rules use it. |
zone | Optional "Head", "Torso" or "Limb". Decides which armour applies. Defaults to Torso. |
options | Optional table. from is a Vector3 the hit came from, so the victim's red hit marker points at it (an aircraft, a turret, a mortar). weapon is a name passed on to Damaged, Killed, PlayerDowned and Damage.Modify, for kill feeds. |
Returns: the damage actually dealt after armour, or 0 if it was blocked (teammate, ForceField, already dead).
-- a killstreak gunship: the hit comes from the sky and shows in the kill feed
XSF.Damage(hum, 120, owner, "Torso", { from = gunship.Position, weapon = "AC-130" })-- a lava floor
script.Parent.Touched:Connect(function(hit)
local hum = hit.Parent:FindFirstChildOfClass("Humanoid")
if hum then XSF.Damage(hum, 15) end
end)XSF.IsDowned(humanoid)
Returns true if someone is down and waiting for a revive.
XSF.Revive(humanoid, by)
Gets a downed character back up at Downed.ReviveHealth. by is the optional Player who revived them, passed on to the Revived event.
Grenades, explosions and pickups
XSF.SpawnGrenade(name, position, velocity, fuse, thrower)
| Argument | What to pass |
|---|---|
name | "M67" (frag), "M18" (smoke) or "Flashbang". |
position | Where it starts, a Vector3. |
velocity | Its starting speed and direction, a Vector3. Vector3.zero just drops it. |
fuse | Seconds until it goes off. |
thrower | Optional Player who gets credit for kills. |
XSF.Explode(position, settings, attacker, weapon)
An explosion anywhere. settings takes Radius (default 20), Damage (default 150), and optionally Cone (degrees) and Back (damage share behind the cone) for directional blasts. attacker and weapon are optional, for kill credit and kill feeds. See Explosives.
XSF.SpawnPickup(name, where, options)
Spawns a gun or an ammo box (name = "Ammo") at a CFrame or Vector3. Returns the pickup model. See Pickups for the options.
XSF.GetSuspicion(player), XSF.ResetSuspicion(player), XSF.AllowMovement(player, seconds)
Read or clear a player's anti-cheat score, and let a player move in a way XSF can't see from the server (a launch pad, a client driven vehicle) without being reported. See Anti-cheat and suspicion.
XSF.ClearPing(player)
Takes a player's ping marker away for everyone, for example at the end of a round. See Ping markers.
Unlocks and skins
GrantAttachment, RevokeAttachment, OwnsAttachment, GrantSkin, RevokeSkin, OwnsSkin, EquipSkin, RollSkin, GetOwned and SetOwned are all covered in Unlocks and Skins.
Events
Connect with XSF.EventName:Connect(function(...) end). They all fire on the server.
| Event | Arguments | When it fires |
|---|---|---|
Fired | player, weaponName, origin, direction | Every shot the server accepts. |
Damaged | humanoid, attacker, amount, zone, weaponName | Every time XSF damages a character. attacker is a Player or nil, zone is Head, Torso or Limb, amount is after armour. |
Killed | humanoid, attacker, weaponName | When a character dies to XSF damage, including bleeding out after being downed by someone. |
PlayerDowned | humanoid, info | When someone goes down. info has attacker, zone, weapon and from. |
Revived | humanoid, byPlayer | When a downed character is revived. byPlayer is nil for scripted revives without one. |
BledOut | humanoid | When a downed character runs out of time or gives up. |
Exploded | kind, position, attacker, weaponName | Every explosion, flash and smoke. kind is Frag, HE, Claymore, Flash or Smoke. |
PickedUp | player, weaponName, model | Someone picked up a gun, or took ammo (weaponName is "Ammo"). |
Dropped | player, weaponName, model | A gun was dropped. player is nil for scripted spawns. |
OwnershipChanged | player | A player got or lost an attachment or skin. Call GetOwned to see what they have now. |
Equipped | player, weaponName, slot | A player took a weapon out. |
SuspicionChanged | player, score, reason | The anti-cheat added points to a player's suspicion score. See Anti-cheat and suspicion. |
Pinged | player, position, kind | A player placed a ping marker. kind is "Spot" or "Enemy". See Ping markers. |
Holstered | player | A player put their weapon away (empty hands). |
Reloaded | player, weaponName, mag, reserve | A reload finished on the server. Shell loaders (shotguns, the Mosin) fire once per shell. |
Rejected | player, reason | The server threw away a shot or action that didn't add up. Reasons include "not equipped", "downed", "origin", "bad dir", "bad args" and "fast reload". Useful for spotting exploiters. Set Debug.LogRejects = true to also print them. |
local XSF = require(game.ServerScriptService.XSF.Server)
-- a simple kill feed and leaderstats
XSF.Killed:Connect(function(victim, attacker, weapon)
if attacker and attacker:FindFirstChild("leaderstats") then
attacker.leaderstats.Kills.Value += 1
end
print(("%s killed %s with %s"):format(
attacker and attacker.Name or "the world", victim.Parent.Name, tostring(weapon)))
end)
-- headshot bonus
XSF.Damaged:Connect(function(victim, attacker, amount, zone)
if attacker and zone == "Head" then
print(attacker.Name .. " hit a headshot for " .. math.floor(amount))
end
end)
-- flag players whose shots keep getting rejected
local rejects = {}
XSF.Rejected:Connect(function(player, reason)
rejects[player] = (rejects[player] or 0) + 1
if rejects[player] == 50 then
warn(player.Name .. " has had 50 rejected shots, last reason: " .. reason)
end
end)A few rejects for a laggy player are normal. Hundreds in a minute is not.
Client scripts #
For your own LocalScripts: a custom HUD, kill feed, shop, main menu or cutscene. Require it from any LocalScript. Everything here only affects the local player.
local XSF = require(game.ReplicatedStorage:WaitForChild("XSF").Client.API)Functions
| Function | What it does |
|---|---|
GetWeapon() | The gun in hand as a table: name, slot, mag (rounds in the magazine), reserve (spare rounds) and mode (Auto, Semi and so on). nil when nothing is held. |
Equip(slot) | Switch to a slot number. |
Unequip() | Put the weapon away (empty hands). Since 2.1 the gun also leaves the character for other players and the server, the same as the Holster key. |
IsHolstered() | true while the player has put their weapon away on purpose. |
GetInventory() | A list of the weapon names being carried, in slot order. |
IsAiming() | true while aiming down sights. |
GetStance() | "Stand", "Crouch" or "Prone". |
IsDown(action) | true while an XSF action is held, by its Keybinds name: IsDown("Sprint"), IsDown("Aim"), IsDown("Fire"). It follows the player's own binds and works the same on keyboard, controller and touch, so you never check keys yourself. |
GetMovementState() | How the player is moving right now, as a table. See Movement state and speed below. |
OnMovement(fn) | Runs fn(dt, state) every frame, right after XSF has moved the player. Returns a function that stops it. |
AddSpeedModifier(fn) | Changes the walk speed XSF sets each frame. fn(speed, state) returns the new speed. Returns a function that removes it. |
SetMenuOpen(open, name) | For your own menus. true frees the cursor, stops XSF reading input (no shooting through the menu) and fires MenuChanged. false with the same name closes it. name is optional and keeps several menus apart; opening the same one twice is safe. See Menus and the mouse. |
IsMenuOpen(name) | true if any menu is open, XSF's own included. Pass a name to ask about one: "Settings", "Loadout", "Backpack" or yours. |
IsCursorFree() | true while the cursor is free: a menu is open, a Modal button is showing, or the FreeCursor key is held. |
SetInputEnabled(on) | false stops XSF reading keys, so a menu can use them. true gives them back. Calls stack, so two menus that both turn input off need two calls to turn it back on. It doesn't free the cursor; for menus use SetMenuOpen. |
SetHUDVisible(on) | false hides XSF's HUD (killstreak cameras, cutscenes, photo modes) until you call it with true. It wins over the player's HUD setting. |
GetSoundGroups() | XSF's sound groups as { World, Own, Body }, to duck or route them yourself. To put them under your master group from the start, use Audio.ParentGroup. |
GetKit(weapon) | The attachments the player runs on a gun (slot to name), the same ones the loadout screen shows. Works for guns they aren't carrying too. |
SetKit(weapon, attachments) | Sets the whole kit like the loadout screen's Apply: the server checks it, and it's saved with the player's settings. Slots you leave out are emptied. Yields. Returns true, or false and the reason (an attachment that doesn't fit or isn't unlocked). Nothing is sent if any part fails. |
GetAttachmentOptions(weapon) | What fits this gun for this player, as two tables of slot to names: available, and locked (fits but not unlocked yet). |
GetAttachmentStats(attachments) | The combined stat changes of a kit (recoil, aim time, range and so on), the numbers the loadout screen's bars use. |
GetAttachmentInfo(name) | One attachment's data: its slot, stats and description. |
SetCameraEnabled(on) | false hands the camera back to Roblox for cutscenes or spectating and puts the gun away. true takes it back. |
Shake(amount) | Camera shake, from 0 to 1. |
Flash(amount) | The flashbang effect, 0 to 1. |
Suppress(amount) | The effect of bullets passing close, 0 to 1. |
GetSetting(key) / SetSetting(key, value) | Read or change one of the player's settings. Changes save like normal. The keys are listed in Player settings. |
GetAllSettings() | Every setting the player has, as one table of key to value. It's a copy, so change settings with SetSetting. Saved kits are in it too, under keys that start with Kit_. |
IsReady() | true once XSF has passed its license check and finished starting on this client. Until then the other functions may do nothing (there's no weapon, no HUD). |
OnReady(fn) | Runs fn() once XSF is ready. If it already is, fn runs straight away (in a new thread). Use it for anything that has to happen after XSF starts, like opening your main menu with SetMenuOpen or calling SetHUDVisible. |
SetGunshotHandler(fn) | Hands gunshot sounds to your own sound system. fn(info) runs before every shot XSF would play on this client, the player's own and everyone else's. Return true and XSF plays nothing for that shot; return anything else and XSF plays it as normal (so you can add to it, or only take over some guns). Muzzle flashes, tracers and bullet whizz aren't affected. One handler at a time; nil removes it. If it errors, XSF warns and plays the shot itself. See the fields below. |
UI.Get(name), UI.OnBuilt(name, fn), UI.SetVisible(name, on), UI.Colors, UI.Fonts | XSF's screens: get the live ScreenGui, run code when it is built (to add your own pieces), hide it whatever XSF does, and the Theme's colours and fonts. Names: XSF_HUD, XSF_Hotbar, XSF_Backpack, XSF_Settings, XSF_Loadout, XSF_Death. See Editing XSF's screens. |
EncodeAttachments(kit) / DecodeAttachments(text) | Turn a kit (slot to name) into the short text XSF saves it as, "Sight=Acog;Barrel=Suppressor", and back. Handy for storing kits in your own DataStore or sending them through a RemoteEvent. DecodeAttachments of nil or "" gives an empty table. |
The info table SetGunshotHandler passes:
| Field | What it is |
|---|---|
weapon | The gun's name, like "HK416". |
class | The Class from its Config: Rifle, SMG, Pistol, Sniper, Shotgun or Launcher. |
position | Where the shot came from (a Vector3). |
own | true for the local player's own shots. |
suppressed | true when a suppressor is fitted. |
subsonic | true for subsonic ammo (quieter, carries less far). |
distance | Studs from the camera to the shot. |
indoor | How enclosed the listener is, 0 (open air) to 1 (a small room). XSF uses it for its own reverb. |
Signals
| Signal | Arguments | When it fires |
|---|---|---|
Fired | weaponName, origin, direction | Every shot the local player fires. |
WeaponChanged | what | Something about the held weapon changed. what is one of equip, ammo, reload, mode, check, empty, fire, inventory, inspect, zero or jam. |
Hurt | damage, fromPosition, zone | The local player took damage. |
HitConfirmed | zone, killed, damage | One of the local player's shots landed on someone. |
Suppressed | amount, position | Bullets passed close to the local player. |
StanceChanged | new, old | The player stood, crouched or went prone. |
SettingChanged | key, value | A setting changed. key is "*" when all settings were loaded at once. |
InputBegan / InputEnded | action | An XSF action was pressed or let go, by its Keybinds name ("Reload", "QuickLethal", your own added actions too). Follows the player's binds on every device. Doesn't fire while a menu has input blocked. |
MenuChanged | name, open | A menu opened or closed: XSF's "Settings", "Loadout" and "Backpack", or yours from SetMenuOpen. |
Equipped | weaponName, slot | The local player took a weapon out (after the switch, as the draw starts). |
Holstered | (none) | The local player put their weapon away (Holster key, the number key of the held gun, or Unequip()). |
ReloadStarted | weaponName, slot | A reload began. |
Reloaded | weaponName, slot, mag, reserve | A reload finished, with the new counts. Shell loaders (shotguns, the Mosin) fire it once per shell. A reload cut short (switching weapons, sprinting without Movement.SprintReload) doesn't fire it. |
ArmsBuilt | armsModel, weaponName | New first person arms were built: on every weapon switch, and for empty hands (weaponName is nil then). Use it to recolour or add to the arms, like a team colour or a watch. The model is rebuilt on the next switch, so set it up again each time. |
OtherFired | shooter, weaponName, origin, direction | Another player fired, as this client sees it (one call per shot, rockets and grenade launchers included). shooter is a Player. Good for a minimap ping or your own hit markers on teammates. |
local XSF = require(game.ReplicatedStorage:WaitForChild("XSF").Client.API)
-- a tiny ammo counter of your own (set Providers.HUD = "None" to hide XSF's)
local label = script.Parent.AmmoLabel
XSF.WeaponChanged:Connect(function()
local w = XSF.GetWeapon()
label.Text = w and (w.mag .. " / " .. w.reserve) or ""
end)
-- a hit sound and a kill sound
XSF.HitConfirmed:Connect(function(zone, killed)
if killed then
script.KillSound:Play()
elseif zone == "Head" then
script.HeadshotSound:Play()
end
end)
-- your own gunshot sounds for one gun; every other gun keeps XSF's
XSF.SetGunshotHandler(function(info)
if info.weapon ~= "Barrett" then return false end
local s = script.BarrettShot:Clone()
s.Volume = if info.own then 1 else math.clamp(1 - info.distance / 1500, 0.1, 1)
local part = Instance.new("Part")
part.Anchored, part.CanCollide, part.Transparency = true, false, 1
part.Position = info.position
part.Parent = workspace
s.Parent = part
s:Play()
game:GetService("Debris"):AddItem(part, 4)
return true
end)
-- a photo mode that hides the first person arms, even after a weapon switch
local photoMode = false
local currentArms
XSF.ArmsBuilt:Connect(function(arms)
currentArms = arms
if photoMode then
for _, p in ipairs(arms:GetDescendants()) do
if p:IsA("BasePart") then p.LocalTransparencyModifier = 1 end
end
end
end)
-- a shop: free the mouse and stop shooting while it is open
local function openShop()
XSF.SetMenuOpen(true, "Shop")
shopGui.Enabled = true
end
local function closeShop()
shopGui.Enabled = false
XSF.SetMenuOpen(false, "Shop")
endMenus and the mouse
In first person XSF locks the mouse to the middle of the screen every frame. It lets go of it in these cases, and while it's free the mouse buttons never fire or aim:
- XSF's own screens (settings, loadout, backpack).
- Your menus opened with
XSF.SetMenuOpen(true, name). Input is blocked too, so keys don't move or reload behind the menu. This is the one to use. - Any visible GuiButton with
Modal = true. That's Roblox's own way to free the mouse in first person, so menus built for normal Roblox games work as they are. - While the player holds the FreeCursor key (Left Alt by default), for quick clicks on your UI without opening a menu.
If your game takes the character away without killing it (a main menu that calls Character:Destroy() or parents it to nil, a "leave match" button), XSF puts the gun away and hides its hotbar and scope overlay until the player spawns again, so nothing of XSF's is left floating on your menu.
Two more rules stop stray shots. If your script frees the cursor itself (setting MouseBehavior every frame), a click that lands on your visible UI still doesn't shoot. And the click that closes a menu, on its close button or the dark area around it, is never counted as a shot.
local XSF = require(game.ReplicatedStorage:WaitForChild("XSF").Client.API)
-- a deploy screen between lives
XSF.SetMenuOpen(true, "Deploy")
deployButton.Activated:Connect(function()
deployGui.Enabled = false
XSF.SetMenuOpen(false, "Deploy")
end)
-- hide your own HUD while XSF's settings menu is up
XSF.MenuChanged:Connect(function(name, open)
if name == "Settings" then myHud.Enabled = not open end
end)
-- a killstreak camera: no XSF HUD while it runs
XSF.SetHUDVisible(false)
task.wait(30)
XSF.SetHUDVisible(true)Movement state and speed
GetMovementState() returns this table, and OnMovement and AddSpeedModifier pass it to your function as state.
| Field | What it is |
|---|---|
stance | "Stand", "Crouch" or "Prone". |
sprinting | true while actually sprinting (moving forward on the ground at sprint speed), including a sprint reload. |
sprintReload | true while running through a reload with Movement.SprintReload on. |
sprintHeld | true while the player wants to sprint (key held, or sprint toggled on), even if they can't right now. |
sliding | true during a slide. |
exhausted | true once stamina ran out. Sprint stays locked until the bar is back above 30%. |
stamina | 0 to 1. |
lean | -1 (full left) to 1 (full right), 0 upright. |
aiming | true while aiming down sights. |
reloading | true during a reload. |
grounded | true when standing on something. |
speed | The walk speed XSF last set. |
Don't set Humanoid.WalkSpeed yourself, XSF sets it again every frame. A speed modifier is the way to change it: it runs after sprint, aim, stance and dragging are worked out, so you get the final number and return your own. Several modifiers run in the order they were added.
local XSF = require(game.ReplicatedStorage:WaitForChild("XSF").Client.API)
-- a tactical sprint: hold sprint for two seconds and you get a short burst of speed
local heldFor = 0
XSF.OnMovement(function(dt, state)
heldFor = if state.sprinting then heldFor + dt else 0
end)
XSF.AddSpeedModifier(function(speed, state)
if state.sprinting and heldFor > 2 and heldFor < 4 then
return speed * 1.25
end
return speed
end)
-- a mud zone: half speed while the player stands in it
local inMud = false
local stopMud = XSF.AddSpeedModifier(function(speed)
return if inMud then speed * 0.5 else speed
end)
-- call stopMud() to remove it again
-- your own stamina bar
XSF.OnMovement(function(dt, state)
bar.Size = UDim2.fromScale(state.stamina, 1)
bar.BackgroundColor3 = if state.exhausted then Color3.fromRGB(200, 60, 60) else Color3.new(1, 1, 1)
end)Things the client API can't do
It can't give weapons, ammo, damage or unlocks, on purpose. Anything that changes the game has to happen on the server, or exploiters could do it too. For a shop, the LocalScript asks the server with a RemoteEvent, and the server checks the player can afford it and calls the server API. There's a full example in Recipes.
NPCs with guns #
Any model in Workspace with a Humanoid can be shot, takes the same damage by body part as players, ragdolls when it dies and fires the same events. You don't need to register it. XSF finds it when it appears, including NPCs you clone in later.
To give an NPC a gun and pose it, set attributes on its model from a server script. XSF puts the gun in its hands and poses the body on every client.
| Attribute | Type | What it does |
|---|---|---|
XSF_Weapon | string | Weapon name, like "HK416". Change it to swap guns, clear it to take the gun away. |
XSF_Attachments | string | Optional. Slot=Name pairs split by semicolons, like "Sight=Acog;Barrel=Suppressor". Set it before XSF_Weapon. |
XSF_Stance | string | "Stand", "Crouch" or "Prone". On R15, crouch also lowers the hip height. |
XSF_Pitch | number | Where it aims up or down, in radians. 0 is level. |
XSF_Aim | number | 0 holds the gun at the ready, 1 raises it to the shoulder. |
XSF_Lean | number | -1 leans left, 1 leans right. |
XSF_Sprint | boolean | Carries the gun low, like it's running. |
XSF_Sliding | boolean | Slide pose. |
XSF_Armor / XSF_Helmet | number | Armour, 0 to 1. See How damage works. |
local npc = workspace.Guard
npc:SetAttribute("XSF_Attachments", "Sight=Aimpoint;Other=Flashlight")
npc:SetAttribute("XSF_Weapon", "MP5")
npc:SetAttribute("XSF_Aim", 1)
npc:SetAttribute("XSF_Armor", 0.3)Making NPCs shoot
NPC shooting logic is yours to write. XSF handles the gun, the pose, damage and death. The simplest way to make an NPC hurt players is a line of sight check and XSF.Damage:
local XSF = require(game.ServerScriptService.XSF.Server)
local npc = script.Parent
local head = npc:WaitForChild("Head")
local params = RaycastParams.new()
params.FilterDescendantsInstances = { npc }
local function closestPlayer()
local best, bestDist = nil, 120
for _, p in game.Players:GetPlayers() do
local root = p.Character and p.Character:FindFirstChild("HumanoidRootPart")
local hum = p.Character and p.Character:FindFirstChildOfClass("Humanoid")
if root and hum and hum.Health > 0 then
local d = (root.Position - head.Position).Magnitude
if d < bestDist then best, bestDist = p, d end
end
end
return best
end
while npc.Parent and npc.Humanoid.Health > 0 do
local target = closestPlayer()
if target then
local root = target.Character.HumanoidRootPart
local dir = root.Position - head.Position
local hit = workspace:Raycast(head.Position, dir, params)
if hit and hit.Instance:IsDescendantOf(target.Character) then
npc:SetAttribute("XSF_Aim", 1)
npc:SetAttribute("XSF_Pitch", math.asin(dir.Unit.Y))
npc.HumanoidRootPart.CFrame = CFrame.lookAt(npc.HumanoidRootPart.Position,
Vector3.new(root.Position.X, npc.HumanoidRootPart.Position.Y, root.Position.Z))
if math.random() < 0.35 then -- it misses a lot
XSF.Damage(target.Character.Humanoid, 18, nil, "Torso")
end
else
npc:SetAttribute("XSF_Aim", 0)
end
end
task.wait(0.25)
endThis doesn't fire visible bullets. For a full NPC that also plays shots and tracers, keep this logic and trigger your own sound and effect, or ask in the Discord for the current approach.
Downed NPCs
Set Downed.NPCs = true in GameConfig if NPCs should go down and be revivable instead of dying straight away. Your scripts can revive them with XSF.Revive, so an NPC squadmate that picks the player up (or that the player can pick up) is a few lines.
Test dummies
The test map's dummies are normal models with Humanoids. Copy one anywhere to get a target that takes damage and ragdolls.
Attributes #
XSF reads and sets a handful of attributes. The ones you can set are how you control things without code in XSF's modules. The ones XSF sets are safe to read from your own scripts.
You set these
| Attribute | On | Type | What it does |
|---|---|---|---|
XSF_Armor | character model | number 0 to 1 | Cuts torso and limb damage by that share. |
XSF_Helmet | character model | number 0 to 1 | Cuts head damage by that share. |
XSF_Weapon, XSF_Attachments, XSF_Stance, XSF_Pitch, XSF_Aim, XSF_Lean, XSF_Sprint, XSF_Sliding | NPC model | see NPCs | Gun and pose for NPCs. |
XSF_PenetrationCost | any part | number | Overrides the material's wall cost. 100 is bulletproof, 0 is ignored by bullets. See Bullets and walls. |
XSF_AttFit, XSF_AttScale, XSF_SightRiser, XSF_Class | gun model | see Adding a gun | How attachments sit on your gun. |
XSF_SkinKeep, XSF_SkinOnlyTagged | gun model | boolean | Keep the gun's own textures under skins; only paint tagged parts. See Skins on your gun. |
XSF_SkinRole | part of a gun model | Body, Furniture, Metal, Mag, Blade, None | What a skin paints this part as. |
XSF_IronSight, XSF_Fold | part of a gun model | boolean, number (degrees) | An iron sight that folds away under an optic, and how far it tips. |
XSF_GripWrap, XSF_GripReach | gun model | boolean, number | Shooting hand grip. See Hands and grips. |
XSF_SelfTest | Workspace | string | Limits the F6 self test to a list like "RPG7, M320". Studio only. |
XSF_VaultSlowMo | Workspace | number | Slows vault animations down (0.25 is quarter speed) so you can check how they look. Studio testing only, remove it after. |
XSF sets these (read only)
| Attribute | On | What it means |
|---|---|---|
XSF_Downed | Humanoid | true while the character is downed. |
XSF_Stance | player character | The player's current stance ("Stand", "Crouch" or "Prone"), so your scripts can react to it. |
XSF_Owned | Player | Their unlocks as JSON. See Unlocks. |
XSF_Skin | gun model | The skin currently on that gun. |
XSF_Weapon, XSF_Mag, XSF_Reserve, XSF_Attachments | pickup model | What a dropped gun is and how much ammo it has. |
Don't set the read only ones yourself. Setting XSF_Downed by hand won't down anyone, it'll just confuse the HUD.
Swapping parts out #
The Providers section of GameConfig picks which version of a system runs.
| Provider | Options |
|---|---|
Inventory | "XSF": XSF's own hotbar and backpack. "RobloxTools": each weapon is a normal Tool in the Roblox backpack, for games that already use Tools (some shop kits, admin commands that give tools, and so on). |
Arms | "XSFRealistic": gloved arms whose fingers close around each gun. "ACSClassic": the original ACS block arms. |
HUD | "XSFMinimal": small readouts that only show when they matter. "None": no XSF HUD, so you can build your own with the client API. |
Advanced: any of these can also point at your own ModuleScript instead of a name. It has to offer the same functions as the built in one, so this is only worth it for big custom projects. Ask in the Discord before you start, and we'll tell you what it needs.
Building your own HUD
Since 2.3 you can usually restyle XSF's own HUD instead, without any code: see Editing XSF's screens. To replace it completely, set Providers.HUD = "None", then build a ScreenGui and fill it from the client API: WeaponChanged and GetWeapon() for ammo, Hurt and the Humanoid's Health for health, StanceChanged for stance, HitConfirmed for hitmarkers.
Anti-cheat and suspicion #
XSF is server authoritative: the server decides every hit, every reload and every throw, and a modified client can only ask. Since 2.3 every request also goes through a rate limit and value checks, and anything that looks like cheating adds points to a suspicion score per player. XSF never kicks or bans on its own. It tells your scripts, and your game decides what to do.
What the server checks
- Shots. The gun must be the one equipped, not downed (unless
Downed.AllowFiring), have rounds, respect its fire rate, and start within 7 studs of the player's head (plus a bit when moving fast). The direction must be a real unit vector. Hits are decided by the server with lag compensation (it rewinds other players to where the shooter saw them). - Reloads can't be finished faster than the gun's reload takes.
- Knives swing from the player's head on the server, with the target rewound for lag, and can't hit through walls.
- Grenades, C4 and claymores are thrown and placed by the server from the player's position; a placement through a wall is refused.
- Every value sent to the server is type checked. Numbers that are NaN or infinite (a common way to break servers) are refused.
- Movement (report only, see below).
Rate limits
Each player has a budget per kind of request. Short bursts are fine (network jitter bunches packets together); going over the budget means the extra requests are ignored, and adds 3 suspicion points at most once a second with the reason "spamming <name>".
| Request | Per second | Burst |
|---|---|---|
| Fire | the gun's own fire rate (refused as "rate") | |
| Equip | 12 | 12 |
| FireMode, Reload | 8 | 8 |
| Melee (knife swing) | 10 | 6 |
| Throw, Detonate | 4 | 4 |
| Drop | 4 | 4 |
| Settings save | 4 | 8 |
| Ping | 3 | 4 |
| Stance and lean updates | 20 | 10 |
| Downed actions (give up, drag) | 2 | 2 |
| Performance report | 1 every 20 seconds | 2 |
| License key entry | its own limit, and the key is capped at 200 characters | |
Suspicion points
Small refusals happen to honest players on a bad connection; big ones need a modified client. Points drain over time (Security.SuspicionDecay per second, 1 by default), so occasional lag never adds up.
| Reason | Points | What it usually means |
|---|---|---|
empty, not equipped, downed | 0.5 | Lag: the client was a moment ahead of the server. |
rate | 1 | A shot faster than the gun fires. A few in a fight is lag; a steady stream is a rapid fire cheat. |
spamming <name> | 3 | A request sent far more often than the game ever sends it. |
fast reload | 4 | A reload finished before the animation could have. |
origin | 6 | A shot that didn't start near the player. Very rare from lag. |
speed N studs/s | 6 | Moving faster than XSF allows, twice in a row. |
flying | 6 | More than 3 seconds off the ground without falling. |
bad args, bad dir | 8 | Values the game never sends (NaN, wrong types, a non unit direction). |
teleport | 12 | A jump of tens of studs in half a second. |
As a rule of thumb, an honest player who lags now and then stays in single digits. A score that keeps climbing past 40 is very likely cheating.
Movement checks
Every player is looked at about twice a second, spread over frames so a full server costs the same every frame. The allowed speed is the fastest of the Humanoid's WalkSpeed, Movement.SprintSpeed, Movement.WalkSpeed and the slide speed, times 1.35, plus 6 studs a second. One fast moment is forgiven (knockback, a fling); two in a row count.
Skipped automatically: the first 3 seconds after spawning, 1.5 seconds after any server script moves the character (your teleports, spawn pads, round resets are forgiven without any setup), anchored or seated characters, PlatformStand, downed players, swimming, climbing, ragdolls and falling down.
Things XSF can't see from the server, like a launch pad or a vehicle driven from the client, need one call before the move:
local XSF = require(game.ServerScriptService.XSF.Server)
launchPad.Touched:Connect(function(hit)
local player = game.Players:GetPlayerFromCharacter(hit.Parent)
if not player then return end
XSF.AllowMovement(player, 3) -- don't judge their movement for 3 seconds
hit.Parent.HumanoidRootPart.AssemblyLinearVelocity = Vector3.new(0, 120, 0)
end)If your game has jetpacks, grapples or anything else that moves players fast all the time, set Security.MovementChecks = false. Every other check stays on.
Reacting to it
local XSF = require(game.ServerScriptService.XSF.Server)
XSF.SuspicionChanged:Connect(function(player, score, reason)
-- score is the player's current total, reason the latest report
if score > 80 then
player:Kick("Removed by anti-cheat (" .. reason .. ")")
elseif score > 40 then
warn(player.Name .. " looks suspicious: " .. score .. " (" .. reason .. ")")
-- or send it to your moderators, a webhook, your own logs
end
end)| API | What it does |
|---|---|
XSF.SuspicionChanged | Event: (player, score, reason) every time points are added. score is the total after draining, rounded to one decimal. |
XSF.GetSuspicion(player) | The player's current score. |
XSF.ResetSuspicion(player) | Back to 0 (after a moderator checked them, at a new round). |
XSF.AllowMovement(player, seconds) | Skips movement checks for that long (2 seconds if you leave it out). |
XSF.Rejected | Event: (player, reason) for every refused shot or action, same as before. |
Testing it
Security.LogSuspicion = trueprints every report in Output:[XSF] suspicion PlayerName +6 (speed 61 studs/s) = 6.- In Studio the Player gets two attributes you can watch in Properties:
XSF_Suspicion(the score) andXSF_SuspicionReason(the latest reason). Live games don't set them. - To test movement, raise your WalkSpeed from the client side command bar while playing; the server reports speed after two checks.
What it can't do
Aimbots and wallhacks run entirely on the cheater's computer and send normal looking shots, so no server can prove them from a single shot. The score helps with what can be proven: impossible fire rates, shots from the wrong place, impossible movement and broken requests. Combine it with your own reports and moderation.
GameConfig
Security = {
MovementChecks = true, -- report speed, teleports and flying. Turn off for jetpacks or client vehicles
SuspicionDecay = 1, -- points drained per second
LogSuspicion = false, -- print every report in Output (testing)
},Recipes #
Copy and paste starting points for things people build on XSF all the time. Each one is complete and works on its own in a fresh place with XSF installed. Change the names and numbers to fit your game.
Server code goes in a Script in ServerScriptService. Client code goes in a LocalScript in StarterPlayerScripts unless it says otherwise. Where a recipe needs a RemoteEvent, the server script creates it, so you don't have to.
Main menu with a Play button
Players spawn with no guns, see a menu, and get their loadout when they press Play. First add GiveLoadout = false to GameConfig.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local XSF = require(game.ServerScriptService.XSF.Server)
local play = Instance.new("RemoteEvent")
play.Name = "PlayPressed"
play.Parent = ReplicatedStorage
local inMatch = {}
play.OnServerEvent:Connect(function(player)
if inMatch[player] then return end -- ignore spam clicks
inMatch[player] = true
XSF.SetLoadout(player, { "M4A1", "G17", "KaBar", "M67" })
end)
-- give guns again after a respawn, as long as they already pressed Play
game.Players.PlayerAdded:Connect(function(player)
player.CharacterAdded:Connect(function()
if inMatch[player] then
task.wait()
XSF.SetLoadout(player, { "M4A1", "G17", "KaBar", "M67" })
end
end)
end)
game.Players.PlayerRemoving:Connect(function(player)
inMatch[player] = nil
end)local ReplicatedStorage = game:GetService("ReplicatedStorage")
local XSF = require(ReplicatedStorage:WaitForChild("XSF").Client.API)
local gui = script.Parent
-- free the mouse and keys while the menu is open
XSF.SetInputEnabled(false)
gui.Play.Activated:Connect(function()
gui.Enabled = false
XSF.SetInputEnabled(true)
ReplicatedStorage:WaitForChild("PlayPressed"):FireServer()
end)Set the ScreenGui's ResetOnSpawn to false, or the menu will pop up again every time the player dies.
Class picker
The same idea with a choice. The server owns the list of classes, so a modified client can only pick a class that exists.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local XSF = require(game.ServerScriptService.XSF.Server)
local CLASSES = {
Assault = { guns = { "HK416", "G17", "M67" }, atts = { HK416 = { Sight = "EOTech", UnderBarrel = "Vertical Grip" } } },
Sniper = { guns = { "R700", "USP", "M18" }, atts = { R700 = { Sight = "PM II", UnderBarrel = "Bipod" } } },
Breacher = { guns = { "M870", "Deagle", "C4" }, atts = {} },
Support = { guns = { "M249", "M1911", "M67" }, atts = {} },
}
local pick = Instance.new("RemoteEvent")
pick.Name = "PickClass"
pick.Parent = ReplicatedStorage
local chosen = {}
local function equip(player)
local class = CLASSES[chosen[player]]
if not class then return end
XSF.SetLoadout(player, class.guns)
for gun, atts in pairs(class.atts) do
XSF.SetAttachments(player, gun, atts)
end
end
pick.OnServerEvent:Connect(function(player, name)
if type(name) ~= "string" or not CLASSES[name] then return end
chosen[player] = name
equip(player)
end)
game.Players.PlayerAdded:Connect(function(player)
player.CharacterAdded:Connect(function()
task.wait()
equip(player)
end)
end)
game.Players.PlayerRemoving:Connect(function(player)
chosen[player] = nil
end)From your menu's buttons, call ReplicatedStorage.PickClass:FireServer("Sniper"). With GiveLoadout = false, players have nothing until they pick.
Leveling that unlocks attachments
Kills give XP, levels unlock attachments. Turn Customization.Ownership on ({ Attachments = true, Skins = true }) and set Customization.SaveOwnership = true so unlocks are remembered between visits. XP itself is saved here with a simple DataStore.
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local XSF = require(game.ServerScriptService.XSF.Server)
local store = DataStoreService:GetDataStore("XP_v1")
local XP_PER_KILL = 100
local XP_PER_LEVEL = 500
-- what each level unlocks (on every gun)
local REWARDS = {
[2] = { "Reflex" },
[3] = { "Compensator", "Angled Grip" },
[5] = { "Acog", "Suppressor" },
[8] = { "Hollow Point", "Armor Piercing" },
[10] = { "PM II", "Thermal" },
}
local function levelOf(xp)
return math.floor(xp / XP_PER_LEVEL) + 1
end
local function grantUpTo(player, level)
for lvl = 1, level do
for _, name in ipairs(REWARDS[lvl] or {}) do
XSF.GrantAttachment(player, name)
end
end
end
Players.PlayerAdded:Connect(function(player)
local stats = Instance.new("Folder")
stats.Name = "leaderstats"
local level = Instance.new("IntValue")
level.Name = "Level"
level.Parent = stats
local xp = Instance.new("IntValue")
xp.Name = "XP"
xp.Parent = stats
stats.Parent = player
local ok, saved = pcall(store.GetAsync, store, "u_" .. player.UserId)
xp.Value = (ok and saved) or 0
level.Value = levelOf(xp.Value)
grantUpTo(player, level.Value) -- also re-grants anything missed
end)
XSF.Killed:Connect(function(victim, attacker)
if not attacker or not attacker:FindFirstChild("leaderstats") then return end
if Players:GetPlayerFromCharacter(victim.Parent) == attacker then return end
local stats = attacker.leaderstats
stats.XP.Value += XP_PER_KILL
local newLevel = levelOf(stats.XP.Value)
if newLevel > stats.Level.Value then
stats.Level.Value = newLevel
grantUpTo(attacker, newLevel)
end
end)
Players.PlayerRemoving:Connect(function(player)
local stats = player:FindFirstChild("leaderstats")
if stats then
pcall(store.SetAsync, store, "u_" .. player.UserId, stats.XP.Value)
end
end)For a per gun progression (unlock the ACOG on the AK by getting kills with the AK), pass the weapon name from Killed to GrantAttachment(attacker, "Acog", weapon).
A skin shop with coins
The client asks, the server checks the price and the balance, then grants. Prices live on the server so nobody can buy for free.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local XSF = require(game.ServerScriptService.XSF.Server)
local PRICE_BY_RARITY = { Common = 100, Uncommon = 250, Rare = 600, Epic = 1500, Legendary = 4000 }
local buy = Instance.new("RemoteFunction")
buy.Name = "BuySkin"
buy.Parent = ReplicatedStorage
Players.PlayerAdded:Connect(function(player)
local stats = Instance.new("Folder")
stats.Name = "leaderstats"
local coins = Instance.new("IntValue")
coins.Name = "Coins"
coins.Value = 500
coins.Parent = stats
stats.Parent = player
end)
-- 25 coins per kill
XSF.Killed:Connect(function(victim, attacker)
if attacker and attacker:FindFirstChild("leaderstats") then
attacker.leaderstats.Coins.Value += 25
end
end)
buy.OnServerInvoke = function(player, skinName)
local skin = type(skinName) == "string" and XSF.Skins[skinName]
if not skin then return false, "No such skin" end
if XSF.OwnsSkin(player, skinName) then return false, "You already own it" end
local price = PRICE_BY_RARITY[skin.Rarity] or 100
local coins = player.leaderstats.Coins
if coins.Value < price then return false, "Not enough coins" end
coins.Value -= price
XSF.GrantSkin(player, skinName)
return true, "Bought " .. skinName
endlocal ReplicatedStorage = game:GetService("ReplicatedStorage")
local ok, message = ReplicatedStorage:WaitForChild("BuySkin"):InvokeServer("Carbon Fiber")
print(message)Save the coins the same way as the XP in the leveling recipe, and turn on Customization.SaveOwnership so bought skins stay bought.
Gamepass skins
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local XSF = require(game.ServerScriptService.XSF.Server)
local GOLD_PACK = 000000000 -- your gamepass id
local GOLD_SKINS = { "Gold Plated", "Black Gold", "Rose Gold", "Brushed Gold" }
local function grantPack(player)
for _, skin in ipairs(GOLD_SKINS) do
XSF.GrantSkin(player, skin)
end
end
Players.PlayerAdded:Connect(function(player)
local ok, owns = pcall(MarketplaceService.UserOwnsGamePassAsync, MarketplaceService, player.UserId, GOLD_PACK)
if ok and owns then grantPack(player) end
end)
-- bought it in game, no rejoin needed
MarketplaceService.PromptGamePassPurchaseFinished:Connect(function(player, passId, bought)
if bought and passId == GOLD_PACK then grantPack(player) end
end)Gamepass grants don't need saving, they're re-granted every time the player joins.
Battle royale loot
Put invisible, anchored, non colliding parts named Loot around your map (inside a folder called LootSpots). This fills them with random guns and ammo boxes. In GameConfig, add GiveLoadout = false and Pickups = { MaxWeapons = 3, Lifetime = 600 }.
local XSF = require(game.ServerScriptService.XSF.Server)
local LOOT = {
{ name = "G17", weight = 30 }, { name = "MP5", weight = 20 }, { name = "UMP45", weight = 15 },
{ name = "M870", weight = 12 }, { name = "AK47", weight = 10 }, { name = "HK416", weight = 8 },
{ name = "SCAR", weight = 4 }, { name = "R700", weight = 3 }, { name = "AWM", weight = 1 },
}
local SIGHTS = { "Reflex", "EOTech", "Aimpoint", "Acog" }
local total = 0
for _, item in ipairs(LOOT) do total += item.weight end
local function roll()
local r = math.random() * total
for _, item in ipairs(LOOT) do
r -= item.weight
if r <= 0 then return item.name end
end
return LOOT[1].name
end
local function fill()
for _, spot in ipairs(workspace.LootSpots:GetChildren()) do
if math.random() < 0.25 then
XSF.SpawnPickup("Ammo", spot.CFrame, { Mags = 2, Uses = 2 })
else
local atts = {}
if math.random() < 0.3 then atts.Sight = SIGHTS[math.random(#SIGHTS)] end
-- leave mag and reserve out: the gun comes with its normal spawn ammo
XSF.SpawnPickup(roll(), spot.CFrame + Vector3.new(0, 1, 0), { atts = atts })
end
end
end
fill() -- call fill() again at the start of every roundRounds with a timer
A team deathmatch loop: intermission, a timed round, a winner, repeat. Assumes two Teams called Red and Blue and turns downed off for quick fights (Downed = { Enabled = false }).
local Players = game:GetService("Players")
local Teams = game:GetService("Teams")
local XSF = require(game.ServerScriptService.XSF.Server)
local ROUND_TIME = 300
local INTERMISSION = 15
local score = { Red = 0, Blue = 0 }
local running = false
local status = Instance.new("StringValue")
status.Name = "RoundStatus"
status.Parent = game.ReplicatedStorage -- show this in your own UI
XSF.Killed:Connect(function(victim, attacker)
if not running or not attacker or not attacker.Team then return end
local victimPlayer = Players:GetPlayerFromCharacter(victim.Parent)
if victimPlayer and victimPlayer.Team ~= attacker.Team then
score[attacker.Team.Name] = (score[attacker.Team.Name] or 0) + 1
end
end)
while true do
for t = INTERMISSION, 1, -1 do
status.Value = "Next round in " .. t
task.wait(1)
end
score.Red, score.Blue = 0, 0
running = true
for _, p in ipairs(Players:GetPlayers()) do
p:LoadCharacter() -- everyone respawns with a fresh loadout
end
for t = ROUND_TIME, 1, -1 do
status.Value = ("Red %d | %d:%02d | %d Blue"):format(score.Red, t // 60, t % 60, score.Blue)
task.wait(1)
end
running = false
local winner = score.Red > score.Blue and "Red" or score.Blue > score.Red and "Blue" or "Nobody"
status.Value = winner .. " wins!"
task.wait(5)
endGun game
Every kill moves you to the next gun. First to finish the list wins.
local Players = game:GetService("Players")
local XSF = require(game.ServerScriptService.XSF.Server)
local LADDER = { "AK47", "HK416", "SCAR", "P90", "MP5", "M870", "AWM", "Deagle", "G17", "KaBar" }
local stage = {}
local function arm(player)
XSF.SetLoadout(player, { LADDER[stage[player] or 1] })
end
Players.PlayerAdded:Connect(function(player)
stage[player] = 1
player.CharacterAdded:Connect(function()
task.wait()
arm(player)
end)
end)
Players.PlayerRemoving:Connect(function(player)
stage[player] = nil
end)
XSF.Killed:Connect(function(victim, attacker)
if not attacker or not stage[attacker] then return end
if Players:GetPlayerFromCharacter(victim.Parent) == attacker then return end
stage[attacker] += 1
if stage[attacker] > #LADDER then
print(attacker.Name .. " wins gun game!")
for p in pairs(stage) do stage[p] = 1 end
for _, p in ipairs(Players:GetPlayers()) do p:LoadCharacter() end
else
arm(attacker)
end
end)Add GiveLoadout = false, Downed = { Enabled = false } and Pickups = { Enabled = false } to GameConfig so nobody skips the ladder by grabbing a dropped gun.
A kill feed on everyone's screen
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local XSF = require(game.ServerScriptService.XSF.Server)
local feed = Instance.new("RemoteEvent")
feed.Name = "KillFeed"
feed.Parent = ReplicatedStorage
local lastZone = {}
XSF.Damaged:Connect(function(victim, attacker, amount, zone)
lastZone[victim] = zone
end)
XSF.Killed:Connect(function(victim, attacker, weapon)
local victimPlayer = Players:GetPlayerFromCharacter(victim.Parent)
feed:FireAllClients(
attacker and attacker.Name or "",
victimPlayer and victimPlayer.Name or victim.Parent.Name,
weapon or "",
lastZone[victim] == "Head"
)
lastZone[victim] = nil
end)local ReplicatedStorage = game:GetService("ReplicatedStorage")
local feed = script.Parent.Feed
ReplicatedStorage:WaitForChild("KillFeed").OnClientEvent:Connect(function(killer, victim, weapon, headshot)
local line = Instance.new("TextLabel")
line.Size = UDim2.new(1, 0, 0, 22)
line.BackgroundTransparency = 1
line.TextColor3 = Color3.new(1, 1, 1)
line.TextXAlignment = Enum.TextXAlignment.Right
line.Font = Enum.Font.GothamBold
line.TextSize = 16
line.Text = (killer ~= "" and killer .. " [" .. weapon .. "] " or "") .. victim .. (headshot and " (headshot)" or "")
line.Parent = feed
task.delay(6, function() line:Destroy() end)
end)Armour pickups
local part = script.Parent
local used = false
part.Touched:Connect(function(hit)
if used then return end
local char = hit.Parent
local player = game.Players:GetPlayerFromCharacter(char)
if not player then return end
used = true
char:SetAttribute("XSF_Armor", 0.4)
char:SetAttribute("XSF_Helmet", 0.5)
part.Transparency = 1
task.wait(30) -- respawns after 30 seconds
part.Transparency = 0
used = false
end)Armour lasts until the character dies, because the attributes are on the character. For armour that breaks, lower XSF_Armor a bit in XSF.Damaged every time the player is hit.
Kill streak rewards
local Players = game:GetService("Players")
local XSF = require(game.ServerScriptService.XSF.Server)
local streak = {}
XSF.Killed:Connect(function(victim, attacker)
local victimPlayer = Players:GetPlayerFromCharacter(victim.Parent)
if victimPlayer then
streak[victimPlayer] = 0
end
if not attacker or attacker == victimPlayer then return end
streak[attacker] = (streak[attacker] or 0) + 1
if streak[attacker] == 3 then
XSF.AddAmmo(attacker, nil, 60)
elseif streak[attacker] == 5 then
XSF.Give(attacker, "M320")
elseif streak[attacker] == 10 then
-- airstrike on the attacker's last victim
local pos = victim.Parent:GetPivot().Position
for i = 1, 5 do
task.delay(i * 0.4, function()
XSF.Explode(pos + Vector3.new(math.random(-20, 20), 0, math.random(-20, 20)),
{ Radius = 18, Damage = 180 }, attacker, "Airstrike")
end)
end
end
end)Headshots only
A hardcore mode where only headshots hurt. This goes in GameConfig, not a script:
Damage = {
FriendlyFire = false,
Hitmarkers = false,
Modify = function(humanoid, amount, info)
-- explosions and script damage still hurt, so grenades and lava work as normal
local explosive = { M67 = true, C4 = true, Claymore = true, RPG7 = true, M320 = true }
if info.weapon and not explosive[info.weapon] and info.zone ~= "Head" then
return 0 -- body and limb hits from guns and knives do nothing
end
return amount
end,
},Spawn protection
XSF already respects Roblox's ForceField. Set Duration on your SpawnLocations (3 to 5 seconds is common) and players can't be hurt by XSF until it wears off. That's the whole recipe.
Building maps for XSF #
XSF is realistic enough that the map decides how fights play. Bullets go through some walls and not others, players can vault and climb, and leaning lets them peek corners. A few numbers to build around:
Scale
- One stud is 28 cm (
Ballistics.StudsPerMeter). A standard Roblox character is about 5 studs tall, so a 7 to 8 stud doorway and 10 to 12 stud ceilings feel right. - Default walk speed is 12 studs per second and sprint is 21. A 300 stud long street takes about 15 seconds to sprint, which is longer than most players expect. Size your map for the speeds you picked.
Cover that works the way players expect
Players will learn what stops bullets. Make it consistent:
- Hard cover (Concrete, Brick, Rock, Metal) at 1 stud or thicker stops everything up to a rifle. Use it for walls players are meant to hide behind.
- Soft cover (Wood, Plastic, Fabric) stops vision, not bullets. Rifles go through 2 studs of wood. Use it for fences, crates and interior walls, so shooting through them is a real tactic.
- Glass barely slows a bullet.
- A part that looks like concrete but uses SmoothPlastic is shoot through. Match materials to looks, or set
XSF_PenetrationCostwhere they don't match. - Thicker than 6 studs always stops bullets, whatever the material.
The full table is in Bullets and walls.
Heights
| Height | What happens | Good for |
|---|---|---|
| Up to 3.6 studs | Vaulted over | Low walls, sandbags, car bonnets, windows you jump through |
| 3.6 to 7 studs | Climbed onto | Ledges, shipping containers, low roofs |
| Over 7 studs | Can't be climbed | Anything players must not get over, like map edges |
| Under 2.6 studs of headroom | Can't stand up from a crouch | Crawl spaces, vents (need prone, under 3.2 from prone) |
These follow Vault.VaultHeight, Vault.MaxHeight and the stance headroom checks. Change the Vault values and these change with them.
Corners and leaning
Leaning moves the head 1.1 studs sideways (Stances.LeanOffset). A doorway with walls thinner than that lets players peek without exposing their body, which is good. Pillars thinner than about 2 studs let players lean around both sides.
Sight lines
Match your longest sight lines to the guns you give out. Rifle damage starts falling at 150 studs by default and reaches its lowest at 600. Snipers keep full damage out to 500 to 1000 studs. A map with 800 stud sight lines and only SMGs is just frustrating. The range of every gun is in Every weapon's stats.
Lighting
If you plan to give out Night Vision or Thermal, make some areas really dark. Night vision is only worth picking when the map has places you can't see without it. Thermal shines in smoke and shadow.
Props
- Anchor anything that doesn't need to move. Loose parts get pushed by every bullet and blast, which costs performance in a big fight.
- A few loose barrels or crates near where fights happen look great when they get blown around.
- Keep invisible walls tall (over 7 studs) so players can't climb them.
Placing XSF things
- Workbenches (with
Customization.Access = "Workbench") belong at spawns and safe spots, not in the middle of fights. - Ammo boxes (
XSF.SpawnPickup("Ammo", ...)) on routes between fights, so players are tempted to move. - Loot spots for a battle royale: more and better loot in riskier places.
Balancing guns #
XSF's guns ship balanced against each other for a realistic game with 100 health. If you change health, add armour or only use a few guns, you'll want to adjust. Here's how to think about it.
Time to kill
The number that matters most is how long it takes to kill someone. Work it out like this:
- Shots to kill = 100 health divided by damage per shot, rounded up.
- Time to kill = (shots to kill minus 1) times 60 divided by RPM.
An HK416 does 60 to the body: 2 shots. At 800 RPM that's one gap of 0.075 seconds. An MP5 does 34: 3 shots, two gaps at 800 RPM, 0.15 seconds. A G17 does 28: 4 shots, three gaps at 480 RPM, 0.375 seconds. That ignores misses, travel time and falloff, but it's the right starting point.
With downed on, add Downed.DownedHealth (40) to finish someone, so the HK416 needs one more shot to actually kill.
Roles, not just numbers
Two guns with the same time to kill still feel different because of everything else. Give each gun a reason to pick it:
| To make a gun... | Change |
|---|---|
| better up close | Lower Handling.AimTime and Mass, raise RPM, shorten falloff (FalloffStart 40 to 60) |
| better far away | Raise MuzzleVelocity and falloff distances, lower Dispersion, add zero steps |
| easier to use | Lower Recoil.Climb and HeatPerShot |
| harder but stronger | Raise damage and climb together |
| better against armour | Raise Fire.Penetration, or hand out Armor Piercing ammo |
| a commitment | Small magazine, slow reload (Handling.ReloadSpeed below 1), small reserve |
Headshots
Most rifles do about 2.5 times body damage to the head, so any headshot is a kill at 100 health. If you raise health to 150 or add helmets, headshots stop being one shot for most guns. Decide if you want that.
Armour changes everything
XSF_Armor = 0.4 makes every body shot do 60%. An HK416 goes from 2 shots to 3, an MP5 from 3 to 5. Armour piercing ammo cuts that armour to 0.16. If you add armour, test the weak guns first. Pistols and SMGs suffer the most.
Shotguns
Shotgun damage is per pellet, 8 pellets per shot. The 870's 28 per pellet is 224 if every pellet hits, which is a kill even through armour up close. To make shotguns weaker at range without touching close range, shorten FalloffEnd or widen PelletSpread.
Use the dashboard for balance passes
The dashboard's per gun stats (damage, RPM, magazine, reserve, bullet speed, aim time, recoil) cover most balance changes, and they reach every new server without republishing. Make a copy of your preset first, change the copy, and switch a test game to it before switching your live game.
Test it, don't guess
- Shoot the dummies in the test map at 25, 50, 100 and 150 studs and count hits to kill.
- Play a round with someone else using the gun you're worried about.
- Watch which guns people actually pick. A loadout where everyone runs the same gun is the clearest sign something's off.
Testing your game #
Weapon self test (F6)
In a Studio playtest, press F6. XSF equips every weapon in your game one by one, fires it, aims, reloads and inspects, then prints a pass or fail line for each in Output, and a summary at the end like XSF self test: 59 weapons, 59 passed, 0 with problems. It takes under a minute and catches broken weapon folders, missing models, missing Anims modules and bad Config values. Your character can't die during the run, and it respawns you when it's done.
To test only some weapons, set the Workspace attribute XSF_SelfTest to a list like "RPG7, M320" before you press Play. It only runs in Studio, never in a live game.
Run it every time you add or change a weapon. It's the fastest way to find the gun you broke.
Test map
The download comes with an optional XSF_TestMap (see Install). Put it in Workspace and you get:
- A house to clear, for close quarters, leaning and grenades.
- A range with dummies and steel plates at 25, 50, 75, 100 and 150 studs, for checking damage, drop and zeroing.
- A vault course with walls from knee to head height.
- Penetration walls in different materials, so you can see what each gun gets through.
- A yard for explosives.
You spawn in front of the house. Delete it before you publish, or move it somewhere players can't reach.
Testing with several players
One player can't test team rules, revives, friendly fire, hit registration or pickups. In Studio, open the Test tab, set the number of players (2 or 3 is enough) and press Start. You get a server window and a window per player. Put the players on different teams to test damage, the same team to test revives.
Testing lag
Studio can add fake lag: File > Studio Settings > Network > Incoming Replication Lag. Try 150 to 250 ms: hits should still land where you aim, because of lag compensation.
Studio only tools
| Key | What it does |
|---|---|
| F3 | Performance readout. |
| F4 | Live recoil and handling tuner for the gun in your hands. See Recoil and sway. |
| F6 | Weapon self test. |
None of these work in a live game, so you can leave them alone.
Performance #
XSF is built to run on phones as well as PCs, but a busy firefight with 20 players is a lot of bullets, sounds and particles. These are the levers.
For players
- Graphics modes (settings, EFFECTS tab): Auto picks Full, Balanced or Performance for each device and steps down if a fight gets heavy. Performance cuts shadows, post effects, particles, brass and bullet holes. Start everyone somewhere else with
Graphics.Default. See Graphics modes. - Lens and grain off saves a little on the Bodycam style.
- Stress effects lower means less blur when hurt or suppressed.
For you
- Fewer weapons in the place file means less to download when players join. Delete weapons you'll never use (folder and model).
- Tracers.
Fire.TracerEveryof 3 is fine. Every round being a tracer (1) on twenty machine guns is not. - Pickups. Lower
Pickups.Lifetimein big servers so the floor doesn't fill with guns. - Unanchored parts. Bullets and blasts push loose parts. Hundreds of loose parts in a firefight is expensive for physics, so anchor what doesn't need to move.
- Use F3 in Studio, or
Debug.Overlay = truefor a live test, to see what a busy moment costs.
What XSF already does
- Every request is rate limited and checked on the server, so a modified client can't flood it. See Anti-cheat.
- The server's own cost is tiny and the same every frame: shots go out in batches, poses are packed into one number per player and only sent when they change, hit checks reuse their memory instead of making garbage, and movement checks are spread across frames.
- Since 2.3 your servers report their frame rate and XSF's own cost to the XSF site (see Performance reports), so slowdowns in XSF get found and fixed.
- Network ping (the delay between a player and the Roblox server) depends on the player's connection and the distance to the server, not on scripts. What XSF controls is how much each frame costs, and lag compensation makes hits line up with what the shooter saw.
- The same warning is only printed once per session, so a problem in a loop won't flood Output.
Updates #
New versions go to testers first. If you used XSF while it was in beta (your account existed before 2.0), you get each new version a few days before everyone else, so you can try it and report bugs. Your account page tells you if you're one of them, and testers get the Beta Tester role in the Discord.
When a server starts, XSF checks whether a newer version is out. If there is one, it prints a line in Output, and in Studio a small note appears on screen with the version you have and the newest one. Players in a live game never see it. Download the new version from your account, and read the patch notes for what changed.
Updating from 2.0 to 2.1
2.1 doesn't change anything you set up, so this is quick. Updating from 2.1 to 2.2 is the same steps.
Updating from 2.2 to 2.3
- Note your changes. Copy anything you changed in
GameConfigand weapon Configs, or keep the old place open in another window. - Delete the old XSF (
ReplicatedStorage.XSF,ServerScriptService.XSF,StarterPlayerScripts.XSF_Boot), keeping your own weapons and models somewhere safe first. - Install 2.3 the same way as the first time.
- Put your changes back into the new GameConfig line by line. It has new sections:
UI,Ping,Graphics,Mobile,PerformanceReportsandSecurity, andAimAssist.Pull. - Check the defaults that changed:
Customization.Ownershipis now{ Attachments = false, Skins = false }: everything is unlocked. If your game sells or unlocks attachments or skins, set them back totrue.- RB is now the weapon wheel (tap still switches to the next weapon).
NextWeaponhas no default key; give it one if you want. - Middle mouse pings. If your game uses middle mouse, set
Keybinds.Pingto something else orPing.Enabled = false. - Controller aim assist now also pulls gently.
AimAssist.Pull = 0keeps only the 2.2 slowdown. - If your game moves players fast in ways the server can't see (jetpacks, client vehicles), call
XSF.AllowMovementor setSecurity.MovementChecks = false, or those players collect suspicion points.
- If you replaced XSF's HUD with
Providers.HUD = "None", consider restyling the new templates instead (Editing XSF's screens). - Run the self test (F6) and play a round.
- Note your changes. Copy anything you changed in
GameConfigand in weapon Configs, or keep the old place open in another window. - Delete the old XSF:
ReplicatedStorage.XSF,ServerScriptService.XSFandStarterPlayer.StarterPlayerScripts.XSF_Boot. Move your own weapons and gun models somewhere safe first if they're insideXSF.WeaponsorXSF.Assets.GunModels. - Install 2.1 the same way as the first time (see Install).
- Put your changes back into the new GameConfig, line by line. The new file has more written in (see New in 2.1), so don't paste the old file over it. Your old GameConfig would still work, it just wouldn't list the new options.
- Check two new defaults. The number key of the gun in your hands now puts it away (
Holster.Reselect), and Left Alt frees the cursor (Keybinds.FreeCursor). SetHolster.Reselect = falseorFreeCursor = {}if your game uses those keys for something else.Yis the Holster key. - Run the self test (F6) and play a round.
Updating to 2.0
- Save your changes first. Copy anything you changed out of your old
GameConfigand weapon Configs, or keep the old place open in another window. If you made your own weapons, note their folder and model names. - Delete the old XSF:
ReplicatedStorage.XSF,ServerScriptService.XSFandStarterPlayer.StarterPlayerScripts.XSF_Boot. - Install 2.0 the same way as the first time (see Install).
- Put your changes back. Only copy the lines you changed, into the new files. Don't paste your whole old GameConfig over the new one: 2.0 has new options (Skin slot, Ownership, Starter) that the old file doesn't have.
- Your own weapons: copy their folders back into
XSF.Weaponsand their models intoXSF.Assets.GunModels. Turn onXSF_AttFiton their models to get the new attachment fitting. - Decide on unlocks. 2.0 ships with
Customization.Ownershipon, so players only get attachments you give them. If your 1.3 game let everyone use every attachment, setOwnership = { Attachments = false, Skins = true }to keep that. - Run the self test (F6) and play a round.
Fixes inside a version
Small fixes sometimes go into the current download without a new version number. If something in the patch notes says it was fixed in your version and you still see it, download again from your account.
Old versions
2.0.0, 1.3.0 and earlier can no longer be downloaded. Games already running an old version keep working, but they won't get fixes and don't read newer dashboard settings. Docs for old versions are still up: pick the version in the box at the top of the sidebar.
Error messages #
When XSF hits a problem in Studio, it prints an orange line in Output starting with [XSF]. For problems it knows, the line says exactly what's wrong and how to fix it. Every message also links the XSF Discord in case you're still stuck. None of this is shown to players in a live game.
| Message starts with | What it means | Fix |
|---|---|---|
The gun model "X" is not in XSF.Assets.GunModels | A weapon's Model points at a model that is not there. | Put the model in XSF.Assets.GunModels, or fix Model in that gun's Config to the exact name. |
There is no weapon called "X" in XSF.Weapons | A name in Loadout, SetLoadout or Give does not match a weapon folder. | Match the folder name exactly, capitals included. |
There is a typo in GameConfig on line N | GameConfig cannot be read, so XSF cannot start. | Look at that line and the one above for a missing comma, bracket or quote. Every line inside a table ends with a comma. |
There is a typo in the Config for X on line N | Same, in one weapon's Config. | Fix that line. Only that gun is affected. |
HTTP requests are off | The license check and settings saving cannot reach the XSF site. | Game Settings, Security, turn on Allow HTTP Requests. |
Studio can't reach DataStores | HTTP is off, so settings tried to save to DataStores, which Studio blocks by default. | Turn on HTTP requests, or Enable Studio Access to API Services. |
Part of XSF's networking is missing | The server half of XSF is not running. | Make sure the XSF folder with Main in it is in ServerScriptService. |
"X" is not a Roblox key name | A key in Keybinds is misspelled. That action has no key until it is fixed. | Use the names under Keys and key names. |
Providers.Arms in GameConfig names arms that don't exist | The arms setting is misspelled. | Use "XSFRealistic" or "ACSClassic". |
A gun model has no part called Handle | A model in GunModels is missing its Handle. | Add a part named Handle where the right hand holds the gun. |
A weapon folder is missing its Anims module | A weapon was added without animations. | Copy Anims from a similar gun, or use the ACS importer. |
XSF is waiting for X, which is missing | Something was moved or renamed. | Put XSF back where the download puts it, with the original names. |
Something went wrong in X | An error XSF does not recognise. | Post the Output in the XSF Discord or open a ticket there. |
GameConfig asks for the website preset "X", but your account has no preset with that name | Preset in GameConfig names a preset you do not have. | Make a preset with that exact name on the dashboard, or fix Preset. Until then the game uses Default. |
could not apply the website settings | A value from the dashboard could not be used. | The game runs on its Studio settings. Post the full line in the XSF Discord. |
More messages
| Message | What it means | Fix |
|---|---|---|
[XSF] there is no attachment called "X" / there is no skin called "X" | A script granted or revoked something that doesn't exist. | Check the spelling and capitals against the attachment list or XSF.Skins. |
[XSF] rejected Name: reason | Only with Debug.LogRejects on. The server threw away a shot. | Nothing to fix for a few. Lots from one player can mean exploits. See Rejected. |
[XSF] This copy is XSF 2.0.0. The latest is ... | A newer version is out. | Download it from your account when you're ready. See Updates. |
[XSF] self test: the server side is missing | F6 was pressed but the server half isn't running. | Make sure ServerScriptService.XSF is in place. |
[XSF] XSF_HUD.WeaponPanel.Weapon.Ammo is missing (the ammo count). XSF keeps working without it... | An element of an XSF screen template was deleted or renamed (any screen, any element; the line names it). | Add an element of that type and name where the line says, or copy it from the stock template in a fresh XSF download. See Editing XSF's screens. |
[XSF] ... is a Frame, XSF needs a TextLabel there ... | A template element has the right name but the wrong class. | Replace it with the class the line asks for (keep the name). |
[XSF] ... is missing (or is not a ...), so these items use the stock look. | The hidden example of a list (hotbar Slot, backpack Tile, settings Row) is gone. | Put a hidden element of that class and name back in the list, or ignore it and keep the stock look. |
[XSF] XSF.UI.Theme: there is no preset called "X", using Tactical. | A typo in Theme.Preset. | Use Tactical, Arcade, Military or Minimal. |
[XSF] suspicion Name +6 (speed 61 studs/s) = 6 | Only with Security.LogSuspicion on. The anti-cheat added points. | Nothing to fix. See Anti-cheat for what each reason means. |
[XSF] finger grip failed / hand animations failed | The hands couldn't be fitted to a gun model. | Usually an unusual custom model. The gun still works. Try XSF_GripWrap, or post the model in the Discord. |
License messages
| Message | What it means | Fix |
|---|---|---|
| Checking the XSF license... | Normal, shows for a moment when a server starts. | Nothing. |
| This copy of XSF has no license key yet. Paste the license key from your XSF account. | The file doesn't have your key in it. Downloads from your account always do, so this usually means the copy came from somewhere else. | Download XSF again from your account, or paste the key from your account page into the box. The place remembers it after that. |
| That is not an XSF license key. / This license key does not exist. | The key in the copy (or the one typed in the box) isn't a real XSF key. | Download XSF again from your account so your own key is inside it. |
| This license key has been altered. | The key in Shared.Key was changed by hand, so it no longer matches the account it was made for. | Download a fresh copy from your account. Don't edit the Key module. |
| This XSF license has been revoked. | The account's license was switched off, usually for sharing or reuploading XSF. | Check your account or ask in the Discord. |
| This game's XSF license was revoked by its owner. | This game was revoked on the dashboard's Games tab. | Press Restore on that game in the dashboard. |
| Could not reach the XSF license server. / The license server had a problem. | The XSF site didn't answer in time. XSF retries on its own several times before saying this. | Usually nothing. A server that is already running is never stopped because the site is down, and a new live server that passed a check in the last 3 days starts normally anyway (Studio playtests always ask the site). It keeps retrying every 20 seconds until the site answers. |
| Too many wrong license keys from this server. | The server sent a lot of keys that don't exist in a short time. | Fix the key (download a fresh copy) and wait an hour. Real keys are never limited, however many servers your game runs. |
| HTTP requests are off. | Allow HTTP Requests is off in Game Settings. | Game Settings > Security > Allow HTTP Requests, then save and press Play. |
The same message is only printed once per session, so a problem in a loop won't flood Output.
Troubleshooting #
The problems people actually run into, most common first. Not listed here? Ask in the XSF Discord or open a ticket on the support page.
Output says Infinite yield possible on ReplicatedStorage:WaitForChild("XSF")
The XSF folder isn't directly inside ReplicatedStorage, so XSF_Boot waits for it forever and no gun ever shows up. It usually ended up one level too deep (ReplicatedStorage.ReplicatedStorage.XSF) or is still inside XSF_Release in Workspace. Drag the XSF folder itself into ReplicatedStorage, and check the other two pieces the same way (see step 5 of Install).
The game looks blurry or grainy
Usually the map's own DepthOfField in Lighting (it blurs things past a set distance) plus the Bodycam grain. Since 2.3 the gun in your hands stays sharp and the grain is lighter. Lower FarIntensity on the DepthOfField or delete it to lose the far blur, or set Camera.Style = "Standard" for no grain at all. Players can turn off Lens and grain in EFFECTS. See Grain and blur.
Something disappeared from the HUD after I edited it
XSF finds elements by name, so a renamed or deleted element simply stops showing. Output has a line naming exactly which one and where it should be. Rename it back or copy it from a fresh download.
My edits to a screen don't show up
Edit the ScreenGui inside ReplicatedStorage.XSF.UI, not a copy in StarterGui or PlayerGui. A copy you dragged into StarterGui to look at also shows on top of XSF's own, so delete it when you're done.
Players get suspicion points in my vehicles or launch pads
XSF can't see movement your client scripts cause. Call XSF.AllowMovement(player, seconds) before the move, or set Security.MovementChecks = false. Moves made by server scripts are forgiven by themselves.
The crosshair rows are missing from the HUD tab
UI.Crosshair.Allowed is false in your GameConfig.
Nothing happens when I press Play
Check the three pieces are in the right places (see Install) and read Output for lines starting with [XSF]. If there's nothing from XSF at all, XSF_Boot probably isn't in StarterPlayerScripts.
It says HTTP requests are off
Game Settings > Security > turn on Allow HTTP Requests, save, then press Play again. In a published game, the setting has to be on for that game (it's saved with the game, not with Studio).
I spawn without guns
- The names in
Loadoutmust match the folder names inXSF.Weaponsexactly, capitals included. Output names the one it couldn't find. - Did you add
GiveLoadout = false? Then XSF waits for your script to give guns. - Is the loadout set on the dashboard to an empty list? Check the Weapons tab of the preset this game uses.
My SetLoadout gets replaced by the normal loadout
Your script ran before XSF handed out the default loadout. Add task.wait() before SetLoadout in CharacterAdded, or add GiveLoadout = false to GameConfig.
Another gun system is fighting XSF
Remove the other system's scripts. Two frameworks can't both control the camera and the mouse. Search the Explorer for leftovers (ACS_Engine, ACS_Framework, FE Gun Kit scripts in StarterPlayerScripts and StarterCharacterScripts).
The gun drifts away from the centre of the screen
That's FreeAim, the Bodycam style gun movement. Aiming down sights centres it. Turn it off or tune it in Gun movement.
My walk speed changes don't work
XSF sets WalkSpeed every frame. Change Movement.WalkSpeed and the other speeds in GameConfig (or on the dashboard) instead of the Humanoid.
Players can't open the loadout screen
- Check
Customization.Enabled. - With
Access = "Workbench", they have to be at a workbench. - They need to be holding a gun or a knife. The screen says "Take out the gun or knife you want to work on first." otherwise. Grenades don't count.
Every attachment shows as LOCKED
That's the unlock system, on by default in 2.0. Give players attachments with XSF.GrantAttachment, list free ones in Customization.Starter, or turn it off with Customization.Ownership = { Attachments = false, Skins = false }. See Unlocks.
A gun keeps jamming
Malfunctions.Enabled is on. Lower Malfunctions.Chance or turn it off. Long full auto bursts and suppressors raise the odds.
My dashboard changes don't show up in my game
- The game must run XSF 1.3 or newer. The Games tab warns about older games.
- Servers read the dashboard when they start. In Studio, stop and press Play again. In a live game, new servers get it.
- Check which preset the game uses on the Games tab, and that you edited that preset (the Editing preset picker).
- If the setting has a STUDIO tag, the game changed it in Studio and keeps its own value. Set it back to the shipped value in Studio to let the website decide.
Teammates can hurt each other (or enemies can't)
Damage.FriendlyFireis the switch.- XSF uses Roblox Teams. Players with no team, or on a team with
Neutralbehaviour, can always be hurt. Make sure your team script actually setsplayer.Team.
Players can't hurt each other at all right after spawning
That's the spawn ForceField. XSF respects it. Lower Duration on your SpawnLocations if it's too long.
Bullets go through a wall they shouldn't
The wall is thin, or its material is cheap to shoot through (glass, wood, plastic). Make it thicker, change its material, or set XSF_PenetrationCost = 100 on it. See Bullets and walls.
The hand covers part of my gun
Use Handling.LeftHandShift for the support hand and the grip attributes for the shooting hand. See Hands and grips.
A skinned gun shows a second copy of itself
That was a bug in the very first 2.0.0 download. Download XSF again from your account and replace Client (or reinstall).
Settings don't save while testing
Make sure Allow HTTP Requests is on. Without it settings fall back to DataStores, which also need Enable Studio Access to API Services in Studio. Also check SettingsSave in GameConfig isn't "Off".
Unlocks are gone after rejoining
Unlocks aren't saved unless you save them. Set Customization.SaveOwnership = true, or save XSF.GetOwned yourself. Studio also needs Enable Studio Access to API Services for DataStores to work.
Questions people ask #
Is XSF free?
Yes. Use it in as many of your own Roblox games as you like, free of charge, including games that make money. Your license is tied to your account, and every game that runs it shows up on your dashboard.
Can I use it in a game I sell or monetize?
Yes, gamepasses, developer products and paid access are all fine. What you can't do is share, sell, reupload or give away XSF itself (including on the Creator Store or the Toolbox), or share your license key. See the terms.
Can I edit the code?
GameConfig, the weapon Configs, the two API modules and, since 2.3, all the UI code (the HUD, crosshair, hotbar, backpack, settings menu, loadout screen, weapon wheel, phone buttons, pings, graphics modes, death screen and the Theme) are readable and editable. The rest is protected. Everything people usually want to change is reachable through those, the attributes and the Damage.Modify hook. If you need something that isn't, ask in the Discord, it might become an option.
Does XSF send anything about my players?
No personal data. The license check sends your key, game id and XSF version. Since 2.3, live servers also send an anonymous performance summary every 5 minutes (frame rates, counts, XSF errors, never names or user ids), which you can turn off with PerformanceReports = false. Player settings are saved on the XSF site only if SettingsSave says so. See Performance reports.
Does XSF have anti-cheat?
The server decides every hit and checks every request, and since 2.3 it keeps a suspicion score per player you can act on. It never kicks or bans by itself. See Anti-cheat and suspicion.
Can I change how the HUD looks?
Yes, without code: every screen is a ScreenGui in ReplicatedStorage.XSF.UI, and a Theme module switches the whole look. See Editing XSF's screens.
Can I use ACS and XSF together?
No. Two gun systems both try to control the camera. Remove the other kit when you install XSF. You can bring ACS guns over with the importer.
Does it work with R6?
Yes, R6 and R15 both work. On R15, other players see more detail (crouch lowers the hips, for example).
Does it work on mobile and console?
Yes, with full touch and controller controls. See Mobile and console.
Can I add my own guns?
Yes. A model with a Handle, an AimPart and a Muzzle, plus a copied weapon folder. See Adding a gun.
Can I make a third person game with it?
No, XSF is built around first person. Other players see your full body and gun in third person, but your own camera is always first person.
Can I have vehicles, or a gun on a vehicle?
XSF doesn't have vehicles. You can use your own vehicle system next to it: turn XSF's input off with SetInputEnabled(false) while driving, and back on when the player gets out. Damage to and from your vehicles can go through XSF.Damage and XSF.Explode.
How do I make a zombie game?
Zombies are NPCs with a Humanoid, and XSF damages them automatically. Write the zombie movement yourself (Humanoid:MoveTo toward the closest player), and use XSF.Damage when a zombie reaches someone. XSF.Killed tells you who killed which zombie, for points.
Will an update break my game?
Updates are never pushed into your game automatically. Your game keeps the version you installed until you install a new one yourself, and the patch notes say what changed before you do.
Where's the source of the gunshot sounds?
Roblox's free audio library and sounds made for XSF, all usable in any game.
Support #
It's the fastest way to get help. Ask setup questions, report bugs and hear about updates first.
- Support tickets: open one from the support page while signed in, or DM the XSF bot on Discord. Same team, same tickets, and you can see your past ones and reopen them.
- Email: XSF@beirutsites.com.
Bug reports help a lot. Include what you did, what happened, what you expected, your XSF version, and the Output log if you can. When XSF hits a problem in Studio it prints what went wrong in Output, and how to fix it when the cause is a known one, so that log usually answers the question on its own.
Performance reports #
Since 2.3 each live server sends the XSF site a small performance summary every 5 minutes. It's how lag gets found and fixed in XSF updates: if a version makes servers slower on big maps, or a phone model struggles with an effect, it shows up in these numbers before anyone has to report it.
What is sent
| Part | Contents |
|---|---|
| The game | Your license key (to know whose game it is), the game id, place id, the server's job id, XSF version, whether it's Studio. |
| Players | Only counts: average and highest player count in the 5 minutes, and the server's max players. |
| Server | Average and worst frame rate, the slowest frame, how many milliseconds per frame XSF's own loops took (weapons, lag compensation, poses, anti-cheat) and how many bullets and hit checks it handled per second, plus Roblox's own heartbeat and physics time, network in and out, memory and instance count. |
| Players' devices | A spread, never per player: the median frame rate, the slowest 10% and fastest 10%, the share of time under 30 fps, how many were on PC, phone, tablet, console or VR, and which graphics mode they used. Each client sends its own average to its server once a minute; the server only forwards the combined numbers. |
| Errors | Up to 12 different errors from XSF's own scripts (server and client), with how many times each happened. Errors from your own scripts are never included. |
Never sent: player names, user ids, chat, what anyone did in the game, your scripts or your map.
When
- The first report goes after a full 5 minutes, so a quick Studio test sends nothing.
- A server that closes after running more than a minute sends a last one.
- Only when the license check has passed.
- It's one HTTP request every 5 minutes per server, and the numbers are added up as the game runs (two numbers in a table per loop), so it costs nothing you could measure.
Turning it off
PerformanceReports = false,That stops both the server reports and the once a minute frame rate message each client sends to its server.
Testing it in Studio
In Studio you can send one straight away from the command bar while playing (server side): game.ServerStorage.XSF_ReportNow:Invoke(). It returns the site's answer, 200 {"ok":true} when it arrived. If it says not sent (reports off or no license yet), reports are off or the license check hasn't passed yet.
XSF