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.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.ParentGroup and Audio.StepSets (your own footsteps per floor material). Per gun: Sounds.Fade and Sounds.Tail, and lists of takes for FireSuppressed and FireThird. See Audio.
  • Holster: empty hands, the number key rule and the hands. See Holster.
  • Customization.Required (slots players can't leave empty) and Customization.Access = "Off". See Customization.
  • SettingsSave: save player settings on the XSF site, only in your game, or not at all. UseWebsite = false runs 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.Modify and Debug.LogRejects written in, so you don't have to look them up.

More for your scripts

  • Server: GetInventory, GetAmmo, GetAttachments, SetCustomizeCheck, a from and weapon option on Damage, and the Equipped, Holstered and Reloaded events. See Server scripts.
  • Client: IsDown, GetMovementState, OnMovement, AddSpeedModifier, SetMenuOpen, SetHUDVisible, the kit functions (GetKit, SetKit and 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 LoadCharacter while one was cooking.
  • SetLoadout left 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 #

Every XSF 2.0 weapon on an armory wall

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.1.0.rbxmx. 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:

What is inside XSF_Release
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_MODEL

3. 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_ReleaseDrag what's inside it toWhat it is
ReplicatedStorageReplicatedStorageThe XSF folder with GameConfig, Weapons, Assets, Client and Shared. Both players and the server read it.
ServerScriptServiceServerScriptServiceA 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.
StarterPlayerScriptsStarterPlayer, then StarterPlayerScriptsXSF_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)ServerStorageXSF_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)WorkspaceXSF_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.
Yes, there are two folders called XSF. That's on purpose. One is the shared half, one is the server half. You never have to tell them apart, because each one is already sitting inside the folder named after where it goes. The XSF from the ReplicatedStorage folder goes to ReplicatedStorage, and the XSF from the ServerScriptService folder goes to ServerScriptService.

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

  1. Delete the now empty XSF_Release folder from Workspace. Leaving it there doesn't break anything, but it's clutter.
  2. 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.
  3. 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:

Explorer
ReplicatedStorage
└── XSF                  (GameConfig, Weapons, Assets, Client, Shared ...)
ServerScriptService
└── XSF                  (Main, Server, Weapons, Damage ...)
StarterPlayer
└── StarterPlayerScripts
    └── XSF_Boot

Press 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.

Nothing happens? Open the Output window (View > Output) and look for an orange line starting with [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.

WhereWhat it isEdit it?
ReplicatedStorage.XSF.GameConfigYour settings. Every option XSF has, with comments.Yes, this is the main one.
ReplicatedStorage.XSF.WeaponsOne 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.GunModelsThe 3D gun models, one per weapon.Add your own models here.
ReplicatedStorage.XSF.Assets.AttModelsAttachment models: sights, muzzle devices, grips, lights.Add your own attachments here.
ReplicatedStorage.XSF.Assets.GrenadeModelsGrenade, C4 and claymore models.Rarely.
ReplicatedStorage.XSF.Assets.ProjectilesThe rocket and 40mm round models.Rarely.
ReplicatedStorage.XSF.Assets.ArmModelThe first person arms.No.
ReplicatedStorage.XSF.Assets.WorkbenchThe workbench model for Customization.Access = "Workbench".Copy it into your map.
ReplicatedStorage.XSF.ClientEverything that runs on each player. API is the module your LocalScripts use.No, except reading API.
ReplicatedStorage.XSF.SharedCode both sides use: ballistics, attachments, skins, the stock copy of every config.No.
ReplicatedStorage.XSF.VersionThe version string, "2.1.0".No.
ServerScriptService.XSFServer code. Server is the module your own Scripts use.No, except reading Server.
StarterPlayerScripts.XSF_BootStarts XSF for each player.No.
ServerStorage.XSF_ToolsThe 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:

FolderWhat's in it
XSF_ThrownGrenades, C4, claymores and rockets in flight or on the ground.
XSF_PickupsDropped guns and ammo boxes.
XSF_ThirdPersonThe guns other players hold, as everyone sees them.
XSF_Local, XSF_AudioClient 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:

XSF.GameConfig
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.

XSF.GameConfig
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:

Script in ServerScriptService
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_Armor on characters, see How damage works.
  • Unlocks. By default players only own the FMJ ammo and each gun's own attachments. That's what you want if you plan to sell or unlock attachments later. If you want everyone to have everything from the start, set Customization.Ownership = { Attachments = false, Skins = false }. 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 by XSF_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

  1. Your client spawns the bullet at the muzzle, plays the shot, kicks the camera and starts simulating the round with gravity and speed.
  2. It tells the server what it fired and where it was pointing.
  3. 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).
  4. The server simulates the same bullet through that rewound world, through walls if it has the power, and finds what it hit.
  5. Damage is worked out from the body part, the gun, the ammo, distance, walls and armour, then applied. See How damage works.
  6. 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:

  1. Built in defaults (in XSF.Shared.Defaults and XSF.Shared.WeaponDefaults).
  2. The website preset for this game, if there is one.
  3. 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

ThingUnit
Distances, ranges, radiiStuds
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 rateRounds per minute
Movement speedsStuds per second, the same as Humanoid.WalkSpeed

Website dashboard #

Open xsfshooter.vercel.app/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.

Your games need XSF 1.3 or newer to read the dashboard. Older versions keep working exactly as before, they just ignore it. The Games tab warns you about any game still on an older version.

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.

  1. Game style. Picks a starting point for movement, health, aiming and combat. Each card lists what it changes, so nothing happens behind your back.
  2. 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.
  3. 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.
  4. 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.

StyleWhat it is forWhat it sets
TacticalSlow, 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
BodycamLooks 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
MilsimAs 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"
ArcadeFast 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 royaleBig 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 defaultsLeave 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:

GroupSetting on the websiteGameConfig optionXSF defaultRange
CameraCamera styleCamera.StyleBodycamStandard, Bodycam
CameraBodycam field of viewCamera.BodycamFov10080 to 120 °
CameraField of viewCamera.FieldOfView8060 to 110 °
CameraCamera bobCamera.Bob10 to 2 x
CameraStrafe tiltCamera.Tilt10 to 2 x
CameraScreen kick when firingCamera.RecoilKick10 to 2 x
CameraFirst person armsProviders.ArmsRealistic glovesRealistic gloves, Classic blocky
MovementWalk speedMovement.WalkSpeed126 to 30 studs/s
MovementSprint speedMovement.SprintSpeed2110 to 45 studs/s
MovementSpeed while aimingMovement.AimSpeed7.53 to 20 studs/s
MovementSprint staminaMovement.Stamina71 to 60 s
MovementStamina refill timeMovement.StaminaRegen4.50.5 to 30 s
MovementSlidingMovement.Slide.EnabledOnOn or Off
MovementSlide speedMovement.Slide.Speed3015 to 60 studs/s
MovementShoot while slidingMovement.Slide.CanShootOnOn or Off
AimingFree aimFreeAim.EnabledOnOn or Off
AimingFree aim widthFreeAim.Yaw91 to 25 °
AimingFree aim heightFreeAim.Pitch51 to 20 °
AimingGun weight when turningMotion.Inertia10 to 2 x
AimingGun bobMotion.Bob10 to 2 x
AimingBreathing swayMotion.Breath10 to 2 x
Health and revivingDowned stateDowned.EnabledOnOn or Off
Health and revivingBleed out timeDowned.BleedOutTime455 to 180 s
Health and revivingRevive timeDowned.ReviveTime51 to 20 s
Health and revivingHealth after reviveDowned.ReviveHealth351 to 100 hp
Health and revivingOnly teammates can reviveDowned.TeamOnlyOnOn or Off
Health and revivingDowned players can shootDowned.AllowFiringOffOn or Off
Health and revivingDrag downed teammatesDowned.DragOnOn or Off
CombatFriendly fireDamage.FriendlyFireOffOn or Off
CombatHitmarkersDamage.HitmarkersOffOn or Off
CombatGun jamsMalfunctions.EnabledOffOn or Off
CombatAuto reloadControls.AutoReloadOnOn or Off
CombatAttachments menuCustomization.EnabledOnOn or Off
CombatWhere attachments can be changedCustomization.AccessAnywhereAnywhere, Only at a workbench
StancesCrouchingStances.CrouchOnOn or Off
StancesGoing proneStances.ProneOnOn or Off
StancesLeaningStances.LeanOnOn or Off
Hotbar and screensHotbar slotsBackpack.HotbarSize51 to 9 slots
Hotbar and screensHotbar on screenBackpack.HotbarAlwaysAlways, Only when switching
Hotbar and screensAmmo and health readoutProviders.HUDXSF minimalXSF minimal, None
Hotbar and screensLow effects by defaultPerformance.LowEffectsOffOn or Off
SoundXSF footstepsAudio.FootstepsOnOn or Off
SoundBackground soundAudio.AmbienceOnOn or Off
Controller and phoneAim assistAimAssist.EnabledOnOn or Off
Controller and phoneAim assist strengthAimAssist.Slowdown0.550.2 to 1 x
Controller and phoneController vibrationRumble.EnabledOnOn 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 websiteWeapon Config fieldRangeShown for
Head damageDamage.Head1 to 1000 Guns and knives
Body damageDamage.Torso1 to 1000 Guns and knives
Arm and leg damageDamage.Limb1 to 1000 Guns and knives
Fire rateFire.RPM20 to 1500 rounds/minGuns only
Magazine sizeAmmo.Magazine1 to 200 roundsGuns only
Spare ammoAmmo.Reserve0 to 1000 roundsGuns only
Bullet speedFire.MuzzleVelocity100 to 2000 m/sGuns only
Time to aimHandling.AimTime0.05 to 1.5 sGuns only
RecoilRecoil (every kick and climb value, multiplied)0 to 3 xGuns 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

  1. If GameConfig has Preset = "Name", that preset.
  2. Otherwise, the preset picked for the game on the Games tab.
  3. Otherwise, the preset called Default.
  4. 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.
XSF.GameConfig
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.

