F5 StudioF5 Studio
Skip to main content

Configuration

F5 Combat HUD is configured through six files in config/, plus one server-only file for Discord.

FileHoldsYou edit it
config/main.luaFramework, debug, NPC policy, language, command, profiles, share codes, backup, crosshair types, databaseOnce, at install
config/features.luaWhat is switched on: feature flags, section gates, moderators, hit particles, marketplace limitsWhenever you change policy
config/defaults.luaWhat a new player starts with, plus the killstreak tier listTo set the house style
config/presets.luaThe ready-made crosshair and effects galleriesRarely
config/weapons.luaWeapon categories for per-weapon crosshairsWhen you add addon weapons
config/limits.luaAdvanced. Server clamps and menu slider rangesRarely — the shipped values are correct
server/config_server.luaDiscord webhook — server-onlyOptional, see Webhooks
Nothing in config/ executes

All six files are plain data, loaded as shared scripts — client and server read the same values. There is no runtime reload: a config change takes effect when the resource restarts.

A boolean has three different meanings here

This is the single most common source of surprise, so it is worth learning before you edit anything:

KeysRead asCommenting the line out means
The 15 Enable* flagsMust be exactly true or falseThe resource refuses to start
Profiles.enabled, Backup.enabled, ShareCodes.*Enabled, AutoInstallDatabase, and most section keysOff only when exactly falseLeft on
Retention.auditEnabled, Retention.playerDataEnabled, EnableHitParticles, EnableMarketplaceOn only when exactly trueLeft off
Debug, CountNPCKills, NPCKillsCountStreak, ShowNPCKillsInFeedPlain truthinessLeft off

Write real booleans, and delete a value rather than commenting it out only when you know which group it is in.


config/main.lua

Framework

config/main.lua
F5Cfg.Framework = {
mode = 'auto', -- 'auto' | 'qb' | 'qbx' | 'esx' | 'custom'
custom = { base = 'qb', getCore = nil, --[[ … ]] },
}
OptionTypeDefaultDescription
modestring'auto'auto takes the first running framework of qbx_core, qb-core, es_extended. Any other value than the five listed stops the resource
customtablesee belowRead only when mode = 'custom'. Under any other mode every field in it is inert, with no warning

The custom table describes a fork of one of the three cores: base names the framework yours derives from, and every other field overrides one thing. Full contract in Framework Compatibility.

Debug

config/main.lua
F5Cfg.Debug = false

Prints kill, death, killstreak, kill feed, save and load diagnostics to both consoles, plus the bridge's framework=… base=… line. Error lines print whether or not it is on.

Leave it off in production

Debug is not purely diagnostic on the client. With damage numbers disabled the damage handler normally returns immediately; with Debug = true it instead runs a per-hit health-polling loop so it has something to report. That costs frames on a busy server.

What Counts as a Kill

config/main.lua
F5Cfg.CountNPCKills = true
F5Cfg.NPCKillsCountStreak = true
F5Cfg.ShowNPCKillsInFeed = true
OptionDefaultDescription
CountNPCKillstrueNPC kills and deaths count toward K/D
NPCKillsCountStreaktrueNPC kills continue a killstreak. With it off, an NPC kill neither extends nor resets the streak — it is ignored
ShowNPCKillsInFeedtrueNPC kills and killer-less deaths appear in the kill feed. Because the death side only asks "was the attacker a player?", this also covers falls, drowning and car crashes — credited to NPC, because the game reports no killer
CountNPCKills also decides when K/D is recorded

With true, every kill and death is recorded — including for players who have the K/D display switched off.

With false, a kill counts only when the victim is a player and that player has their own K/D HUD enabled. For those players the display toggle doubles as a recording toggle: switch the HUD off, and their stats stop moving.

Kill and death recording is gated by EnableScoreboard and EnableKDHud before any of this is consulted.

Language

config/main.lua
F5Cfg.Locale = 'en'

Picks a file from locales/. Twelve ship. A missing key falls back to English per key, not per file. An empty or non-string value stops the resource; a language code with no file is only a warning. See Localization.

Command and Keybind

config/main.lua
F5Cfg.Command = 'chud'
F5Cfg.Keybind = 'F9'
OptionDefaultDescription
Command'chud'Opens the menu. This is the only way in — there is no menu keybind. Registered unrestricted: every player can run it. An empty value stops the resource
Keybind'F9'Default key for the crosshair on/off toggle, offered through FiveM's own keybind screen. A player who has already joined keeps whatever they bound themselves

The keybind toggles the crosshair, notifies the player and saves. It does nothing at all when EnableCrosshair = false.

Profiles

config/main.lua
F5Cfg.Profiles = {
enabled = true,
maxProfiles = 5,
}
OptionTypeDefaultDescription
enabledbooleantruefalse replaces the section with the disabled panel and makes every profile callback refuse, so a modified NUI gains nothing
maxProfilesnumber5Must be a number, and at least 1 — the resource refuses to start otherwise. Use enabled = false to switch profiles off

