Configuration
F5 Combat HUD is configured through six files in config/, plus one server-only file for Discord.
| File | Holds | You edit it |
|---|---|---|
config/main.lua | Framework, debug, NPC policy, language, command, profiles, share codes, backup, crosshair types, database | Once, at install |
config/features.lua | What is switched on: feature flags, section gates, moderators, hit particles, marketplace limits | Whenever you change policy |
config/defaults.lua | What a new player starts with, plus the killstreak tier list | To set the house style |
config/presets.lua | The ready-made crosshair and effects galleries | Rarely |
config/weapons.lua | Weapon categories for per-weapon crosshairs | When you add addon weapons |
config/limits.lua | Advanced. Server clamps and menu slider ranges | Rarely — the shipped values are correct |
server/config_server.lua | Discord webhook — server-only | Optional, see Webhooks |
config/ executesAll 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.
This is the single most common source of surprise, so it is worth learning before you edit anything:
| Keys | Read as | Commenting the line out means |
|---|---|---|
The 15 Enable* flags | Must be exactly true or false | The resource refuses to start |
Profiles.enabled, Backup.enabled, ShareCodes.*Enabled, AutoInstallDatabase, and most section keys | Off only when exactly false | Left on |
Retention.auditEnabled, Retention.playerDataEnabled, EnableHitParticles, EnableMarketplace | On only when exactly true | Left off |
Debug, CountNPCKills, NPCKillsCountStreak, ShowNPCKillsInFeed | Plain truthiness | Left 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
F5Cfg.Framework = {
mode = 'auto', -- 'auto' | 'qb' | 'qbx' | 'esx' | 'custom'
custom = { base = 'qb', getCore = nil, --[[ … ]] },
}
| Option | Type | Default | Description |
|---|---|---|---|
mode | string | 'auto' | auto takes the first running framework of qbx_core, qb-core, es_extended. Any other value than the five listed stops the resource |
custom | table | see below | Read 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
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.
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
F5Cfg.CountNPCKills = true
F5Cfg.NPCKillsCountStreak = true
F5Cfg.ShowNPCKillsInFeed = true
| Option | Default | Description |
|---|---|---|
CountNPCKills | true | NPC kills and deaths count toward K/D |
NPCKillsCountStreak | true | NPC kills continue a killstreak. With it off, an NPC kill neither extends nor resets the streak — it is ignored |
ShowNPCKillsInFeed | true | NPC 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 recordedWith 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
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
F5Cfg.Command = 'chud'
F5Cfg.Keybind = 'F9'
| Option | Default | Description |
|---|---|---|
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
F5Cfg.Profiles = {
enabled = true,
maxProfiles = 5,
}
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | false replaces the section with the disabled panel and makes every profile callback refuse, so a modified NUI gains nothing |
maxProfiles | number | 5 | Must 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
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.
| Option | Default | Description |
|---|---|---|
crosshairPrefix | 'CR_' | Prefix of crosshair codes |
effectsPrefix | 'FX_' | Prefix of effects codes |
scoreboardPrefix | 'SB_' | Prefix of scoreboard codes |
crosshairEnabled | true | Gates generating and importing crosshair codes, server-side as well as in the menu |
effectsEnabled | true | Same for effects codes |
scoreboardEnabled | true | Same for scoreboard codes |
A disabled tab disables its code regardless of these flags — and the startup check tells you when the two disagree.
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
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
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.
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
F5Cfg.AutoInstallDatabase = true
F5Cfg.Retention = {
auditEnabled = false,
auditDays = 30,
playerDataEnabled = false,
playerDataDays = 180,
}
| Option | Default | Description |
|---|---|---|
AutoInstallDatabase | true | Create missing tables and add missing columns at startup, from install.sql. Never drops, renames or retypes |
Retention.auditEnabled | false | Delete audit rows older than auditDays |
Retention.auditDays | 30 | Minimum 1; a non-number falls back to 30 |
Retention.playerDataEnabled | false | Delete settings, profiles and K/D for players not seen in playerDataDays days |
Retention.playerDataDays | 180 | Minimum 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
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:
| Flag | Menu | Rendering | Saved data |
|---|---|---|---|
EnableCrosshair | Tab hidden; presets, style, parameters, colours, weapon crosshairs and crosshair share codes go with it | Crosshair forced off at every entry point | Untouched — turn it back on and everything returns |
EnableEffects | Tab hidden | All six effects stop | Hitmarker, damage numbers, killmarker, headshot marker, hit direction and kill flash are reset to your defaults on the player's next save |
EnableScoreboard | Tab hidden | Killstreak, K/D and kill feed stop; the server stops broadcasting kill feed events | Those three are reset to your defaults on the next save |
EnableHitmarker | Section hidden | Hitmarker stops | Reset to DefaultHitmarker on the next save |
EnableHitParticles | Section hidden | Particles stop | hitmarker.particlesEnabled forced to false on every save |
EnableDamageNumbers | Section hidden | Damage numbers stop | Reset to DefaultDamageNumbers |
EnableKillmarker | Section hidden | Killmarker stops | Reset to DefaultKillmarker |
EnableHeadshotMarker | Section hidden | Headshot marker stops | Reset to DefaultHeadshotMarker |
EnableHitDirection | Section hidden | Indicator stops | Reset to DefaultHitDirection |
EnableKillFlash | Section hidden | Flash stops | Reset to DefaultKillFlash |
EnableKillstreak | Section hidden | Announcements and counter stop | Untouched |
EnableKDHud | Section hidden | K/D display stops, and K/D recording stops | Untouched |
EnableKillFeed | Section hidden | Feed stops; the server refuses feed events | Untouched |
EnableWeaponCrosshairs | Section hidden | Per-weapon crosshairs stop | The whole per-weapon map is erased on the next save |
EnableMarketplace | Tab hidden | — | Listings stay in the database; every marketplace callback refuses |
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
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:
| Key | false does |
|---|---|
presets | Hides the preset gallery. Menu only — it owns no saved field |
style | Pins type and showDot to DefaultCrosshair |
parameters | Pins size, thickness and gap to DefaultCrosshair |
colors | Pins color, outline, outlineThickness and outlineColor to DefaultCrosshair |
rainbow | Forces rainbow = false |
glow | Forces glow = false |
dynamic | Forces dynamic = false, and the client's spread loop never runs |
aimingVisibility | Forces showOnlyOnADS and hideOnADS to false; the ADS dimming is skipped |
animated | Forces animation = 'none' |
hpColor | Forces hpColor = false |
customImage | Forces 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.
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
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
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.
| Key | Hides |
|---|---|
killstreak | Announcement style, position, thresholds |
killstreakCounter | The live on-screen streak counter |
kdHud | The kills / deaths / ratio display |
kdHudPresets | The K/D preset grid |
killFeed | The kill feed configurator |
killFeedPresets | The kill feed preset grid |
dragPosition | Drag-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
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.
| Key | false does |
|---|---|
publish | No publishing, editing or unpublishing — a player cannot remove a listing they already own |
publishCustomImage | Refuses to publish or update a listing whose crosshair carries a custom image. Listings published while it was on keep working |
apply | No applying someone else's crosshair |
likes | No likes |
authorProfile | Author names stop being clickable |
search | The search box disappears and any incoming search text is discarded |
sort | The sort control disappears and every browse falls back to defaultSort |
tags | Tags are dropped from submissions and no minimum is required |
EnableMarketplace = false overrides every flag here.
publishCustomImage looks at the data, not the switchA 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
F5Cfg.ModeratorAces = { 'command.combathud_admin', 'god', 'admin', 'mod' }
F5Cfg.SectionOverrides = { }
Both are covered in full on Permissions.
Hit Particles
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.
| Field | Required | Description |
|---|---|---|
id | yes | What lands in the player's profile. An entry with no id is dropped silently |
label | — | Shown in the menu, exactly as written (outside locales/) |
asset | — | GTA particle dictionary, e.g. core |
name | yes | Particle effect name. An entry with no name is dropped silently |
scale | — | Clamped to 0.1–3.0, default 0.9 |
zOffset | — | Clamped to -2.0–2.0, default 0.0 |
tintable | — | Only a literal true lets the menu's colour picker tint it. Only balloon_pop and firework_burst ship tintable |
candidates | — | Fallback 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 keyRenaming 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
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' },
},
}
| Option | Type | Default | Description |
|---|---|---|---|
maxCrosshairListings | number | 10 | Listings per player. Enforced inside the SQL insert; minimum 1 |
defaultSort | string | 'newest' | 'newest', 'most_applied' or 'most_liked'. Anything else silently becomes newest |
pageSize | number | 12 | Default page size; minimum 1. A client may ask for its own, capped by the next option |
maxListingsPerPage | number | 100 | Ceiling on a requested page size; minimum 10 |
titleMaxLength | number | 50 | Minimum 10. Applied as the input's maxlength and re-checked on the server |
descriptionMaxLength | number | 160 | Minimum 20. An empty description is allowed; an empty title is not |
tags.maxTagsPerListing | number | 3 | Tags a player may pick. Must be a number |
tags.minTagsPerListing | number | 2 | Tags a player must pick. Automatically lowered when it would ask for more than are available |
tags.customImageTag | string | 'custom-image' | Reserved tag, see below |
tags.available | string[] | 9 tags | The tag vocabulary |
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.
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
F5Cfg.DefaultCrosshair = { enabled = false, type = 'cross', size = 20, --[[ … ]] }
DefaultCrosshair.enabled = true does not reach a brand-new playerEvery 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.
| Field | Default | Range / values |
|---|---|---|
enabled | false | Crosshair off until the player turns it on — see the note above |
type | 'cross' | Any id from F5Cfg.CrosshairTypes |
size | 20 | 1–100 |
thickness | 2 | 1–20 |
gap | 5 | 0–50 |
color | white | r/g/b/a, each 0–255 |
outline | true | boolean |
outlineThickness | 1 | 0–10 |
outlineColor | black | 0–255 per channel |
dynamic | false | Spread that opens while moving and firing |
dynamicMovement | 5 | 0–30 |
dynamicShooting | 10 | 0–50 |
dynamicRecovery | 0.15 | 0.01–1.0 |
showOnlyOnADS | false | Show the crosshair only while aiming |
hideOnADS | false | Hide it while aiming |
adsOpacity | 30 | 0–100 % |
hitAnimation | true | Pulse the crosshair on a hit |
hitAnimationScale | 1.2 | 1.0–2.0 as stored — the slider shows it as 100–200 % |
hitAnimationDuration | 100 | 50–500 ms |
showDot | false | Centre dot |
rainbow | false | Cycle the colour |
rainbowSpeed | 50 | 1–100 |
glow | false | |
glowIntensity | 20 | 5–50 |
glowOpacity | 80 | 20–100 |
glowUseCrosshairColor | true | Glow follows the crosshair colour |
glowColor | cyan | Used when the above is false |
animation | 'none' | none, pulse, rotate, breathe |
animationSpeed | 50 | 1–100 |
hpColor | false | Colour follows health |
hpColorLow | red | Colour at 0 % HP |
hpColorHigh | green | Colour at 100 % HP |
Hitmarker
| Field | Default | Range / values |
|---|---|---|
enabled | false | |
sound | false | Plays html/sounds/hitmarker.ogg |
soundVolume | 50 | 0–100 |
particlesEnabled | false | Needs EnableHitParticles = true |
particleEffect | 'coin_burst' | An id from HitParticles.effects |
particleColor | white | Only 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 |
color | white | |
size | 20 | 1–100 |
thickness | 2 | 1–20 |
duration | 200 | 50–5000 ms |
Damage Numbers
| Field | Default | Range / values |
|---|---|---|
enabled | false | |
font | 'Impact' | Must be in Validation.Fonts |
fontSize | 32 | 8–72 |
color | white | |
outlineColor | black | |
duration | 1500 | 50–5000 ms |
distance | 100 | 20–300 — how far the number floats |
criticalColor | red | Used for headshots |
criticalMultiplier | 1.5 | 1.0–5.0 as stored — the slider shows 100–300 % |
Killmarker
| Field | Default | Range / values |
|---|---|---|
enabled | false | |
sound | false | Plays html/sounds/killmarker.ogg |
soundVolume | 50 | 0–100 |
color | gold | |
size | 20 | 1–100 |
thickness | 1 | 1–20 |
duration | 400 | 50–5000 ms |
text | false | Show a word alongside the mark |
textColor | red | |
textFont | '' | Must be in Validation.Fonts; empty keeps the style's own |
textFontSize | 68 | 24–140 |
Headshot Marker
| Field | Default | Range / values |
|---|---|---|
enabled | false | |
sound | false | Plays html/sounds/headshot.ogg |
soundVolume | 20 | 0–100 |
color | pink | |
size | 15 | 1–100 |
thickness | 1 | 1–20 |
duration | 300 | 50–5000 ms |
Hit Direction
| Field | Default | Range / values |
|---|---|---|
enabled | false | |
color | red, alpha 180 | |
size | 60 | 30–120 |
distance | 150 | 80–250 — distance from the screen centre |
duration | 500 | 50–5000 ms (the slider offers 200–1000) |
fadeOut | false | Fade instead of cutting |
thickness | 4 | 2–8 |
Kill Flash
| Field | Default | Range / values |
|---|---|---|
enabled | false | |
color | red | |
intensity | 30 | 10–80 |
duration | 300 | 100–1000 ms |
headshotColor | gold | |
headshotIntensity | 50 | 10–80 |
K/D HUD
| Field | Default | Range / values |
|---|---|---|
enabled | false | |
style | 'modern' | modern, transparent, minimal, classic, compact, slant, military, arcade |
position | 'top-right' | top-right, top-left, bottom-right, bottom-left |
customPosition | nil | Drag-to-position, clamped to 3840 × 2160 |
size | 100 | 50–200 % |
showBackground | true | |
bgOpacity | 75 | 0–100 |
backgroundColor | black, alpha 150 | |
textColor | white | |
accentColor / ratioColor | orange | |
showIcon, showKills, showDeaths, showRatio, showLabels, showDividers | true | |
statAnimation | 'pulse' | pulse, shake, flash, slide, bounce, none |
statAnimSpeed | 100 | 50–200 % |
fontFamily | nil | From Validation.Fonts; nil lets each style choose |
killsIcon | preset user-minus | { type = 'preset', id = … } or { type = 'url', url = … } |
deathsIcon | preset skull | as above |
ratioIcon | preset star-outline | as above |
An icon URL must be https://, 10–500 characters. An unknown preset id quietly falls back to that stat's default.
Killstreak
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.
| Field | Default | Range / values |
|---|---|---|
enabled | false | |
soundEnabled | true | Starting value; each player can mute it |
timeout | 15000 | 1000–60000 ms — the window to get the next kill. No menu slider |
resetOnDeath | true | |
display.style | 'modern' | modern, cinematic, brutal, tactical, vapor |
display.position | 'top-center' | top-center, top-left, top-right, center |
display.customPosition | nil | Clamped to 3840 × 2160 |
display.fontSize | 28 | 16–48 |
display.iconSize | 20 | 12–40 |
display.subtitleSize | 9 | 7–16 |
display.duration | 3000 | 1000–4000 ms |
display.animation | 'scale' | scale, slide, fade |
display.fontFamily | nil | From Validation.Fonts |
counter.enabled | false | The 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.showAfter | 2 | 1–5 kills |
counter.fontSize | 18 | 12–32 |
counter.color | white, alpha 200 | |
counter.fadeAfter | 10000 | 0–60000 ms of inactivity; 0 disables the fade. No menu slider |
counter.fontFamily | nil | |
counter.iconOverride | nil | nil 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).
| Field | Description |
|---|---|
kills | The streak count that triggers it |
name | Shown on screen, exactly as written — outside locales/ |
color | Accent colour |
sound / soundFile / soundVolume | A file under html/sounds/, volume 0–100 |
icon | { type = 'preset', id = … }, { type = 'url', url = … }, or a raw SVG string |
special | Extra emphasis on the announcement |
screenEffect | A full-screen effect on top of it |
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
| Field | Default | Range / values |
|---|---|---|
enabled | false | |
style | 'modern' | modern, transparent, minimal, classic, compact |
position | 'top-right' | top-right, top-left, bottom-right, bottom-left |
customPosition | nil | Clamped to 3840 × 2160 |
maxEntries | 5 | 1–10 |
duration | 5000 | 2000–15000 ms |
scale | 100 | 50–200 % |
fontFamily | nil | From Validation.Fonts |
showTimestamp | false | |
showWeapon | true | The weapon icon |
showWeaponName | false | The weapon name next to the icon |
showHeadshot | true | |
showAllKills | false | Off: only the player's own kills and deaths. On: everyone's kills too |
showPlayerId | false | |
showAvatar | false | Needs steam_webApiKey |
showDistance | false | |
bgOpacity | 60 | 0–100 |
textColor / killColor / deathColor / backgroundColor / headshotIconColor | white / green / red / black / amber | |
animation.type | 'slide' | fade, slide, scale, bounce, typewriter, glitch |
animation.speed | 100 | 50–200 % |
shape.cornerRadius | 8 | 0–20 px |
shape.accentWidth | 3 | 0–6 px (0 hides it) |
shape.accentGlow | 0 | 0–100 |
streakEmphasis | false | Highlight entries made during a streak |
activePresetId | nil | Which preset the player last applied — UI bookkeeping |
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
| Field | Default | Range / values |
|---|---|---|
enabled | false | |
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 |
size | 40 | 10–200 |
opacity | 100 | 0–100 |
rotation | 0 | 0–360° |
Upload rules, host rules and the marketplace interaction are on Features → Custom image.
config/presets.lua
Two galleries, both pure data.
| Table | Count | Grouped into |
|---|---|---|
F5Cfg.Presets | 64 crosshairs | Classic, Competitive, Dots, Dynamic, Tactical, Geometric, Neon, Minimal, Heavy |
F5Cfg.EffectsPresets | 17 effect setups | FPS Classics, Immersive, Minimal, Flashy, Full Package |
A crosshair preset is one flat table — category, name, and the crosshair fields it wants to set:
{ 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:
{
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.
F5Cfg.Presets is not how you hide the galleryRemove 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.
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.
presets.lua is checked at startupA 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.
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.
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.
| Field | Description |
|---|---|
categories[].id | The 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[].label | Shown in the menu, exactly as written — outside locales/ |
categories[].weapons | Weapon 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 |
extraIcons | Extra icon names to register that are not weapons — gadget_parachute ships |
iconAliases | weapon_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
{
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:
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:
| Table | What 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.UI | The 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.
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
F5Cfg.Validation = {
size = { min = 1, max = 100 },
thickness = { min = 1, max = 20 },
-- …
}
| Range | Value | Clamps |
|---|---|---|
size | 1–100 | Crosshair, hitmarker, killmarker and headshot marker size |
thickness | 1–20 | The same four thicknesses |
gap | 0–50 | Crosshair gap |
duration | 50–5000 | Hitmarker, damage number, killmarker, headshot marker and hit direction duration |
fontSize | 8–72 | Damage number font size |
soundVolume | 0–100 | Hitmarker, killmarker and headshot marker volume |
outlineThickness | 0–10 | Crosshair outline |
dynamicMovement | 0–30 | |
dynamicShooting | 0–50 | |
dynamicRecovery | 0.01–1.0 | |
adsOpacity | 0–100 | |
hitAnimationDuration | 50–500 | |
rainbowSpeed | 1–100 | |
glowIntensity | 5–50 | |
glowOpacity | 20–100 | |
animationSpeed | 1–100 | Crosshair animation. The slider carrying it is named crosshairAnimationSpeed |
hitAnimationScale | 1.0–2.0 | Stored 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.
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.HitDirectionhas no duration entry on purpose. Hit direction duration is clamped against the flatValidation.duration. Adding adurationMin/durationMaxthere 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.Fontsis 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
F5Cfg.Validation.CustomImage = {
sizeMin = 10, sizeMax = 200,
opacityMin = 0, opacityMax = 100,
rotationMin = 0, rotationMax = 360,
imageDataMaxBytes = 524288,
urlMinLength = 10, urlMaxLength = 500,
allowedUrlHosts = {},
snapshotMaxBytes = 393216,
}
| Option | Default | Description |
|---|---|---|
imageDataMaxBytes | 524288 | Cap 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 / urlMaxLength | 10 / 500 | Length of a URL-mode image address |
allowedUrlHosts | {} | Hosts a player may load an image from. Empty means any public HTTPS host |
snapshotMaxBytes | 393216 | Bytes downloaded when a URL crosshair is snapshotted into a marketplace listing. Kept below imageDataMaxBytes because the snapshot is stored base64-encoded |
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.
Menu slider ranges
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
- Features — what each of these settings does in game
- Framework Compatibility — the
Frameworkblock in detail - Permissions —
ModeratorAcesandSectionOverrides - Marketplace — what the marketplace limits govern
- Database —
AutoInstallDatabaseandRetention - Discord Webhooks —
server/config_server.lua - Troubleshooting — every startup message and its cause