XSF.GameConfig
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 Recoil table. 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

  1. Leave GameConfig as it ships.
  2. Do all your tuning on the website, testing with Play in Studio.
  3. 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: Loadout falls back to just { "HK416" }, Camera.Style to "Standard" and Audio.Ambience to false.
  • 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", not Bodycam.
  • true, false and nil are lowercase and have no quotes.
  • Brackets have to match. Every { needs a }.
  • Names are case sensitive. walkspeed is not WalkSpeed, 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 and wrong
-- 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.

GameConfig
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.

NameWeaponClassNotes
HK416HK416Rifle5.56. Controllable, gets lively on full auto.
M4A1M4A1Rifle5.56. Quick, flat and easy to keep on target.
AK47AK-47Rifle7.62x39. Hits hard and climbs hard.
AK12AK-12Rifle5.45. A modern AK that stays flatter.
AKS74UAKS-74URifleShort barrel AK. Fast handling, wild up close.
HK33HK33Rifle5.56 roller delayed, a harder shove per shot.
SCARSCAR-HRifle7.62 NATO. Big hits, slower rate.
G36CG36CRifleCompact 5.56, very steady.
AUGSteyr AUGRifleBullpup 5.56.
FAMASFAMASRifleBullpup with a very high rate of fire.
TAR21TAR-21RifleBullpup 5.56, short and quick to aim.
GALILGalilRifleIsraeli AK pattern rifle.
FALFN FALRifle7.62 NATO battle rifle. Heavy punch.
M14M14Rifle7.62 DMR. Pairs well with zeroing.
M249M249RifleBelt fed 5.56 machine gun. Big reserve, slow reload.
PKMPKMRifleBelt fed 7.62. Heavy and brutal.
MP5MP5SMG9mm. Light and quick, low kick.
MP7MP7SMG4.6mm. Tiny and very fast.
UMP45UMP-45SMG.45 ACP. Slower, harder hitting SMG.
VectorVectorSMG.45 ACP. Very fast fire rate, stays flat.
P90P90SMG5.7mm with a 50 round magazine.
BIZONPP-19 BizonSMGHelical 64 round magazine.
UZIUziSMG9mm classic.
MAC10MAC-10SMGSpits bullets, hard to control.
THOMPSONThompsonSMG.45 drum fed classic.
MP40MP40SMG9mm, slow and steady.
R700Remington 700SniperBolt action. Heavy, slow to settle, hits very hard.
AWMAWMSniper.338 Lapua. One chest hit drops most targets.
KAR98Kar98kSniperBolt action, reloads with stripper clips.
MOSINMosin-NagantSniperBolt action, reloads with stripper clips.
SVDSVD DragunovSniperSemi auto 7.62x54R.
M82Barrett M82Sniper.50 BMG anti material rifle.
M870Remington 870Shotgun12 gauge pump. Eight pellets per shell, damage is per pellet.
M1014M1014ShotgunSemi auto 12 gauge.
SPAS12SPAS-12ShotgunPump 12 gauge.
DBSDouble barrelShotgunTwo shells, fast and loud.
AA12AA-12ShotgunFull auto 12 gauge.
G17Glock 17Pistol9x19. Flat shooting and fast to recover.
USPUSPPistol.45. Snappy flip, quick recovery.
M1911M1911Pistol.45 classic.
M92Beretta M9Pistol9mm, smooth.
CZ75CZ 75Pistol9mm, fast follow ups.
FIVESEVENFive-seveNPistol5.7mm with 20 rounds.
DeagleDesert EaglePistol.50 AE hand cannon. Huge hits, huge kick.
R686S&W 686Pistol.357 revolver with a swing out cylinder and speedloader.
KaBarKa-BarMeleeThe classic fighting knife.
M9M9 BayonetMeleeLonger reach, a little slower.
KarambitKarambitMeleeShort reach, very fast hooking cuts.
BalisongButterfly knifeMeleeLight and quick.
BOWIEBowie knifeMeleeBig blade, heavy cuts.
KUKRIKukriMeleeForward weighted chopper.
KnifeCombat knifeMeleeThe original XSF knife.
M67M67 fragGrenadeFragmentation grenade.
M18M18 smokeGrenadeSmoke grenade.
FlashbangM84 flashbangGrenadeStun grenade.
C4C4ExplosiveThrown, sticks to what it hits, set off with right click.
ClaymoreClaymoreExplosivePlaced mine that fires a fan at the first enemy in front.
RPG7RPG-7LauncherRocket with back blast. Reloads from the muzzle.
M320M320Launcher40mm grenade launcher on a real arc.

How slots work

  • The first Backpack.HotbarSize weapons (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:

Script in ServerScriptService
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.

OptionDefaultWhat 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

OptionDefaultWhat it does
Presetnilnil 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.
UseWebsitetruefalse 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

OptionDefaultWhat 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.

OptionDefaultWhat it does
Movement.WalkSpeed12Normal walking speed.
Movement.SprintSpeed21Sprinting speed. Sprinting lowers the gun, so you can't fire mid sprint.
Movement.AimSpeed7.5Speed while aiming down sights.
Movement.ReloadSpeed10Speed while reloading.
Movement.CrouchMultiplier0.55Crouched speed is whatever speed you'd have, times this. Crouch walking is 12 x 0.55 = 6.6.
Movement.ProneSpeed3.2Crawling speed when prone.
Movement.Stamina7Seconds of sprint from a full bar.
Movement.StaminaRegen4.5Seconds to refill the bar from empty.
Movement.SprintReloadfalsetrue 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.EnabledtrueTurns sliding on or off. With it off, crouch while sprinting just crouches.
Movement.Slide.Speed30Speed at the start of the slide.
Movement.Slide.EndSpeed7Speed it slows down to by the end.
Movement.Slide.Duration0.8How long a slide lasts, in seconds.
Movement.Slide.Cooldown0.7Seconds before you can slide again.
Movement.Slide.CanShoottrueAllow 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.

OptionDefaultWhat it does
Camera.Style"Bodycam""Standard" or "Bodycam" (wide lens, vignette, grain, bleached colour). Delete the line and it falls back to Standard.
Camera.BodycamFov100Field of view used by the Bodycam style. Players can't change this one.
Camera.FieldOfView80(player) Field of view in the Standard style.
Camera.Sensitivity1(player) Mouse sensitivity multiplier. 1 matches Roblox's own feel.
Camera.AimSensitivity0.85(player) Extra multiplier while aiming.
Camera.GamepadSpeed260(player) Controller look speed, degrees per second at full stick.
Camera.GamepadCurve1.8Controller stick curve. Higher gives finer control near the centre and faster turns at the edge. 1 is linear.
Camera.TouchSpeed0.28Mobile look speed, degrees per pixel dragged.
Camera.EyeHeightnilCamera 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.Tilt1(player) The view leans into strafes. 0 is off, 1 normal, above 1 stronger.
Camera.Bob1(player) The view bobs as you walk.
Camera.RecoilKick1(player) The view kicks when you fire. This is only the camera, the gun still recoils.
Camera.Landing1(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.

OptionDefaultWhat it does
Motion.Inertia1The gun lags behind when you turn and settles after. Heavier guns lag more.
Motion.Lead1The muzzle swings ahead into the direction you're turning, like you're leading with your arms.
Motion.Bob1The gun bobs as you walk and run.
Motion.Breath1Slow 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.

OptionDefaultWhat it does
FreeAim.EnabledtrueOn or off. Off locks the gun to the centre of the screen like a normal shooter.
FreeAim.Yaw9How far the gun can swing left or right before the body turns, in degrees.
FreeAim.Pitch5How far it can swing up or down, in degrees.
FreeAim.Return0.4How 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.

OptionDefaultWhat it does
Customization.EnabledtrueTurns 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.AllowednilPer 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.BlockednilA list of attachment or skin names nobody can pick on any gun, like { "PM II", "Gold Plated" }.
Customization.Ownership{ Attachments = true, Skins = true }true means players only get what you give them with XSF.GrantAttachment and XSF.GrantSkin. Set Attachments = false to let everyone use every attachment, and the same for Skins. 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.SaveOwnershipfalsetrue: 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.RequirednilA 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.

Animations

OptionDefaultWhat 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)

OptionDefaultWhat it does
Malfunctions.EnabledfalseTurns jams on. A jammed gun shows JAMMED and won't fire until the player reloads. See Jams, sound and effects.
Malfunctions.Chance0.003Chance 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).

OptionDefaultWhat it does
Throwables.Arc0.12How far above the crosshair a normal throw leaves. 0 throws flat along the crosshair, 0.3 is a high arc.
Throwables.LobArc0.25The same for an underarm lob (holding aim while you let go).
Throwables.Lift0.12Extra upward speed on top of the aim, as a share of the throw speed.
Throwables.Gravity1Gravity 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.Bounce0.18Elasticity. 0 lands dead, 0.6 bounces around a room.
Throwables.Friction1.2Grip on the ground, 0 to 2. Low values slide on smooth floors.
Throwables.Density3How heavy it is. Heavier grenades shove loose props harder.
Throwables.Spin12How much it tumbles on the way out. 0 flies without spinning.
Throwables.RollDrag0Share 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.
XSF.GameConfig
-- floatier, higher throws that stop rolling quickly
Throwables = { Arc = 0.2, Gravity = 0.7, RollDrag = 3 },

Performance

OptionDefaultWhat it does
Performance.LowEffectsfalseThe starting value of the player's Low effects setting. Low effects cuts particles, drops brass and muzzle smoke and keeps fewer bullet holes. Turn it on for games aimed at phones. Players can still turn it off. See Performance.

Stances

OptionDefaultWhat it does
Stances.CrouchtrueAllow crouching.
Stances.PronetrueAllow going prone.
Stances.LeantrueAllow leaning left and right.
Stances.LeanAngle22How far the view tilts when leaning, in degrees.
Stances.LeanOffset1.1How 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.

OptionDefaultWhat it does
Downed.EnabledtruePlayers go down before dying and can be revived. Off means they die straight away.
Downed.BleedOutTime45Seconds before a downed player dies on their own.
Downed.ReviveTime5Seconds a teammate holds the revive prompt.
Downed.ReviveHealth35Health after being revived.
Downed.DownedHealth40Extra damage a downed player can take before dying. Shooting a downed player finishes them.
Downed.TeamOnlytrueOnly teammates can revive. Ignored in games without teams.
Downed.CrawlSpeed2.5Crawling speed while downed.
Downed.AllowFiringfalseLet downed players shoot.
Downed.NPCsfalseNPCs go down too, instead of dying straight away.
Downed.DragtrueTeammates can drag a downed player to cover (H on keyboard, D-pad up on controller).
Downed.DragSpeed6Walking speed while dragging someone.