The cap is enforced inside the SQL insert, so a race cannot exceed it. It applies only to creating a profile: overwriting one the player already has is always allowed, and lowering the number never deletes anything — a player holding seven profiles keeps all seven and simply cannot create an eighth.

Share Codes

config/main.lua
F5Cfg.ShareCodes = {
crosshairPrefix = 'CR_',
effectsPrefix = 'FX_',
scoreboardPrefix = 'SB_',
crosshairEnabled = true,
effectsEnabled = true,
scoreboardEnabled = true,
}

Players can export a crosshair, an effects setup or a scoreboard setup as a code and paste it to someone else. The prefix decides which decoder runs.

OptionDefaultDescription
crosshairPrefix'CR_'Prefix of crosshair codes
effectsPrefix'FX_'Prefix of effects codes
scoreboardPrefix'SB_'Prefix of scoreboard codes
crosshairEnabledtrueGates generating and importing crosshair codes, server-side as well as in the menu
effectsEnabledtrueSame for effects codes
scoreboardEnabledtrueSame for scoreboard codes

A disabled tab disables its code regardless of these flags — and the startup check tells you when the two disagree.

Changing a prefix invalidates every code in circulation

The prefix lives inside every code players have already swapped. Change it and old codes are simply rejected as invalid. There is no migration. The same applies to Backup.prefix.

Do not make one prefix a prefix of another (CR_ and CR_FX_): the duplicate check only catches exact matches, and imports would be misrouted.

A crosshair code deliberately carries less than players expect: type, size, thickness, gap, colours, outline and the dynamic settings — but not glow, rainbow, animation, HP colour, centre dot, ADS behaviour, the enabled flag, or the uploaded image. Everything it does not carry stays at the importer's current value.

Backup Codes

config/main.lua
F5Cfg.Backup = {
enabled = true,
prefix = 'F5B_',
}

A backup code is the player's whole HUD — crosshair, all effects, killstreak, K/D, kill feed, kill flash, custom image settings and per-weapon crosshairs — in one string they can keep outside the game. It does not include profiles or K/D stats.

The uploaded picture does not travel with it: size, opacity, rotation and URL do, the image itself does not. Restoring keeps whatever image the player already had, and leaves custom image switched off when there is none. That is what keeps codes short enough to paste into a chat.

Backup codes are produced and read entirely by the menu; the server sees only the restored settings, which pass the normal validation on the next save.

Crosshair Types

config/main.lua
F5Cfg.CrosshairTypes = {
{ id = 'cross', label = 'Cross' },
{ id = 'dot', label = 'Dot' },
-- … 11 in total
}

The shipped ids: cross, dot, circle, square, plus, crossdot, tshape, chevron, diamond, triangle, corners.

label reaches the menu exactly as written — these labels are outside locales/, so translate them yourself if your server is not English. id is what gets saved, and it is also the whitelist: the server rewrites any unknown type to cross.

Removing an id resets the players who used it

The rewrite to cross happens on load as well as on save, so deleting a type immediately changes the crosshair of everyone who had it — including their per-weapon crosshairs.

Adding an id does not work either: the shapes are drawn by the NUI, which knows exactly these eleven. A twelfth id gives you a button in the menu and a value the server accepts, and renders as nothing.

Database

config/main.lua
F5Cfg.AutoInstallDatabase = true

F5Cfg.Retention = {
auditEnabled = false,
auditDays = 30,
playerDataEnabled = false,
playerDataDays = 180,
}
OptionDefaultDescription
AutoInstallDatabasetrueCreate missing tables and add missing columns at startup, from install.sql. Never drops, renames or retypes
Retention.auditEnabledfalseDelete audit rows older than auditDays
Retention.auditDays30Minimum 1; a non-number falls back to 30
Retention.playerDataEnabledfalseDelete settings, profiles and K/D for players not seen in playerDataDays days
Retention.playerDataDays180Minimum 1; a non-number falls back to 180

Both retention switches need a literal true, and both delete rows at the next startup once enabled. Retention runs once per resource start — there is no scheduler. Full detail in Database.


config/features.lua

Feature Toggles

config/features.lua
F5Cfg.EnableCrosshair = true
F5Cfg.EnableEffects = true
F5Cfg.EnableScoreboard = true
F5Cfg.EnableHitmarker = true
F5Cfg.EnableHitParticles = true
F5Cfg.EnableDamageNumbers = true
F5Cfg.EnableKillmarker = true
F5Cfg.EnableHeadshotMarker = true
F5Cfg.EnableKillstreak = true
F5Cfg.EnableHitDirection = true
F5Cfg.EnableWeaponCrosshairs = true
F5Cfg.EnableKDHud = true
F5Cfg.EnableKillFeed = true
F5Cfg.EnableKillFlash = true
F5Cfg.EnableMarketplace = true