Backpack (hotbar)

The hotbar along the bottom of the screen and the backpack panel. See Hotbar and backpack.

OptionDefaultWhat 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.HotbarSize5How many slots the hotbar has, from 1 to 9. Number keys 1 to this pick them. Everything else waits in the backpack.

Damage

OptionDefaultWhat it does
Damage.FriendlyFirefalseTeammates can hurt each other, with bullets and explosions. Players with no team, or on a neutral team, can always be hurt.
Damage.HitmarkersfalseShow a small marker when your shots land. This is also the default for the player's own Hitmarkers setting.

Audio

OptionDefaultWhat it does
Audio.FootstepstrueXSF's own footstep sounds, which change with the floor material.
Audio.StepVolume1Footstep volume multiplier.
Audio.RobloxStepsfalseAlso keep Roblox's default running sound. You'll hear both, so leave this off unless you turned XSF footsteps off.
Audio.AmbiencetrueQuiet wind and distant traffic outdoors, room tone indoors. Turn it off if your game plays its own ambience.
Audio.CutFadenilSeconds 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.VoicesnilHow 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.StealFade0.08Seconds 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.ParentGroupnilA 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.StepSetsnilYour 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.
XSF.GameConfig
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.

OptionDefaultWhat it does
Holster.Enabledtruefalse turns the Holster key off. Your scripts can still holster with XSF.Unequip() (client).
Holster.ReselecttruePressing 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.HandstrueShows 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. It never moves the aim for the player, it only slows looking down while an enemy is under the barrel. Mouse and touch are never affected.

OptionDefaultWhat it does
AimAssist.EnabledtrueTurns the slowdown on or off.
AimAssist.Slowdown0.55Look speed multiplier over a target. Lower is stickier.
AimAssist.Angle2.5How close to the barrel a target has to be, in degrees.
AimAssist.Range300Furthest a target can be, in studs.

Rumble

OptionDefaultWhat it does
Rumble.EnabledtrueController vibration when firing.
Rumble.Strength1Scales the vibration.

Controls

OptionDefaultWhat it does
Controls.AimTogglefalse(player) Aim is a toggle instead of hold.
Controls.SprintTogglefalse(player) Sprint is a toggle instead of hold. Sprint always toggles on controller.
Controls.LeanTogglefalse(player) Lean is a toggle instead of hold.
Controls.CrouchToggletrueCrouch is a toggle. Set false for hold to crouch.
Controls.CrouchHoldPronetrue(player) Controller and touch: tap crouch to crouch, hold it to go prone.
Controls.AutoReloadtrueGuns 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.

OptionDefaultWhat it does
Ballistics.StudsPerMeter1 / 0.28How 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.Gravity9.81Bullet gravity in m/s². Separate from workspace.Gravity, so a low gravity map doesn't make bullets float.
Ballistics.MaxLife3Seconds a bullet is simulated before it's removed. At 880 m/s that's about 2.6 km, far more than any map.

Debug

OptionDefaultWhat it does
Debug.OverlayfalseShows a performance readout on screen for everyone, in live games too. In Studio you can toggle it with F3 instead.
Debug.LogRejectsfalsetrue prints a line in Output every time the server throws away a shot that didn't add up.

More options #

Since 2.1 most of these are written in the GameConfig that ships: GiveLoadout, Vault, Pickups, Ragdoll, Damage.Modify and Debug.LogRejects. If your GameConfig is from 2.0 they aren't in your file, but they still work. Add the lines yourself at the top level (next to Loadout and the other sections), with a comma at the end like every other line. Arms and Customization.SaveKey are still only on this page.

XSF.GameConfig
-- all of these are optional
GiveLoadout = false, -- no guns on spawn, you hand them out with XSF.SetLoadout

Ragdoll = false, -- characters don't ragdoll when they die

Vault = {
	Enabled = true,    -- false turns off vaulting and climbing
	MaxHeight = 7,     -- tallest ledge you can climb onto, in studs
	VaultHeight = 3.6, -- walls up to this high get vaulted over instead of climbed
	Reach = 3.2,       -- how far in front of you it looks for a wall
},

Pickups = {
	Enabled = true,
	DropOnDeath = true, -- a player's gun drops when they die
	AllowDrop = true,   -- players can drop the gun in their hands
	Lifetime = 90,      -- seconds before a dropped gun disappears
	MaxWeapons = nil,   -- most guns a player can carry, nil = no limit
},

Arms = {
	SleeveColor = nil,     -- a Color3 that every player's sleeves use
	PlayerClothing = true, -- false ignores what players wear
},
OptionDefaultWhat it does
GiveLoadouttruefalse stops XSF handing out Loadout when a character spawns. Use it with a main menu, classes, a shop or rounds where you give weapons yourself with XSF.SetLoadout. Players then spawn with empty hands until your script gives them something, and that includes after a respawn.
Ragdollonfalse turns off XSF's death ragdoll for players and NPCs, so Roblox's normal death happens instead.
Vault.EnabledtrueTurns vault and mantle off completely.
Vault.MaxHeight7Tallest ledge a player can climb, in studs above their feet. Raise it for parkour, lower it to stop people reaching roofs.
Vault.VaultHeight3.6Obstacles up to this height are vaulted over (you land on the far side). Taller ones, up to MaxHeight, are climbed onto.
Vault.Reach3.2How far in front of the player XSF looks for something to vault, in studs.
Pickups.EnabledtrueTurns dropping and picking up guns off. Scripts can still spawn pickups.
Pickups.DropOnDeathtrueThe gun a player is holding drops where they die.
Pickups.AllowDroptruePlayers can drop the gun in their hands with the Drop key (X).
Pickups.Lifetime90Seconds before a dropped gun is cleaned up.
Pickups.MaxWeaponsnilMost weapons a player can carry. When they're full, picking a gun up swaps it with the one in their hands, which drops on the spot. Great for battle royale (try 3).
Arms.SleeveColornilA Color3 every player's first person sleeves use, for uniforms. nil takes the colour from each player's shirt.
Arms.PlayerClothingtruefalse ignores what players wear and gives everyone olive sleeves.
Damage.ModifynilA function that can change any hit before it lands: function(humanoid, amount, info) return newAmount end. Runs on the server after armour. See How damage works.
Customization.SaveKey"XSF_Ownership"The DataStore name XSF uses when SaveOwnership is on. Change it to wipe everyone's unlocks (for a new season) or to keep test places separate.
Debug.LogRejectsfalsetrue warns in Output with the player name and reason whenever the server rejects a shot.
XSF.GameConfig
-- every player wears green sleeves
Arms = { SleeveColor = Color3.fromRGB(60, 70, 50) },

-- battle royale: three guns at most, guns stay on the floor for five minutes
Pickups = { MaxWeapons = 3, Lifetime = 300 },

Workspace attributes that change XSF behaviour live in Attributes.

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.

ActionDefaultWhat it does
FireMouseButton1, ButtonR2Shoot. Hold for automatic fire. On grenades, hold to get ready (and cook a frag) and release to throw. On knives, slash.
AimMouseButton2, ButtonL2Aim 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.
ReloadR, ButtonXReload. 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.
SprintLeftShift, ButtonL3Sprint. Always a toggle on controller.
CrouchC, ButtonBCrouch. While sprinting it slides. On controller and touch, holding it goes prone.
ProneZGo prone. Press again to get up.
LeanLeft / LeanRightQ / ELean around corners. Hold or toggle, the player picks.
FireModeV, DPadUpSwitch between the gun's fire modes (auto, burst, semi).
CheckMagT, DPadLeftLook at the magazine. The HUD says roughly how full it is (FULL, NEARLY FULL, ABOUT HALF, ALMOST EMPTY) instead of an exact count.
CheckGearG, DPadLeftShow the full HUD for a moment: health, stance, attachments, spare magazines.
InspectI, DPadDownTurn the gun over to look at it. Knives do their own tricks.
NextWeapon / PrevWeaponButtonR1 / ButtonL1Cycle weapons. The mouse wheel also does this and isn't in Keybinds.
Light / LaserL / KTurn the weapon light or laser on and off. Everyone sees them.
GearToggleButtonR3Controller: cycles light and laser.
ZeroUp / ZeroDownRightBracket / LeftBracketChange the sight zero (or the M320 range ladder) on guns that have zero steps.
SettingsP, DPadRightOpen the settings menu.
CustomizeN, ButtonYOpen the loadout screen.
BackpackB, ButtonSelectOpen the backpack panel. See Hotbar and backpack.
PickupF, ButtonYPick 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.
DropXDrop 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.
HolsterYPut the gun away (empty hands), and press again to take it back out. See Holster.
QuickLethalnot boundThrows 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.
QuickTacticalnot boundThe same for a flashbang or smoke. A weapon's Config can move it between the two with Grenade.Quick = "Lethal" or "Tactical".
FreeCursorLeftAltHold to free the mouse cursor. Clicks then go to your UI and never shoot. See Menus and the mouse.
Slot1 to Slot9One to NineJump 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.KeyCode name without the Enum.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. CheckMag and CheckGear both use D-pad left on controller by default.
  • Avoid keys Roblox already uses: Escape (menu), the slash key (chat) and F9 (developer console).
XSF.GameConfig
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.

StandardBodycam
Field of viewCamera.FieldOfView (80), and players can change itCamera.BodycamFov (100), fixed for everyone
PictureClean, Roblox's normal coloursWide lens, vignette at the edges, film grain and bleached colour, like real body camera footage
Works well withArcade, competitive, anything where players need to read the map clearlyTactical, 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.