All fifteen must be a literal true or false. A missing one — a typo, a commented-out line — is a startup error naming the flag, and the resource does not start.

What each one actually does, precisely:

FlagMenuRenderingSaved data
EnableCrosshairTab hidden; presets, style, parameters, colours, weapon crosshairs and crosshair share codes go with itCrosshair forced off at every entry pointUntouched — turn it back on and everything returns
EnableEffectsTab hiddenAll six effects stopHitmarker, damage numbers, killmarker, headshot marker, hit direction and kill flash are reset to your defaults on the player's next save
EnableScoreboardTab hiddenKillstreak, K/D and kill feed stop; the server stops broadcasting kill feed eventsThose three are reset to your defaults on the next save
EnableHitmarkerSection hiddenHitmarker stopsReset to DefaultHitmarker on the next save
EnableHitParticlesSection hiddenParticles stophitmarker.particlesEnabled forced to false on every save
EnableDamageNumbersSection hiddenDamage numbers stopReset to DefaultDamageNumbers
EnableKillmarkerSection hiddenKillmarker stopsReset to DefaultKillmarker
EnableHeadshotMarkerSection hiddenHeadshot marker stopsReset to DefaultHeadshotMarker
EnableHitDirectionSection hiddenIndicator stopsReset to DefaultHitDirection
EnableKillFlashSection hiddenFlash stopsReset to DefaultKillFlash
EnableKillstreakSection hiddenAnnouncements and counter stopUntouched
EnableKDHudSection hiddenK/D display stops, and K/D recording stopsUntouched
EnableKillFeedSection hiddenFeed stops; the server refuses feed eventsUntouched
EnableWeaponCrosshairsSection hiddenPer-weapon crosshairs stopThe whole per-weapon map is erased on the next save
EnableMarketplaceTab hiddenListings stay in the database; every marketplace callback refuses
"Reset to your defaults" is not reversible

Where the table says a section is reset, the player's stored values are overwritten with config/defaults.lua the next time they save anything. Turning the feature back on later restores the feature, not their old tuning. The flags marked untouched can be flipped back and forth freely.

The Scoreboard tab also disappears when EnableKillstreak, EnableKDHud and EnableKillFeed are all false, even with EnableScoreboard = true — and the startup check warns you about exactly that, as it does when EnableEffects is on with every effect off.

EnableMarketplace also needs F5Cfg.Marketplace to exist as a table — but only the server checks that. Delete the table and the tab still appears while every request comes back with "Marketplace is disabled". To switch the marketplace off, use the flag, not the table.

Crosshair Sections

config/features.lua
F5Cfg.CrosshairSections = {
presets = true, style = true, parameters = true, colors = true,
rainbow = true, glow = true, dynamic = true, aimingVisibility = true,
customImage = true, animated = true, hpColor = true,
}

Setting one to false replaces its panel with a "disabled by server admin" notice. Nine of the eleven are also enforced on save:

Keyfalse does
presetsHides the preset gallery. Menu only — it owns no saved field
stylePins type and showDot to DefaultCrosshair
parametersPins size, thickness and gap to DefaultCrosshair
colorsPins color, outline, outlineThickness and outlineColor to DefaultCrosshair
rainbowForces rainbow = false
glowForces glow = false
dynamicForces dynamic = false, and the client's spread loop never runs
aimingVisibilityForces showOnlyOnADS and hideOnADS to false; the ADS dimming is skipped
animatedForces animation = 'none'
hpColorForces hpColor = false
customImageForces the image off and blanks both the uploaded data and the URL. Size, opacity and rotation still save

Style, parameters and colours cannot be stripped the way an effect can — a crosshair always needs a shape, a size and a colour — so they are pinned to F5Cfg.DefaultCrosshair instead. Pinned values are re-clamped through config/limits.lua, so a typo in your defaults still cannot escape the limits.

Write false, not "nothing"

For rainbow, glow, dynamic, aimingVisibility, customImage, animated and hpColor, the menu hides the section unless the key is exactly true, while the server only pins the field when the key is exactly false. Delete one of these lines and you get the worst of both: the section disappears from the menu, but nothing is enforced on save.

Effects Sections

config/features.lua
F5Cfg.EffectsSections = {
preview = true,
presets = true,
}

The effects themselves are the Enable* flags. These two cover the parts that are not effects: the live preview stage and the preset gallery. Both are menu visibility only.

Scoreboard Sections

config/features.lua
F5Cfg.ScoreboardSections = {
killstreak = true,
killstreakCounter = true,
kdHud = true,
kdHudPresets = true,
killFeed = true,
killFeedPresets = true,
dragPosition = true,
}

Menu visibility only — none of these keys is read by the server. Turning one off hides that part of the configurator; it does not strip saved values and does not stop anything rendering. A player who already switched a feature on keeps seeing it, they just cannot retune it. For rendering, use EnableKillstreak / EnableKDHud / EnableKillFeed.

KeyHides
killstreakAnnouncement style, position, thresholds
killstreakCounterThe live on-screen streak counter
kdHudThe kills / deaths / ratio display
kdHudPresetsThe K/D preset grid
killFeedThe kill feed configurator
killFeedPresetsThe kill feed preset grid
dragPositionDrag-to-position for all four elements

killstreakCounter, kdHudPresets, killFeedPresets and dragPosition have no Enable* counterpart, so hiding them is the only control there is.

Marketplace Sections

config/features.lua
F5Cfg.MarketplaceSections = {
publish = true, publishCustomImage = true, apply = true, likes = true,
authorProfile = true, search = true, sort = true, tags = true,
}

These are enforced: the control disappears and the matching server call refuses.

Keyfalse does
publishNo publishing, editing or unpublishing — a player cannot remove a listing they already own
publishCustomImageRefuses to publish or update a listing whose crosshair carries a custom image. Listings published while it was on keep working
applyNo applying someone else's crosshair
likesNo likes
authorProfileAuthor names stop being clickable
searchThe search box disappears and any incoming search text is discarded
sortThe sort control disappears and every browse falls back to defaultSort
tagsTags are dropped from submissions and no minimum is required

EnableMarketplace = false overrides every flag here.

publishCustomImage looks at the data, not the switch

A listing is refused when the crosshair profile carries image data or an image URL — not when the custom image is switched on. A player who once uploaded an image and has since turned the feature off is still blocked from publishing their plain shape crosshair until they clear it.

Moderators and Per-Group Overrides

config/features.lua
F5Cfg.ModeratorAces = { 'command.combathud_admin', 'god', 'admin', 'mod' }
F5Cfg.SectionOverrides = { }

Both are covered in full on Permissions.

Hit Particles

config/features.lua
F5Cfg.HitParticles = {
defaultEffect = 'coin_burst',
effects = {
{
id = 'money_burst',
label = 'Money Burst',
asset = 'core',
name = 'ent_brk_banknotes',
scale = 0.9,
zOffset = 0.05,
},
-- … 10 in total
}
}

GTA particle bursts on a hit. Ten effects ship: money_burst, balloon_pop, candy, clown, coin_burst, concrete_burst, wood_splinter, rock_shatter, electric_flash, firework_burst.

FieldRequiredDescription
idyesWhat lands in the player's profile. An entry with no id is dropped silently
labelShown in the menu, exactly as written (outside locales/)
assetGTA particle dictionary, e.g. core
nameyesParticle effect name. An entry with no name is dropped silently
scaleClamped to 0.13.0, default 0.9
zOffsetClamped to -2.02.0, default 0.0
tintableOnly a literal true lets the menu's colour picker tint it. Only balloon_pop and firework_burst ship tintable
candidatesFallback effects tried in order when the main one will not load

Each candidate gets a short window to load; when the whole chain fails the client falls back to banknotes rather than drawing nothing. Particles spawn on the victim's head bone for a headshot and the spine otherwise.

id is the durable key

Renaming an id silently resets every player who had picked it back to defaultEffect. Labels are safe to change; ids are not.

The server memoises the list of valid ids with no invalidation, so any edit to HitParticles needs a full resource restart, not just a config reload.

If no configured effect is tintable, the colour row disappears from the menu entirely.

Marketplace

config/features.lua
F5Cfg.Marketplace = {
maxCrosshairListings = 10,
defaultSort = 'newest',
pageSize = 12,
maxListingsPerPage = 100,
titleMaxLength = 50,
descriptionMaxLength = 160,

tags = {
maxTagsPerListing = 3,
minTagsPerListing = 2,
customImageTag = 'custom-image',
available = { 'competitive', 'fun', 'roleplay', 'minimal', 'colorful',
'animated', 'sniper', 'close-range', 'custom-image' },
},
}
OptionTypeDefaultDescription
maxCrosshairListingsnumber10Listings per player. Enforced inside the SQL insert; minimum 1
defaultSortstring'newest''newest', 'most_applied' or 'most_liked'. Anything else silently becomes newest
pageSizenumber12Default page size; minimum 1. A client may ask for its own, capped by the next option
maxListingsPerPagenumber100Ceiling on a requested page size; minimum 10
titleMaxLengthnumber50Minimum 10. Applied as the input's maxlength and re-checked on the server
descriptionMaxLengthnumber160Minimum 20. An empty description is allowed; an empty title is not
tags.maxTagsPerListingnumber3Tags a player may pick. Must be a number
tags.minTagsPerListingnumber2Tags a player must pick. Automatically lowered when it would ask for more than are available
tags.customImageTagstring'custom-image'Reserved tag, see below
tags.availablestring[]9 tagsThe tag vocabulary
The reserved tag is free