XSF.GameConfig
-- 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.Tilt leans the view a little into strafes.
  • Camera.Bob bobs the view as you walk.
  • Camera.RecoilKick kicks the view up when you fire. The gun's own recoil still happens with this at 0, so the gun still climbs.
  • Camera.Landing dips 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 times Camera.AimSensitivity while aiming.
  • Controller speed is Camera.GamepadSpeed degrees per second at full stick, shaped by Camera.GamepadCurve so small stick movements stay precise.
  • Touch speed is Camera.TouchSpeed degrees 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:

  1. Base speed. Sprinting uses Movement.SprintSpeed (21). If not sprinting, aiming uses Movement.AimSpeed (7.5). If not aiming, reloading uses Movement.ReloadSpeed (10). Otherwise it's Movement.WalkSpeed (12).
  2. Stance. Crouching multiplies the base speed by Movement.CrouchMultiplier (0.55). Prone replaces it with Movement.ProneSpeed (3.2). A slide starts at Slide.Speed (30) and eases down to Slide.EndSpeed (7). Downed players crawl at Downed.CrawlSpeed (2.5).
  3. 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 = true players can keep running through a reload: the legs sprint, the hands keep reloading.
  • A full bar lasts Movement.Stamina seconds (7).
  • The bar starts refilling about 0.8 seconds after you stop sprinting and takes Movement.StaminaRegen seconds (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_Stance and XSF_Sliding attributes 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.Brace numbers.
  • Turn any stance off with Stances.Crouch, Stances.Prone or Stances.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. Return sets 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.
OptionDefaultWhat it does
FreeAim.EnabledtrueOn or off. Off locks the gun to the centre like a normal shooter.
FreeAim.Yaw9How far the gun can swing left or right, in degrees. Bigger feels looser and heavier.
FreeAim.Pitch5How far it can swing up or down, in degrees.
FreeAim.Return0.4How fast the body squares up behind the gun when you hold still. 0 is never, around 1 is quick.

Suggested setups

XSF.GameConfig
-- 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.AllowFiring is 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.BleedOutTime seconds (45) if nobody helps.
  • They can take Downed.DownedHealth more 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.Killed fires 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.ReviveTime seconds (5).
  • They get up with Downed.ReviveHealth (35).
  • With Downed.TeamOnly on, 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

Script in ServerScriptService
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
end

Downed 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 fasterMovement.WalkSpeed and Movement.SprintSpeed. Try 16 and 26 for arcade.
turn off sliding, prone or leaningMovement.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 screenFreeAim.Enabled = false
stop guns reloading by themselvesControls.AutoReload = false
let teammates hurt each otherDamage.FriendlyFire = true
show hitmarkersDamage.Hitmarkers = true
kill players straight away instead of downing themDowned.Enabled = false
give everyone every attachment and skinCustomization.Ownership = { Attachments = false, Skins = false }
stop players changing attachments at allCustomization.Enabled = false
let players change sights but not ammoCustomization.Slots = { Sight = true, Barrel = true, UnderBarrel = true, Other = true, Ammo = false, Skin = true }
only allow attachment changes at a benchCustomization.Access = "Workbench", then copy XSF.Assets.Workbench into your map
make guns jam now and thenMalfunctions.Enabled = true
go easy on weak phones by defaultPerformance.LowEffects = true
use each gun's own reload animationsAnimations = "Imported"
use the normal Roblox backpackProviders.Inventory = "RobloxTools"
hide XSF's HUD and make my ownProviders.HUD = "None", then use the client API
change a default keythe action in Keybinds, for example Crouch = { "LeftControl", "ButtonB" }
give guns from a menu instead of on spawnadd GiveLoadout = false, then call XSF.SetLoadout
stop dead players dropping gunsadd Pickups = { DropOnDeath = false }
turn off vaultingadd Vault = { Enabled = false }
put everyone in the same sleeve colouradd Arms = { SleeveColor = Color3.fromRGB(60, 70, 50) }
make one gun hit harderDamage in that gun's Config, or the gun's stats on the dashboard
make every gun kick lessthe Recoil slider per gun on the dashboard, or Stances.Brace for stance based steadiness
give everyone body armourset 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.

ActionKeyboard and mouseController
Fire / AimLeft / right mouseR2 / L2
ReloadRX
SprintLeft ShiftL3 (click to toggle)
Crouch / SlideC (while sprinting to slide)tap B, or B while sprinting to slide
ProneZhold B
Lean left / rightQ / Enot bound
Jump and vaultSpaceA
Next / previous weaponmouse wheelR1 / L1
Hotbar slots1 to 5 (up to 9 with Backpack.HotbarSize)
BackpackBSelect
Fire modeVD-pad up
Check magazineTD-pad left
Check gear (full HUD)GD-pad left
Inspect weaponID-pad down
Light / laserL / KR3 cycles both
Sight zero up / down] / [
Drop gunX
Holster (empty hands)Y, or the number key of the gun in hand
Quick throw lethal / tacticalnot bound, players can bind them
Free the mouse cursor (hold)Left Alt
Pick up gun / take ammoFY
Loadout screenNY
Settings menuPD-pad right
Revive a teammate (hold)FD-pad down
Drag a downed teammateHD-pad up
Give up while downed (hold 2 s)G or crouchcrouch
Set off C4right 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), lean left and right, fire mode and the loadout screen (KIT). The hotbar stays on screen so players can tap a slot.
  • Players can hide buttons they don't use in the MOBILE tab of the settings menu. Most people turn off lean and fire mode.
  • Tap crouch to crouch, hold it to go prone.
  • Low effects is a good default for phone games: Performance.LowEffects = true. Players can turn it off on better phones.
  • 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.GamepadSpeed and Camera.GamepadCurve, and players can change look speed in the menu.
  • Aim assist slows looking down a little over an enemy. It never pulls the aim. See AimAssist.
  • 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 Keybinds if 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

TabSettings
GAMEHUD (Minimal, Full or Off), centre dot, see your legs, toggle aim, toggle sprint and the other gameplay toggles
CAMERAMotion preset (Full or Comfort), field of view (Standard camera), mouse sensitivity, aim sensitivity, controller look speed, camera tilt, camera bob, recoil view kick, landing dip
WEAPONWeapon inertia, muzzle lead, weapon bob, breathing sway
EFFECTSStress effects, lens and grain, low effects (faster on weak devices), volume
CONTROLSEvery keybind. Click one and press the new key, or clear it with the X
MOBILEWhich on screen buttons show up: fire, aim, reload, jump, crouch, sprint, swap weapon, gear, lean left, lean right and the rest

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 low effects and hitmarkers. 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.

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.

KeyType
FieldOfView, Sensitivity, AimSensitivity, GamepadSpeednumber
CameraTilt, CameraBob, CameraRecoilKick, CameraLandingnumber, 0 to 2
MotionInertia, MotionLead, MotionBob, MotionBreathnumber, 0 to 2
MotionPreset"Full" or "Comfort"
AimToggle, SprintToggle, LeanToggle, CrouchHoldProneboolean
StressEffectsnumber, 0 to 1
LensEffects, LowEffects, ShowLegs, Hitmarkersboolean
Crosshairboolean, the centre dot
HudMode"Minimal", "Full" or "Off"
MasterVolumenumber, 0 to 1
Touch_FIRE, Touch_AIM, Touch_RELOAD and the other Touch_ keysboolean, whether that mobile button shows
LocalScript
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("Crosshair", true)
end

XSF.SettingChanged:Connect(function(key, value)
	if key == "*" then
		print("all settings loaded")
	else
		print(key, "is now", value)
	end
end)

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.

XSF.Weapons.HK416.Config (as it ships, minus the inspect)
-- 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.

Tune it live. In a Studio playtest, hold a gun and press F4. Sliders for recoil, sway, aim time, eye relief and mass appear. Drag them until it feels right, press PRINT TO OUTPUT, and paste what it prints into the gun's Config. F3 shows a performance readout. Both only work in Studio.

Top level

SettingDefaultWhat 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.
Modelthe folder nameThe name of the model in XSF.Assets.GunModels. Only needed when it doesn't match the folder name.
FeedmagazineHow it reloads, when it isn't a normal magazine: "Stripper" (Kar98k, Mosin), "Revolver" (686), "Breech" (M320), "Muzzle" (RPG-7). See Reloads and feeds.
ShellInsertfalsetrue loads one shell at a time, like the pump shotguns. The HUD then counts spare shells instead of magazines.
SlideTravelCFrame.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.
SlideLocktrueThe 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, ShellSizebrassColour 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.
Soundsclass soundsThe gun's own sounds. See Sounds below.

Fire

SettingDefaultWhat it does
Fire.RPM700Rounds 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.Burst3Rounds per trigger pull in Burst mode.
Fire.MuzzleVelocity850Bullet speed in meters per second. Faster bullets drop less and arrive sooner. Pistols are around 250 to 470, rifles 700 to 970.
Fire.Dispersion0.04Random spread in degrees for every shot, even when aiming. Snipers use about 0.008.
Fire.Pellets1Projectiles per shot. Shotguns use 8. Damage is per pellet.
Fire.PelletSpread0Extra spread in degrees for each pellet, added on top of Dispersion. The 870 uses 3.2.
Fire.TracerEvery3Every Nth round is a visible tracer. 1 makes every round a tracer, 0 turns tracers off (snipers, pistols and shotguns ship with 0).
Fire.Zero100The range in studs the sights are zeroed to. Bullets cross the line of sight at this distance.
Fire.ZeroStepsnilA list of zero ranges the player can step through with ] and [, like { 100, 200, 300, 400 }. See Zeroing.
Fire.Penetrationfrom ClassOverrides how much wall the bullet can go through. See Bullets and walls.

Ammo

SettingDefaultWhat it does
Ammo.Magazine30Rounds in a full magazine (or tube, belt, cylinder).
Ammo.Reserve90Spare rounds the player spawns with, on top of the full magazine.
Ammo.ChambertrueReloading 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

SettingDefaultWhat it does
Damage.Head120Damage for a head hit.
Damage.Torso45Damage for a hit to the upper or lower torso (or Torso on R6) and the root part.
Damage.Limb30Damage for arms, hands, legs and feet.
Damage.FalloffStart150Distance in studs where damage starts to drop.
Damage.FalloffEnd600Distance where it reaches its lowest.
Damage.MinMultiplier0.6What 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

SettingDefaultWhat it does
Handling.Mass3.5Weight in kilograms. Heavier guns swing slower, lag more when you turn and settle slower after recoil.
Handling.AimTime0.28Seconds to raise the sights. Magnified sights and grips change this (see Attachments).
Handling.AimFov60Field of view while aiming with iron sights or a red dot. Lower zooms in more. Magnified sights replace this with their own.
Handling.EyeRelief0.85How far your eye sits behind the sight when aiming, in studs. Lower brings the sight closer to your face.
Handling.Scopedfalsetrue 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.ScopeFill0.8For scopes, how much of the screen the scope picture fills when aimed.
Handling.HipConvergence60At the hip, the barrel is angled so bullets cross the centre of the view this many studs away.
Handling.HipOffsetCFrame.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.AimPivotnear the gripThe 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.ReloadSpeed1Speed multiplier for reload animations. 0.75 is a quarter slower (the revolver), 1.2 is 20% faster.
Handling.InspectgenericThe gun's own inspect (I), as keyframes. See below.
Handling.BoltnilBolt actions: where the hand grabs the bolt and how it turns and pulls. See Reloads and feeds.
Handling.LeftHandShiftnilMoves 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.LeftHandfound automaticallyfalse 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.

XSF.Weapons.Vector.Config
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.

KeyWhat it is
FireThe 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.
FireSuppressedReplaces Fire while a suppressor is fitted. One id or a list of takes, picked at random like Fire.
FireThirdWhat other players hear, if you want it different from what the shooter hears. One id or a list of takes.
TailThe 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, VolumeMultiply the shot (1 is unchanged).
CutStops each take after this many seconds, for samples with a long tail.
FadeSeconds 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, EjectHandling sounds. Each is a table: { Id = 123, Volume = 0.6, Pitch = 1, Cut = 0.9 }. Only Id is required.
Sounds in a weapon Config
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.1.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

NameRPMModesm/sMag / spareHead / body / limbBody shots to killFalloffKgAim timeShips with
FAL650Auto, Semi84020 / 80185 / 72 / 462150 to 500 (x0.6)4.30.3 snothing. Zero steps: 100, 200, 300, 400, 500, 600
M14700Semi, Auto85020 / 80185 / 72 / 462150 to 500 (x0.6)4.50.3 snothing. Zero steps: 100, 200, 300, 400, 500, 600
SCAR600Auto, Semi71420 / 80185 / 72 / 462150 to 500 (x0.6)3.90.33 snothing
AK47600Auto, Semi71530 / 90170 / 68 / 422150 to 600 (x0.6)3.90.32 snothing
PKM650Auto825100 / 200165 / 64 / 422150 to 500 (x0.6)7.50.45 snothing
HK33750Auto, Semi87025 / 75150 / 60 / 332150 to 600 (x0.6)3.90.32 sAcog
HK416800Auto, Semi88030 / 90150 / 60 / 382150 to 600 (x0.6)3.60.3 sEOTech, Vertical Grip, AN PEQ
M4A1850Auto, Semi88030 / 120145 / 58 / 362150 to 600 (x0.6)3.30.28 sEOTech, AN PEQ
AUG700Auto, Semi97030 / 90145 / 54 / 362150 to 600 (x0.6)3.60.3 snothing
FAMAS1000Auto, Burst, Semi96025 / 100140 / 52 / 352150 to 600 (x0.6)3.60.3 snothing
G36C750Auto, Semi85030 / 120140 / 52 / 352150 to 600 (x0.6)2.80.3 snothing
GALIL650Auto, Semi95035 / 140140 / 52 / 352150 to 600 (x0.6)40.3 snothing
TAR21850Auto, Semi91030 / 120140 / 52 / 352150 to 600 (x0.6)3.30.3 sReflex
AK12650Auto, Burst, Semi88030 / 120138 / 50 / 342150 to 600 (x0.6)3.30.3 snothing
AKS74U700Auto, Semi73530 / 120138 / 50 / 342150 to 600 (x0.6)2.70.3 snothing
M249800Auto915100 / 200130 / 50 / 342120 to 400 (x0.6)7.50.45 sBipod

SMGs

NameRPMModesm/sMag / spareHead / body / limbBody shots to killFalloffKgAim timeShips with
MAC101090Auto28030 / 120118 / 40 / 28345 to 170 (x0.6)2.80.2 snothing
THOMPSON700Auto, Semi28530 / 120118 / 40 / 28345 to 170 (x0.6)4.90.26 snothing
UMP45600Auto, Semi28525 / 100118 / 40 / 28345 to 170 (x0.6)2.50.23 snothing
Vector1100Auto, Semi29025 / 100110 / 38 / 26350 to 180 (x0.6)2.70.22 snothing
MP40550Auto40032 / 128100 / 34 / 24350 to 180 (x0.6)40.24 snothing
MP5800Auto, Semi40030 / 120100 / 34 / 24360 to 200 (x0.6)2.60.24 sSuppressor, Flashlight
UZI600Auto, Semi40032 / 128100 / 34 / 24350 to 180 (x0.6)3.50.22 snothing
MP7950Auto, Semi73540 / 16096 / 33 / 23470 to 230 (x0.6)1.90.2 snothing
P90900Auto, Semi71550 / 15092 / 31 / 22465 to 220 (x0.6)2.60.22 snothing
BIZON680Auto, Semi32064 / 19292 / 30 / 21445 to 160 (x0.6)2.70.22 snothing

Snipers

NameRPMModesm/sMag / spareHead / body / limbBody shots to killFalloffKgAim timeShips with
M82120Semi85310 / 30500 / 190 / 12011000 to 2500 (x0.9)130.6 sBipod. Zero steps: 100, 200, 300, 400, 600, 800, 1000, 1500
AWM40Bolt9365 / 25400 / 150 / 1001800 to 2000 (x0.85)6.50.5 sPM II, Bipod. Zero steps: 100, 200, 300, 400, 600, 800, 1000
KAR9845Bolt7605 / 30300 / 115 / 751500 to 1200 (x0.8)4.10.42 snothing. Zero steps: 100, 200, 300, 400, 600, 800
MOSIN45Bolt8655 / 30300 / 112 / 741500 to 1200 (x0.8)4.20.42 snothing. Zero steps: 100, 200, 300, 400, 600, 800
R70045Bolt85010 / 30300 / 110 / 801600 to 1500 (x0.8)4.40.42 sPM II, Bipod. Zero steps: 100, 200, 300, 400, 600, 800
SVD280Semi83010 / 40290 / 105 / 701600 to 1500 (x0.8)4.30.42 snothing. Zero steps: 100, 200, 300, 400, 600, 800, 1000

Shotguns

NameRPMModesm/sMag / spareHead / body / limbBody shots to killFalloffKgAim timeShips with
DBS400Semi4002 / 2466 / 31 / 18 x8 pellets118 to 70 (x0.2)3.40.3 snothing
M87070Pump4007 / 2160 / 28 / 16 x8 pellets125 to 90 (x0.25)3.60.3 snothing
AA12300Auto4008 / 3254 / 25 / 15 x8 pellets122 to 80 (x0.25)5.20.3 snothing
SPAS1275Pump4008 / 2454 / 25 / 15 x8 pellets122 to 80 (x0.25)4.40.3 snothing
M1014220Semi4007 / 2852 / 24 / 14 x8 pellets122 to 80 (x0.25)3.80.3 snothing

Pistols

NameRPMModesm/sMag / spareHead / body / limbBody shots to killFalloffKgAim timeShips with
Deagle200Semi4707 / 35190 / 70 / 45250 to 200 (x0.6)2.00.24 snothing
R686150Semi4406 / 36180 / 64 / 40245 to 170 (x0.6)1.20.22 snothing
M1911380Semi2537 / 42140 / 46 / 30340 to 150 (x0.6)1.10.2 snothing
CZ75460Semi37016 / 64100 / 34 / 24350 to 180 (x0.6)10.2 snothing
FIVESEVEN480Semi65020 / 8096 / 34 / 24360 to 200 (x0.6)10.2 snothing
M92450Semi38115 / 60100 / 34 / 24350 to 180 (x0.6)10.2 snothing
USP450Semi27012 / 6090 / 32 / 22440 to 150 (x0.6)0.90.18 snothing
G17480Semi37517 / 6880 / 28 / 19435 to 140 (x0.6)0.750.16 snothing

Knives

NameHead / body / limbRangeTime between slashesHits afterNotes
KUKRI100 / 58 / 406.2 studs0.85 s0.24 sChops harder than any other knife, and swings slowest.
BOWIE85 / 50 / 356 studs0.72 s0.2 sLong blade, heavy cuts.
M978 / 47 / 335.8 studs0.7 s0.18 sLonger reach, a little slower.
KaBar75 / 45 / 325.5 studs0.65 s0.18 sThe classic fighting knife.
Knife75 / 45 / 325.5 studs0.65 s0.18 sThe original XSF knife.
Karambit70 / 40 / 305 studs0.5 s0.18 sShort reach, very fast hooking cuts.
Balisong65 / 38 / 285 studs0.45 s0.18 sThe 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

NameTypeDamageRadiusFuseCarriedNotes
M67Frag18028 studs4.5 s, cooks in hand2Thrown at 95, lobbed at 45.
FlashbangFlashnone45 studs1.8 s after the throw2Blinds anyone looking toward it and deafens everyone nearby.
M18Smokenone22 studs2 s after the throw2Smoke lasts 35 seconds.
C4Charge30022 studswhen you set it off3Up to 4 out at once.
ClaymoreMine24020 studs, 60° fanarms after 2 s, trips at 8 studs2Up to 2 out at once. Behind it takes 10%.
RPG7Rocket260 blast, 120 direct24 studsarms after 18 studs1 + 3Speeds up from 45 to 110 m/s.
M32040mm190 blast, 90 direct16 studsarms after 14 studs1 + 676 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

SettingDefaultWhat 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.YawBias0.25Pushes 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.Back0.16How far it pushes back into your shoulder, in studs.
Recoil.Rise0.03How far it lifts up, in studs.
Recoil.Shake0.9A 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.Kick1.2How 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.Recovery0.3How fast the aim settles back down after you stop firing.
Recoil.HeatPerShot0.07How 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.HeatMax1.6The most heat it can build. At max, recoil is 2.6 times the first shot.
Recoil.HeatDecay2.2How 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.PatterngeneratedThe 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.

A custom pattern
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

SourceEffect
CrouchingAll recoil x0.8 (Stances.Brace.Crouch)
Pronex0.62
Prone with a bipodx0.42
Slidingx1.25, so it gets worse
AimingThe Recoil.Aim multipliers
Vertical GripUp kick and climb x0.88
Angled GripSideways kick and climb x0.88
CompensatorSideways x0.8
Muzzle BrakeUp x0.85, push back x0.9
SuppressorUp x0.9, push back x0.85
Subsonic ammoUp x0.9, push back x0.9
Dashboard Recoil sliderEverything 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

  1. In a Studio playtest, hold the gun and press F4.
  2. Fire full auto at a wall from 50 studs and watch where the bullet holes land.
  3. Too hard to control? Lower Climb first. Too easy? Raise it. This is 80% of the work.
  4. Drifts too much sideways? Narrow ClimbYaw or move YawBias toward 0.
  5. Gets out of hand too quickly in long bursts? Lower HeatPerShot or HeatMax.
  6. Looks too tame? Raise Pitch, Back and Kick. These change how it looks and feels without changing how hard it is to control.
  7. 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.

SettingDefaultWhat it does
Sway.InertiaMax7The most the gun can lag behind when you turn fast, in degrees.
Sway.InertiaGain0.018How much turning speed turns into lag.
Sway.InertiaSpring{ 4.2, 0.42 }How the lag settles.
Sway.InertiaRoll0.9How much the gun rolls into turns.
Sway.LeadMax, LeadGain, LeadPeek, LeadSpring6, 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.BreathAmount0.35Breathing sway while still. Snipers use 0.5.
Sway.AimMultiplier0.35All 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:

NameTypeRequired?What it's for
HandlePartYesWhere the right hand holds the gun. Everything is measured from it. Its front face should point down the barrel.
AimPartPartYesWhere your eye lines up when aiming with iron sights. Put it right behind the rear sight, centred on the bore line.
MuzzleAttachment inside HandleYesWhere bullets, flash and smoke come out.
ChamberAttachment inside HandleNoWhere the ejection port is, if you want to place it exactly.
MagPart (or model)For XSF reloadsThe 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 BoltMotor6D inside HandleNoA 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.
NodesFolder of partsFor attachmentsOne 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

  1. Put the model in XSF.Assets.GunModels.
  2. Copy the weapon folder in XSF.Weapons that'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 in Loadout.
  3. If the model's name is different from the folder name, add Model = "YourModelName" to the Config.
  4. 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.
  5. Add the folder name to Loadout and press Play. The hands find the grip on their own.
  6. 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.

AttributeTypeWhat it does
XSF_AttFitbooleantrue 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_AttScalenumberScales 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_SightRisernumberExtra height under sights, in studs (0.015 by default), for guns with tall front sights that would block the view.
XSF_ClassstringPicks 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_GripWrapbooleantrue closes the shooting hand into a fixed fist. See Hands and grips.
XSF_GripReachnumberHow far XSF may slide the hand looking for the grip. See Hands and grips.

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.

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 AimPart so 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 Muzzle attachment to the end of the barrel.
  • Attachments float or sink. Turn on XSF_AttFit, or move the part in Nodes.
  • 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.

  1. Put the gun model in XSF.Assets.GunModels.
  2. Put its ACS_Animations module anywhere in ServerStorage.
  3. Open the command bar (View > Command Bar) and paste this, changing the path and the name:
Command bar
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 a Mag part, and the imported clips are used for everything else. Set Animations = "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_AttScale on 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_Tools again 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:

SlotIncluded
SightReflex, EOTech, Aimpoint, Acog, TA33 Acog, PM II, Night Vision, Thermal
BarrelSuppressor, Flash Hider, Compensator, Muzzle Brake
UnderBarrelVertical Grip, Angled Grip, Bipod
OtherLaser, Flashlight, AN PEQ (on pistols these mount under the barrel)
AmmoFMJ, Hollow Point, Armor Piercing, Subsonic
Skin108 skins, see Skins

What each one does

AttachmentSlotEffect
ReflexSightSmall open red dot. No zoom, keeps the gun's own aim speed.
EOTechSightHolographic sight with a wide window. No zoom.
AimpointSightTube red dot. No zoom.
AcogSightAim FOV 22 (about 3x). Aiming takes 15% longer.
TA33 AcogSightAim FOV 26 (about 2.5x). Aiming takes 10% longer.
PM IISightAim FOV 12 (about 6x) with a full scope view. Aiming takes 25% longer.
Night VisionSightAim FOV 26. Image intensifier: dark rooms and night maps turn readable in green. Aiming takes 15% longer.
ThermalSightAim FOV 22. White hot thermal: people glow through smoke and shadow, the world goes grey. Aiming takes 20% longer.
SuppressorBarrelQuiet shot for everyone, no muzzle flash. 10% less climb, 15% less push back, 5% less damage and bullet speed.
Flash HiderBarrelHides the muzzle flash. No other change.
CompensatorBarrel20% less sideways recoil.
Muzzle BrakeBarrel15% less climb and 10% less push back.
Vertical GripUnderBarrel12% less climb. The support hand holds the grip.
Angled GripUnderBarrel12% less sideways recoil and 8% faster aiming. The support hand holds the grip.
BipodUnderBarrelWhile prone, recoil drops to the Stances.Brace.Bipod value (0.42) instead of 0.62.
LaserOtherVisible laser beam and dot, seen by everyone. Toggle with K.
FlashlightOtherWeapon light, seen by everyone. Toggle with L.
AN PEQOtherLaser 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:

  1. The gun has to take it. It needs a node for that slot.
  2. Your rules allow it. Customization.Slots, Allowed and Blocked in GameConfig.
  3. The player owns it. With Customization.Ownership on (the default), players only get attachments you give them, plus anything in Customization.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:

Script in ServerScriptService
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

  1. Put the model in XSF.Assets.AttModels, named exactly what it'll be called in game. It needs a part called Main.
  2. Sights also need AimPos (where the eye goes) and SightMark (the reticle). Muzzle devices need BarrelPos (the new muzzle). Lights need FlashPoint, lasers LaserPoint.
  3. Give it a line in the attachment list (the List table at the top of XSF.Shared.Attachments) with its slot and effects, the same way the built in ones are written. That module is protected, so only edit inside the List table and keep a backup. The fields are Slot, AimFov, AimTime, Velocity, Damage, Recoil = { Vertical, Horizontal, Kick }, Suppressed, NoFlash, Bipod, Laser, Light and Grip.
  4. It shows up in the loadout screen for every gun with a matching node, and works in SetAttachments and 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. Hovering an option shows exactly what it changes, as green or red bars against the gun's base: recoil, aim time, damage, bullet speed and wall penetration.

  • 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.

XSF.GameConfig
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.

AmmoDamageWallsArmourOther
FMJx1x1normalThe standard round. Free for everyone by default (Customization.Starter).
Hollow Pointx1.2x0.45armour counts 1.5 times as muchBest against unarmoured targets in the open. Bad through cover and vests.
Armor Piercingx0.9x1.9ignores 60% of armourFor armoured enemies and shooting through walls.
Subsonicx0.92x1normalBullets 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 = true the gun keeps one round in the chamber on top of the new magazine.
  • With Animations = "XSF", the hand really grabs the gun's Mag part. It works out the motion from where the magazine is on each gun.
  • Handling.ReloadSpeed speeds 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:

XSF.Weapons.AWM.Config
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,
},
FieldWhat it is
KnobWhere the bolt handle is, relative to the gun's Handle, in studs.
AxisHow far above the bore the bolt turns around.
LiftHow far the handle lifts before it pulls back, in degrees.
TravelHow 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.Chance of 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.