The server adds custom-image itself when a published crosshair actually renders an image, and strips it when a player tries to add it by hand. It is appended after the maximum is applied, so it never costs a player one of their slots — and it does not count toward the minimum either. It must also appear in available to be filterable; if it does not, startup warns you.

Raising maxListingsPerPage above 100 changes nothing in the moderation panel — that panel asks for exactly one page of 100 and has no paging. Lowering it below 100 narrows what moderators can see.


config/defaults.lua

Everything here is what a new player starts with. Every feature ships switched off — each player turns on what they want from the menu.

Editing a default does not change existing players

Defaults are copied into a fresh profile. A player who already has a row keeps their own values. The exception is a section your config has switched off or pinned: those are rewritten from these tables on the player's next save.

Two columns need reading together: Default is what ships, Range is what the server accepts. Where the menu slider offers a narrower range, that is deliberate — see Validation ranges.

Crosshair

config/defaults.lua
F5Cfg.DefaultCrosshair = { enabled = false, type = 'cross', size = 20, --[[ … ]] }
DefaultCrosshair.enabled = true does not reach a brand-new player

Every other default table is applied as shipped. The crosshair is the exception: a player who has no row yet starts with the crosshair off no matter what you set here. It starts working for them once a row exists — after their first kill or death, or as soon as they toggle the crosshair once themselves.

If you want the crosshair on for everyone from the very first join, tell players to press the toggle key; there is no config that skips this.

FieldDefaultRange / values
enabledfalseCrosshair off until the player turns it on — see the note above
type'cross'Any id from F5Cfg.CrosshairTypes
size201–100
thickness21–20
gap50–50
colorwhiter/g/b/a, each 0–255
outlinetrueboolean
outlineThickness10–10
outlineColorblack0–255 per channel
dynamicfalseSpread that opens while moving and firing
dynamicMovement50–30
dynamicShooting100–50
dynamicRecovery0.150.01–1.0
showOnlyOnADSfalseShow the crosshair only while aiming
hideOnADSfalseHide it while aiming
adsOpacity300–100 %
hitAnimationtruePulse the crosshair on a hit
hitAnimationScale1.21.0–2.0 as stored — the slider shows it as 100–200 %
hitAnimationDuration10050–500 ms
showDotfalseCentre dot
rainbowfalseCycle the colour
rainbowSpeed501–100
glowfalse
glowIntensity205–50
glowOpacity8020–100
glowUseCrosshairColortrueGlow follows the crosshair colour
glowColorcyanUsed when the above is false
animation'none'none, pulse, rotate, breathe
animationSpeed501–100
hpColorfalseColour follows health
hpColorLowredColour at 0 % HP
hpColorHighgreenColour at 100 % HP

Hitmarker

FieldDefaultRange / values
enabledfalse
soundfalsePlays html/sounds/hitmarker.ogg
soundVolume500–100
particlesEnabledfalseNeeds EnableHitParticles = true
particleEffect'coin_burst'An id from HitParticles.effects
particleColorwhiteOnly applies to an effect declared tintable. Of the ten shipped effects only Balloon Pop and Firework Burst are, and the default effect is not — so out of the box this setting changes nothing until the player picks a tintable effect
colorwhite
size201–100
thickness21–20
duration20050–5000 ms

Damage Numbers

FieldDefaultRange / values
enabledfalse
font'Impact'Must be in Validation.Fonts
fontSize328–72
colorwhite
outlineColorblack
duration150050–5000 ms
distance10020–300 — how far the number floats
criticalColorredUsed for headshots
criticalMultiplier1.51.0–5.0 as stored — the slider shows 100–300 %

Killmarker

FieldDefaultRange / values
enabledfalse
soundfalsePlays html/sounds/killmarker.ogg
soundVolume500–100
colorgold
size201–100
thickness11–20
duration40050–5000 ms
textfalseShow a word alongside the mark
textColorred
textFont''Must be in Validation.Fonts; empty keeps the style's own
textFontSize6824–140

Headshot Marker

FieldDefaultRange / values
enabledfalse
soundfalsePlays html/sounds/headshot.ogg
soundVolume200–100
colorpink
size151–100
thickness11–20
duration30050–5000 ms

Hit Direction

FieldDefaultRange / values
enabledfalse
colorred, alpha 180
size6030–120
distance15080–250 — distance from the screen centre
duration50050–5000 ms (the slider offers 200–1000)
fadeOutfalseFade instead of cutting
thickness42–8

Kill Flash

FieldDefaultRange / values
enabledfalse
colorred
intensity3010–80
duration300100–1000 ms
headshotColorgold
headshotIntensity5010–80

K/D HUD

FieldDefaultRange / values
enabledfalse
style'modern'modern, transparent, minimal, classic, compact, slant, military, arcade
position'top-right'top-right, top-left, bottom-right, bottom-left
customPositionnilDrag-to-position, clamped to 3840 × 2160
size10050–200 %
showBackgroundtrue
bgOpacity750–100
backgroundColorblack, alpha 150
textColorwhite
accentColor / ratioColororange
showIcon, showKills, showDeaths, showRatio, showLabels, showDividerstrue
statAnimation'pulse'pulse, shake, flash, slide, bounce, none
statAnimSpeed10050–200 %
fontFamilynilFrom Validation.Fonts; nil lets each style choose
killsIconpreset user-minus{ type = 'preset', id = … } or { type = 'url', url = … }
deathsIconpreset skullas above
ratioIconpreset star-outlineas above

An icon URL must be https://, 10–500 characters. An unknown preset id quietly falls back to that stat's default.

Killstreak

config/defaults.lua
F5Cfg.Killstreak = {
enabled = false,
soundEnabled = true,
timeout = 15000,
resetOnDeath = true,
display = { --[[ … ]] },
thresholds = { --[[ … ]] },
counter = { --[[ … ]] },
thresholdIconOverrides = {},
}

This table is a hybrid: most of it is per-player defaults, but thresholds is server config — the canonical list of streak levels and sounds, which players can never change. The server overwrites whatever a client sends with your list, on both save and share-code import.

FieldDefaultRange / values
enabledfalse
soundEnabledtrueStarting value; each player can mute it
timeout150001000–60000 ms — the window to get the next kill. No menu slider
resetOnDeathtrue
display.style'modern'modern, cinematic, brutal, tactical, vapor
display.position'top-center'top-center, top-left, top-right, center
display.customPositionnilClamped to 3840 × 2160
display.fontSize2816–48
display.iconSize2012–40
display.subtitleSize97–16
display.duration30001000–4000 ms
display.animation'scale'scale, slide, fade
display.fontFamilynilFrom Validation.Fonts
counter.enabledfalseThe live on-screen streak counter
counter.style'modern'modern, neon, military, retro, hologram
counter.position'bottom-right'bottom-right, bottom-left, top-right, top-left
counter.showAfter21–5 kills
counter.fontSize1812–32
counter.colorwhite, alpha 200
counter.fadeAfter100000–60000 ms of inactivity; 0 disables the fade. No menu slider
counter.fontFamilynil
counter.iconOverridenilnil keeps the style's own icon
thresholdIconOverrides{}Per-player icon choices, keyed by position in the list — accepted keys are 1–64

Thresholds

Nine tiers ship: DOUBLE KILL (2), TRIPLE KILL (3), QUADRA KILL (4), PENTAKILL (5), LEGENDARY (6), RAMPAGE (8), UNSTOPPABLE (10), GODLIKE (15), BEYOND GODLIKE (20).

FieldDescription
killsThe streak count that triggers it
nameShown on screen, exactly as written — outside locales/
colorAccent colour
sound / soundFile / soundVolumeA file under html/sounds/, volume 0–100
icon{ type = 'preset', id = … }, { type = 'url', url = … }, or a raw SVG string
specialExtra emphasis on the announcement
screenEffectA full-screen effect on top of it
Add new tiers at the end

thresholdIconOverrides is keyed by position in the list, not by kill count. Inserting a tier in the middle shifts every icon players have already picked.

Kill Feed

FieldDefaultRange / values
enabledfalse
style'modern'modern, transparent, minimal, classic, compact
position'top-right'top-right, top-left, bottom-right, bottom-left
customPositionnilClamped to 3840 × 2160
maxEntries51–10
duration50002000–15000 ms
scale10050–200 %
fontFamilynilFrom Validation.Fonts
showTimestampfalse
showWeapontrueThe weapon icon
showWeaponNamefalseThe weapon name next to the icon
showHeadshottrue
showAllKillsfalseOff: only the player's own kills and deaths. On: everyone's kills too
showPlayerIdfalse
showAvatarfalseNeeds steam_webApiKey
showDistancefalse
bgOpacity600–100
textColor / killColor / deathColor / backgroundColor / headshotIconColorwhite / green / red / black / amber
animation.type'slide'fade, slide, scale, bounce, typewriter, glitch
animation.speed10050–200 %
shape.cornerRadius80–20 px
shape.accentWidth30–6 px (0 hides it)
shape.accentGlow00–100
streakEmphasisfalseHighlight entries made during a streak
activePresetIdnilWhich preset the player last applied — UI bookkeeping
The kill feed validator is the odd one out

Every other section clamps a bad value to the nearest legal one. The kill feed instead falls back to your default for that field. A wrong type there reverts to DefaultKillFeed, not to the nearest number.

Custom Image