Low effects

Players can turn on Low effects in settings. It cuts particles, drops brass and muzzle smoke and keeps fewer bullet holes. Performance.LowEffects sets what new players start with. See Performance.

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

XSF.Weapons.KaBar.Config
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 },
}
SettingWhat it does
Melee.RangeHow far the slash reaches, in studs. 5 to 6.2 on the shipped knives.
Melee.CooldownSeconds between slashes. Lower is faster.
Melee.HitDelaySeconds into the swing when the hit lands. Bigger feels heavier.
Melee.BackstabDamage 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.
DamageHead, 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

Script in ServerScriptService
local XSF = require(game.ServerScriptService.XSF.Server)

local function knifeRound()
	for _, player in game.Players:GetPlayers() do
		XSF.SetLoadout(player, { "Karambit" })
	end
end

Grenades #

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.

GrenadeWhat it doesFuseRadius
M67 fragUp 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 has28 studs
Flashbang (M84)Blinds anyone looking toward it and deafens everyone nearby, even through walls. No damage.1.8 s after it leaves your hand45 studs
M18 smokeA thick cloud that blocks sight for 35 seconds. Thermal sights see through it.2 s after it leaves your hand22 studs

Each grenade slot starts with two (one in hand, one in reserve). Grenades bounce off walls and roll on slopes.

Grenade settings

XSF.Weapons.M67.Config
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 },
}
SettingWhat it does
Grenade.Type"Frag", "Flash", "Smoke", "C4" or "Claymore".
Grenade.FuseSeconds until it goes off.
Grenade.Cooktrue starts the fuse when the player gets it ready, not when it's thrown.
Grenade.RadiusBlast, flash or smoke radius in studs.
Grenade.DamageDamage at the centre, for frags and charges.
Grenade.DurationHow long smoke lasts.
Grenade.ThrowSpeed, LobSpeedThrow speeds for an overhand throw and an underhand lob.
Grenade.Arc, LobArc, Lift, Gravity, Bounce, Friction, Density, Spin, RollDragHow 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.ReserveHow many extra the player carries.

Throwing grenades from a script

For traps, airstrikes and scripted events:

Script in ServerScriptService
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 with Arms.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:

WhereSettingWhat it does
Attribute on the gun model in Assets.GunModelsXSF_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 modelXSF_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 ConfigHandling.LeftHandShiftMoves the support hand along the gun, in studs. Positive Z moves it back toward you.
Weapon ConfigHandling.LeftHand = falseOne handed: the support hand stays off the gun.
Weapon ConfigHandling.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

  1. Body part. The part that was hit picks the zone. Head is Head. UpperTorso, LowerTorso, Torso (R6) and HumanoidRootPart are Torso. Everything else, arms, hands, legs and feet, is Limb.
  2. Base damage comes from the gun's Damage table for that zone.
  3. 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.
  4. Falloff. Up to FalloffStart studs of bullet travel, damage is full. From there it drops in a straight line to MinMultiplier at FalloffEnd, and stays there. With the defaults (150, 600, 0.6), a hit at 375 studs does 80%.
  5. 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.
  6. Armour. XSF_Armor on the character cuts Torso and Limb damage by that share. XSF_Helmet does 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%.
  7. Your modifier. If GameConfig has a Damage.Modify function, it runs now and can change the number (see below).
  8. Downed. With Downed.Enabled, a hit that would kill puts the player down instead. After that they can take DownedHealth more damage before dying, and bleed out after BleedOutTime seconds.

Who can't be hurt

  • Teammates, while Damage.FriendlyFire is 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

Script in ServerScriptService
-- 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.

XSF.GameConfig
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.MuzzleVelocity in meters per second, converted to studs with Ballistics.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's workspace.Gravity.
  • Each shot gets a random spread of Fire.Dispersion degrees (plus PelletSpread for shotgun pellets).
  • Bullets live for Ballistics.MaxLife seconds (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:

ClassPower
Shotgun (per pellet)0.35
Pistol0.45
SMG0.55
Rifle1.3
DMR1.8
Sniper2.4
Anything else1

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:

MaterialCost per stud
ForceField0
Glass0.15
Fabric0.2
Cardboard0.25
Foil0.4
Plastic, SmoothPlastic0.55
Wood, WoodPlanks0.6
Ice0.9
Brick2.4
CorrodedMetal2.6
Concrete2.8
Grass, Cobblestone3
Metal, Slate3.2
Granite, Marble3.5
Sand, Ground, Rock, DiamondPlate4
Anything else2

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.

Command bar
-- 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
end

Damage 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".

WeaponHow it works
RPG-7The 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. Closer than 18 studs the warhead doesn't arm: it only hurts on a direct hit (120). Armed, it blasts for up to 260 in 24 studs. Reloading pushes a new warhead into the muzzle. One loaded plus three spare.
M320Fires 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 arms after 14 studs, blasts for up to 190 in 16 studs, and a direct hit does 90. The barrel swings open to reload. One loaded plus six spare.
C4Throw 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.
ClaymoreSet 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 Back times the damage.
  • Loose parts in the blast get thrown around.
  • Friendly fire rules apply. Your own explosions can always hurt you.

Explosive settings

XSF.Weapons.RPG7.Config (the important part)
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. Closer than Arm it
-- does not go off, only the direct hit (Impact) hurts
Projectile = { Speed = 45, MaxSpeed = 110, Ignite = 0.12, Burn = 0.7, Drop = 0.12, Life = 5,
	Arm = 18, Radius = 24, Damage = 260, Impact = 120, Blast = "HE", ShowRound = true },
Projectile fieldWhat it does
SpeedLaunch speed in m/s.
MaxSpeed, Ignite, BurnFor rockets: the motor lights after Ignite seconds and burns for Burn seconds, speeding up to MaxSpeed.
DropHow much gravity affects it, 1 is full gravity. The RPG uses 0.12, the M320 1.
LifeSeconds before it self destructs in the air.
ArmStuds it must travel before it can explode.
Radius, DamageBlast radius and damage at the centre.
ImpactDamage of a direct hit, armed or not.
ShowRoundShows the round sitting in the launcher while loaded.
ModelThe 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:

Script in ServerScriptService
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.MaxWeapons set, 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

Script in ServerScriptService
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 })
OptionForWhat it does
maggunsRounds in the magazine. Defaults to a full magazine.
reservegunsSpare rounds. Defaults to the gun's spawn reserve.
attsgunsAttachments 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.
MagsammoMagazines given to each gun per use. Default 2.
UsesammoHow many times the box can be used. Default 1.
Infiniteammotrue 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

Script in ServerScriptService
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.

GunZero steps (studs)
AWM, SVD100, 200, 300, 400, 600, 800, 1000
M82100, 200, 300, 400, 600, 800, 1000, 1500
R700, Kar98k, Mosin100, 200, 300, 400, 600, 800
FAL, M14100, 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.

Weapon Config
Fire = { RPM = 700, Modes = { "Semi" }, MuzzleVelocity = 850, Zero = 200, ZeroSteps = { 100, 200, 300, 400, 600 } },

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.

XSF.GameConfig
-- 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

RarityColourHow manyExamples
Commongrey18Matte Black, Olive Drab, Flat Dark Earth, Woodland, Desert Storm, Arctic, Worn Steel
Uncommongreen28Two Tone Tan, Gunmetal, Tiger Stripe, Digital Woodland, Flecktarn, Rust
Rareblue25Crimson, Cobalt, Copper, Carbon Fiber, Python, Topographic
Epicpurple25Chrome, Neon Cyan, Plasma, Damascus, Circuit Board, Magma
Legendarygold12Gold 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:

Script in ServerScriptService
local XSF = require(game.ServerScriptService.XSF.Server)
for name, skin in pairs(XSF.Skins) do
	print(name, skin.Rarity, skin.Info)
end

How skins paint a gun

A skin colours each part of the gun by its role: body, furniture (stocks and grips), metal, magazine and blade. 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

With Customization.Ownership.Skins on (the default), players only have the skins you give them. Turn it off to give everyone every skin. 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.

A skin crate
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
end

Putting a skin on for the player

Script in ServerScriptService
-- 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 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. A gun's own default attachments (from its Config) and anything in Customization.Starter are always free.

Turning it on and off

XSF.GameConfig
Customization = {
	-- players only get what you give them (the default)
	Ownership = { Attachments = true, Skins = true },

	-- everyone gets every attachment, skins still have to be earned
	-- Ownership = { Attachments = false, Skins = true },

	-- everything free for everyone
	-- Ownership = false,

	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

FunctionWhat 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.OwnsSkinThe 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.OwnershipChangedFires 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 by Customization.SaveKey, "XSF_Ownership" by default) when unlocks change and loads it when the player joins. Change SaveKey to 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, then XSF.SetOwned when they join. Do this if you already have a data system (ProfileService, ProfileStore, DataStore2), so everything saves and rolls back together.
Saving unlocks with your own DataStore
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:UserOwnsGamePassAsync on 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.

Script in ServerScriptService
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

NameWhat it is
XSF.VersionThe version string, like "2.1.0".
XSF.ConfigYour GameConfig as XSF sees it, with defaults and website values filled in. Read it, don't change it at runtime.
XSF.AttachmentsEvery attachment and its effects, keyed by name: XSF.Attachments["Acog"].AimFov is 22.
XSF.SkinsEvery 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.

ArgumentWhat to pass
playerThe Player.
namesA list of weapon folder names, like { "MP5", "USP" }. Unknown names are skipped with a message in Output. An empty list {} takes everything away.
Example
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.

Example
-- 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.

ArgumentWhat to pass
playerThe Player.
nameWeapon name, or nil for the gun in their hands.
amountRounds to add.
Example
-- 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.

Example
-- 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
end

XSF.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.

ArgumentWhat to pass
playerThe Player.
weaponWeapon name, or its slot number.
attachmentsA 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.

Example
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.

ArgumentWhat it is
playerThe Player.
weaponThe gun's name.
kitThe 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.

Script in ServerScriptService
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.

ArgumentWhat to pass
humanoidThe Humanoid to hurt.
amountDamage before armour.
attackerOptional Player who gets the credit. Friendly fire rules use it.
zoneOptional "Head", "Torso" or "Limb". Decides which armour applies. Defaults to Torso.
optionsOptional 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).

Example
-- 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" })
Example
-- 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)

ArgumentWhat to pass
name"M67" (frag), "M18" (smoke) or "Flashbang".
positionWhere it starts, a Vector3.
velocityIts starting speed and direction, a Vector3. Vector3.zero just drops it.
fuseSeconds until it goes off.
throwerOptional 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.

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.

EventArgumentsWhen it fires
Firedplayer, weaponName, origin, directionEvery shot the server accepts.
Damagedhumanoid, attacker, amount, zone, weaponNameEvery time XSF damages a character. attacker is a Player or nil, zone is Head, Torso or Limb, amount is after armour.
Killedhumanoid, attacker, weaponNameWhen a character dies to XSF damage, including bleeding out after being downed by someone.
PlayerDownedhumanoid, infoWhen someone goes down. info has attacker, zone, weapon and from.
Revivedhumanoid, byPlayerWhen a downed character is revived. byPlayer is nil for scripted revives without one.
BledOuthumanoidWhen a downed character runs out of time or gives up.
Explodedkind, position, attacker, weaponNameEvery explosion, flash and smoke. kind is Frag, HE, Claymore, Flash or Smoke.
PickedUpplayer, weaponName, modelSomeone picked up a gun, or took ammo (weaponName is "Ammo").
Droppedplayer, weaponName, modelA gun was dropped. player is nil for scripted spawns.
OwnershipChangedplayerA player got or lost an attachment or skin. Call GetOwned to see what they have now.
Equippedplayer, weaponName, slotA player took a weapon out.
HolsteredplayerA player put their weapon away (empty hands).
Reloadedplayer, weaponName, mag, reserveA reload finished on the server. Shell loaders (shotguns, the Mosin) fire once per shell.
Rejectedplayer, reasonThe 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.
Script in ServerScriptService
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.

LocalScript in StarterPlayerScripts
local XSF = require(game.ReplicatedStorage:WaitForChild("XSF").Client.API)

Functions

FunctionWhat 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.
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:

FieldWhat it is
weaponThe gun's name, like "HK416".
classThe Class from its Config: Rifle, SMG, Pistol, Sniper, Shotgun or Launcher.
positionWhere the shot came from (a Vector3).
owntrue for the local player's own shots.
suppressedtrue when a suppressor is fitted.
subsonictrue for subsonic ammo (quieter, carries less far).
distanceStuds from the camera to the shot.
indoorHow enclosed the listener is, 0 (open air) to 1 (a small room). XSF uses it for its own reverb.