FieldDefaultRange / values
enabledfalse
source'upload''upload' uses imageData, 'url' uses url. Both fields are kept when switching, so a player can flip back and forth
imageData''A base64 data: URL, at most 512 KB encoded
url''An https:// URL, 10–500 characters
size4010–200
opacity1000–100
rotation00–360°

Upload rules, host rules and the marketplace interaction are on Features → Custom image.


config/presets.lua

Two galleries, both pure data.

TableCountGrouped into
F5Cfg.Presets64 crosshairsClassic, Competitive, Dots, Dynamic, Tactical, Geometric, Neon, Minimal, Heavy
F5Cfg.EffectsPresets17 effect setupsFPS Classics, Immersive, Minimal, Flashy, Full Package

A crosshair preset is one flat table — category, name, and the crosshair fields it wants to set:

config/presets.lua
{ category = "Competitive", name = "Valorant", type = "cross", size = 6, thickness = 2, gap = 2,
color = { r = 255, g = 255, b = 255, a = 255 }, outline = true, outlineThickness = 1,
outlineColor = { r = 0, g = 0, b = 0, a = 200 }, showDot = true },

An effects preset sets whole sections at once, and carries a desc shown in the gallery:

config/presets.lua
{
category = "FPS Classics", name = "CS2 Competitive",
desc = "Minimal hitmarker, clean competitive feel, no distractions",
hitmarker = { enabled = true, sound = true, soundVolume = 40, --[[ … ]] },
killmarker = { enabled = false },
headshotMarker = { enabled = true, --[[ … ]] },
damageNumbers = { enabled = false },
killFlash = { enabled = false },
hitDirection = { enabled = false },
},

You can add, remove and re-order entries freely: a preset is applied once, at the moment a player clicks it, and nothing stores which crosshair preset a crosshair came from. category is what groups the gallery, and both category and name are shown exactly as written — they are outside locales/.

Values inside a preset still pass the server validators when the player saves, so a preset cannot smuggle an out-of-range value in.

Deleting F5Cfg.Presets is not how you hide the gallery

Remove the table and the gallery simply renders empty — the tab and its heading stay. To hide it properly, use CrosshairSections.presets = false and leave the table in place.

What applying a preset does to the rest of the crosshair

A crosshair preset sets the fields it lists and resets the ones it does not to built-in starting values — not to your DefaultCrosshair. So a preset with no size snaps the size back to 20, not to whatever you configured.

An effects preset is blunter still: it rewrites every effect it names, including the colours, sizes and durations, from config/defaults.lua. A player who applies one loses the tuning they had on those effects, not just their on/off state. That is expected behaviour for a one-click preset, but it is worth telling players.

Nothing in presets.lua is checked at startup

A malformed entry, an unknown type or a missing name produces no console message — the tile simply renders wrong. Add presets carefully and look at the gallery afterwards.

The K/D and kill feed preset grids are not in this file

Those two galleries are built into the menu itself. ScoreboardSections.kdHudPresets and killFeedPresets control whether they are shown.


config/weapons.lua

Per-weapon crosshairs: a player can save a different crosshair per weapon category, and the HUD swaps as they switch weapons. Needs EnableCrosshair and EnableWeaponCrosshairs.

config/weapons.lua
F5Cfg.WeaponCrosshairs = {
categories = {
{ id = 'pistols', label = 'Pistols', weapons = { 'weapon_pistol', --[[ … ]] } },
-- …
},
extraIcons = { 'gadget_parachute' },
iconAliases = {},
}

Eight categories ship: pistols, smgs, rifles, shotguns, snipers, heavy, melee, throwables.

FieldDescription
categories[].idThe category's key. Treat it as permanent and rename only the label — the HUD still reads a stored category_<id> entry when a weapon has no crosshair of its own
categories[].labelShown in the menu, exactly as written — outside locales/
categories[].weaponsWeapon names (not hashes). Saving a crosshair to a category writes it to each of that category's weapons, so moving a weapon between categories is safe — but removing a weapon from every category is not: its saved crosshair is dropped from the player's row on their next save
extraIconsExtra icon names to register that are not weapons — gadget_parachute ships
iconAliasesweapon_x = 'weapon_y' — borrow another weapon's icon. Both sides are weapon names without .png

A weapon that is in no category simply has no per-weapon crosshair; the player's normal crosshair is used.

Adding an addon weapon

config/weapons.lua
{
id = 'rifles',
label = 'Assault Rifles',
weapons = {
'weapon_assaultrifle',
'weapon_my_addon_rifle', -- added
},
},

Then give it an icon — either images/icons/weapon_my_addon_rifle.png, or an alias:

config/weapons.lua
iconAliases = {
weapon_my_addon_rifle = 'weapon_assaultrifle',
},

Without either, the kill feed shows the entry with no weapon icon. See NUI & Assets.

The per-weapon map a player can hold is capped at 256 entries, and keys that match no configured category or weapon are dropped on save.


config/limits.lua