Signals

SignalArgumentsWhen it fires
FiredweaponName, origin, directionEvery shot the local player fires.
WeaponChangedwhatSomething about the held weapon changed. what is one of equip, ammo, reload, mode, check, empty, fire, inventory, inspect, zero or jam.
Hurtdamage, fromPosition, zoneThe local player took damage.
HitConfirmedzone, killed, damageOne of the local player's shots landed on someone.
Suppressedamount, positionBullets passed close to the local player.
StanceChangednew, oldThe player stood, crouched or went prone.
SettingChangedkey, valueA setting changed. key is "*" when all settings were loaded at once.
InputBegan / InputEndedactionAn 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.
MenuChangedname, openA menu opened or closed: XSF's "Settings", "Loadout" and "Backpack", or yours from SetMenuOpen.
EquippedweaponName, slotThe 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()).
ReloadStartedweaponName, slotA reload began.
ReloadedweaponName, slot, mag, reserveA 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.
ArmsBuiltarmsModel, weaponNameNew 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.
OtherFiredshooter, weaponName, origin, directionAnother 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.
LocalScript
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")
end

Menus 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.

LocalScript
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.

FieldWhat it is
stance"Stand", "Crouch" or "Prone".
sprintingtrue while actually sprinting (moving forward on the ground at sprint speed), including a sprint reload.
sprintReloadtrue while running through a reload with Movement.SprintReload on.
sprintHeldtrue while the player wants to sprint (key held, or sprint toggled on), even if they can't right now.
slidingtrue during a slide.
exhaustedtrue once stamina ran out. Sprint stays locked until the bar is back above 30%.
stamina0 to 1.
lean-1 (full left) to 1 (full right), 0 upright.
aimingtrue while aiming down sights.
reloadingtrue during a reload.
groundedtrue when standing on something.
speedThe 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.

LocalScript
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.

AttributeTypeWhat it does
XSF_WeaponstringWeapon name, like "HK416". Change it to swap guns, clear it to take the gun away.
XSF_AttachmentsstringOptional. Slot=Name pairs split by semicolons, like "Sight=Acog;Barrel=Suppressor". Set it before XSF_Weapon.
XSF_Stancestring"Stand", "Crouch" or "Prone". On R15, crouch also lowers the hip height.
XSF_PitchnumberWhere it aims up or down, in radians. 0 is level.
XSF_Aimnumber0 holds the gun at the ready, 1 raises it to the shoulder.
XSF_Leannumber-1 leans left, 1 leans right.
XSF_SprintbooleanCarries the gun low, like it's running.
XSF_SlidingbooleanSlide pose.
XSF_Armor / XSF_HelmetnumberArmour, 0 to 1. See How damage works.
Script
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:

Script inside the NPC
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)
end

This 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

AttributeOnTypeWhat it does
XSF_Armorcharacter modelnumber 0 to 1Cuts torso and limb damage by that share.
XSF_Helmetcharacter modelnumber 0 to 1Cuts head damage by that share.
XSF_Weapon, XSF_Attachments, XSF_Stance, XSF_Pitch, XSF_Aim, XSF_Lean, XSF_Sprint, XSF_SlidingNPC modelsee NPCsGun and pose for NPCs.
XSF_PenetrationCostany partnumberOverrides the material's wall cost. 100 is bulletproof, 0 is ignored by bullets. See Bullets and walls.
XSF_AttFit, XSF_AttScale, XSF_SightRiser, XSF_Classgun modelsee Adding a gunHow attachments sit on your gun.
XSF_GripWrap, XSF_GripReachgun modelboolean, numberShooting hand grip. See Hands and grips.
XSF_SelfTestWorkspacestringLimits the F6 self test to a list like "RPG7, M320". Studio only.
XSF_VaultSlowMoWorkspacenumberSlows 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)

AttributeOnWhat it means
XSF_DownedHumanoidtrue while the character is downed.
XSF_Stanceplayer characterThe player's current stance ("Stand", "Crouch" or "Prone"), so your scripts can react to it.
XSF_OwnedPlayerTheir unlocks as JSON. See Unlocks.
XSF_Skingun modelThe skin currently on that gun.
XSF_Weapon, XSF_Mag, XSF_Reserve, XSF_Attachmentspickup modelWhat 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.

ProviderOptions
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

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.

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.

Script in ServerScriptService
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)
LocalScript inside a ScreenGui with a TextButton called Play
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.

Script in ServerScriptService
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. Keep Customization.Ownership on (the default) and set Customization.SaveOwnership = true so unlocks are remembered between visits. XP itself is saved here with a simple DataStore.

Script in ServerScriptService
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.

Script in ServerScriptService
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
end
LocalScript (your shop button)
local 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

Script in ServerScriptService
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 }.

Script in ServerScriptService
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 round

Rounds 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 }).

Script in ServerScriptService
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)
end

Gun game

Every kill moves you to the next gun. First to finish the list wins.

Script in ServerScriptService
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

Script in ServerScriptService
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)
LocalScript inside a ScreenGui with a Frame called Feed (add a UIListLayout to it)
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

Script inside a Part (the armour pickup)
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

Script in ServerScriptService
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:

XSF.GameConfig
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_PenetrationCost where they don't match.
  • Thicker than 6 studs always stops bullets, whatever the material.

The full table is in Bullets and walls.

Heights

HeightWhat happensGood for
Up to 3.6 studsVaulted overLow walls, sandbags, car bonnets, windows you jump through
3.6 to 7 studsClimbed ontoLedges, shipping containers, low roofs
Over 7 studsCan't be climbedAnything players must not get over, like map edges
Under 2.6 studs of headroomCan't stand up from a crouchCrawl 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:

  1. Shots to kill = 100 health divided by damage per shot, rounded up.
  2. 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 closeLower Handling.AimTime and Mass, raise RPM, shorten falloff (FalloffStart 40 to 60)
better far awayRaise MuzzleVelocity and falloff distances, lower Dispersion, add zero steps
easier to useLower Recoil.Climb and HeatPerShot
harder but strongerRaise damage and climb together
better against armourRaise Fire.Penetration, or hand out Armor Piercing ammo
a commitmentSmall 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

KeyWhat it does
F3Performance readout.
F4Live recoil and handling tuner for the gun in your hands. See Recoil and sway.
F6Weapon 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

  • Low effects (settings, EFFECTS tab) cuts particles, drops brass and muzzle smoke and keeps fewer bullet holes. Set the default for new players with Performance.LowEffects. Turn it on by default for phone first games.
  • 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.TracerEvery of 3 is fine. Every round being a tracer (1) on twenty machine guns is not.
  • Pickups. Lower Pickups.Lifetime in 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 = true for a live test, to see what a busy moment costs.

What XSF already does

  • Shots are rate limited on the server, so a modified client can't flood it.
  • 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.

  1. Note your changes. Copy anything you changed in GameConfig and in weapon Configs, or keep the old place open in another window.
  2. Delete the old XSF: ReplicatedStorage.XSF, ServerScriptService.XSF and StarterPlayer.StarterPlayerScripts.XSF_Boot. Move your own weapons and gun models somewhere safe first if they're inside XSF.Weapons or XSF.Assets.GunModels.
  3. Install 2.1 the same way as the first time (see Install).
  4. 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.
  5. 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). Set Holster.Reselect = false or FreeCursor = {} if your game uses those keys for something else. Y is the Holster key.
  6. Run the self test (F6) and play a round.

Updating to 2.0

  1. Save your changes first. Copy anything you changed out of your old GameConfig and weapon Configs, or keep the old place open in another window. If you made your own weapons, note their folder and model names.
  2. Delete the old XSF: ReplicatedStorage.XSF, ServerScriptService.XSF and StarterPlayer.StarterPlayerScripts.XSF_Boot.
  3. Install 2.0 the same way as the first time (see Install).
  4. 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.
  5. Your own weapons: copy their folders back into XSF.Weapons and their models into XSF.Assets.GunModels. Turn on XSF_AttFit on their models to get the new attachment fitting.
  6. Decide on unlocks. 2.0 ships with Customization.Ownership on, so players only get attachments you give them. If your 1.3 game let everyone use every attachment, set Ownership = { Attachments = false, Skins = true } to keep that.
  7. 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 withWhat it meansFix
The gun model "X" is not in XSF.Assets.GunModelsA 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.WeaponsA 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 NGameConfig 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 NSame, in one weapon's Config.Fix that line. Only that gun is affected.
HTTP requests are offThe license check and settings saving cannot reach the XSF site.Game Settings, Security, turn on Allow HTTP Requests.
Studio can't reach DataStoresHTTP 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 missingThe 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 nameA 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 existThe arms setting is misspelled.Use "XSFRealistic" or "ACSClassic".
A gun model has no part called HandleA 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 moduleA weapon was added without animations.Copy Anims from a similar gun, or use the ACS importer.
XSF is waiting for X, which is missingSomething was moved or renamed.Put XSF back where the download puts it, with the original names.
Something went wrong in XAn 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 namePreset 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 settingsA 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

MessageWhat it meansFix
[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: reasonOnly 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 missingF6 was pressed but the server half isn't running.Make sure ServerScriptService.XSF is in place.
[XSF] finger grip failed / hand animations failedThe 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

MessageWhat it meansFix
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.
This license is not valid.The key doesn't match an account. It can also mean the game was revoked on the dashboard.Check the Games tab on the dashboard and press Restore if you revoked it, or download a fresh copy.
Could not reach the XSF license server.The XSF site didn't answer, or HTTP is off.Turn on Allow HTTP Requests. If it's on, it's a hiccup on our side, try again in a minute.
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).

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 Loadout must match the folder names in XSF.Weapons exactly, 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.FriendlyFire is the switch.
  • XSF uses Roblox Teams. Players with no team, or on a team with Neutral behaviour, can always be hurt. Make sure your team script actually sets player.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 and the two API modules 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.

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 #

Join the XSF Discord: discord.gg/W8mWeGqMeJ
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.

Terms of service · Privacy policy