Advanced. Two different things live under one table, and the difference matters:

TableWhat it is
F5Cfg.Validation.*What the server accepts. Anything outside is clamped on save, whatever the client sent. This is the security boundary
F5Cfg.Validation.UIThe min / max / step of each menu slider, keyed by the input element id

A slider may deliberately offer a narrower range than the server allows — that is not a bug. Widening a slider past the server range is warned about at startup for the flat ranges; for the per-feature tables it is not warned about at all, and the value is simply trimmed on save.

The shipped values are correct

Widening a server range lets players save settings the UI was never designed around. Narrowing one silently rewrites settings players already had. A range whose min is above its max is refused at startup, because it would freeze the field at min instead of misbehaving visibly.

Server ranges

config/limits.lua
F5Cfg.Validation = {
size = { min = 1, max = 100 },
thickness = { min = 1, max = 20 },
-- …
}
RangeValueClamps
size1–100Crosshair, hitmarker, killmarker and headshot marker size
thickness1–20The same four thicknesses
gap0–50Crosshair gap
duration50–5000Hitmarker, damage number, killmarker, headshot marker and hit direction duration
fontSize8–72Damage number font size
soundVolume0–100Hitmarker, killmarker and headshot marker volume
outlineThickness0–10Crosshair outline
dynamicMovement0–30
dynamicShooting0–50
dynamicRecovery0.01–1.0
adsOpacity0–100
hitAnimationDuration50–500
rainbowSpeed1–100
glowIntensity5–50
glowOpacity20–100
animationSpeed1–100Crosshair animation. The slider carrying it is named crosshairAnimationSpeed
hitAnimationScale1.0–2.0Stored as a multiplier, shown as a percentage

Then the per-feature tables: CrosshairAnimations, DamageNumbers, HitDirection, KillFlash, CustomImage, KDHud, Killstreak, Killmarker, CustomPosition, Fonts and KillFeed. Their values are listed alongside each feature in defaults above.

Two values are stored in a different unit than the slider shows

hitAnimationScale (stored 1.0–2.0, shown 100–200 %) and DamageNumbers.criticalMultiplier (stored 1.0–5.0, shown 100–300 %). The numbers in config/limits.lua are the stored ones. "Correcting" hitAnimationScale to { min = 100, max = 200 } to match the slider would pin every crosshair to a hundredfold hit pulse.

Three more worth knowing:

  • Validation.HitDirection has no duration entry on purpose. Hit direction duration is clamped against the flat Validation.duration. Adding a durationMin / durationMax there does nothing.
  • Validation.CustomPosition (3840 × 2160) bounds drag-to-position for all four movable elements, so a position saved on a 4K screen is pulled back into view on a smaller one.
  • Validation.Fonts is the whitelist for every font dropdown in the menu. Adding a name here is not enough on its own — the family also has to reach the browser. See NUI & Assets → Fonts.

Custom image limits

config/limits.lua
F5Cfg.Validation.CustomImage = {
sizeMin = 10, sizeMax = 200,
opacityMin = 0, opacityMax = 100,
rotationMin = 0, rotationMax = 360,
imageDataMaxBytes = 524288,
urlMinLength = 10, urlMaxLength = 500,
allowedUrlHosts = {},
snapshotMaxBytes = 393216,
}
OptionDefaultDescription
imageDataMaxBytes524288Cap on the whole data:…;base64,… string, not on the source file. Base64 inflates by roughly 4/3, so this admits files up to about 384 KB. The menu measures the same string, so an oversized file is refused there rather than dropped on save
urlMinLength / urlMaxLength10 / 500Length of a URL-mode image address
allowedUrlHosts{}Hosts a player may load an image from. Empty means any public HTTPS host
snapshotMaxBytes393216Bytes downloaded when a URL crosshair is snapshotted into a marketplace listing. Kept below imageDataMaxBytes because the snapshot is stored base64-encoded
Host matching is exact and has no wildcards

allowedUrlHosts = { 'imgur.com' } does not allow i.imgur.com — list every host you mean. Matching is case-insensitive.

Regardless of the list, the server always refuses non-HTTPS URLs, URLs carrying credentials, IP-literal hosts and private or loopback names, so a player cannot aim the publish-time fetch at your own network.

config/limits.lua
F5Cfg.Validation.UI = {
size = { min = 1, max = 50 },
thickness = { min = 1, max = 10 },
gap = { min = 0, max = 20 },
-- … one entry per slider, keyed by element id
}

Every slider in the menu has an entry, and editing it here is all that is needed to change what players can drag to. Sliders that ship narrower than the server allows include size (1–50 against 1–100), thickness (1–10 against 1–20), gap (0–20 against 0–50), dynamicMovement, dynamicShooting, dynamicRecovery, and every duration slider.

alpha (0–255) has no server counterpart — colour channels are always clamped to 0–255.


See Also