Safe zones are areas where combat is disabled โ spawn points, hospitals, shops, and anywhere players getting killed would break the experience. This guide covers how zone detection actually works, the code that disables combat properly, and why the in-game creator approach beats hand-editing coordinates.
What a safe zone does
A safe zone is a defined area where the server enforces protection โ typically disabling weapons, preventing damage, and showing the player an indicator. When a player enters, combat is neutralised; when they leave, normal rules resume.
Common locations:
- Spawn / new player areas โ so fresh players are not spawn-killed
- Hospitals โ so injured players can recover
- Shops and markets โ to keep commerce civil
- Event or hub areas โ wherever fighting would break the experience
Zone detection: the part that decides your performance
This is where safe zone scripts are won or lost. There are three ways to know a player is inside a zone, and they are not equal.
The bad way โ a distance loop every frame:
-- Do not do this
CreateThread(function()
while true do
local coords = GetEntityCoords(PlayerPedId())
for _, zone in pairs(Config.Zones) do
if #(coords - zone.center) < zone.radius then
-- inside
end
end
Wait(0)
end
end)That runs every frame, for every zone, forever. Ten zones is survivable. Forty is a measurable frame cost on every client, whether or not anyone is near one. This pattern is one of the most common causes of unexplained client lag, and it is why safe zones have a reputation for being heavy.
The acceptable way โ the same loop with a variable wait:
CreateThread(function()
while true do
local sleep = 1000
local coords = GetEntityCoords(PlayerPedId())
for _, zone in pairs(Config.Zones) do
local dist = #(coords - zone.center)
if dist < zone.radius + 50 then
sleep = 0 -- only tighten up when actually near a zone
end
end
Wait(sleep)
end
end)Idle cost drops to almost nothing because the thread sleeps a full second when the player is nowhere near a zone.
The right way โ let ox_lib handle it:
lib.zones.poly({
points = {
vec3(150.2, -1040.5, 29.4),
vec3(158.7, -1040.5, 29.4),
vec3(158.7, -1032.1, 29.4),
vec3(150.2, -1032.1, 29.4),
},
thickness = 4.0,
onEnter = function()
TriggerEvent('safezone:enter')
end,
onExit = function()
TriggerEvent('safezone:exit')
end,
})ox_lib's zone system handles the spatial checks efficiently and calls you only on transitions. You write no loop at all. If you are already running the ox stack, this is the correct answer.
Disabling combat properly
Entering the zone is only half of it. Here is what actually needs to happen:
local inZone = false
RegisterNetEvent('safezone:enter', function()
inZone = true
local ped = PlayerPedId()
SetEntityInvincible(ped, true)
SetCanAttackFriendly(ped, false, false)
NetworkSetFriendlyFireOption(false)
-- holster whatever they are carrying
SetCurrentPedWeapon(ped, `WEAPON_UNARMED`, true)
end)
RegisterNetEvent('safezone:exit', function()
inZone = false
local ped = PlayerPedId()
SetEntityInvincible(ped, false)
SetCanAttackFriendly(ped, true, true)
NetworkSetFriendlyFireOption(true)
end)
-- firing has to be suppressed continuously while inside
CreateThread(function()
while true do
if inZone then
DisablePlayerFiring(PlayerId(), true)
SetPlayerCanDoDriveBy(PlayerId(), false)
Wait(0)
else
Wait(500)
end
end
end)Note the shape of that last thread: Wait(0) only while inside a zone, Wait(500) otherwise. DisablePlayerFiring has to be called every frame to work, but only when it matters.
Reference pages for SetEntityInvincible and PlayerPedId cover those natives in detail.
The exploit nobody mentions
Everything above runs on the client, and the client can be modified.
A player with a modified game can simply not run your DisablePlayerFiring loop. Client-side protection stops honest players from accidentally shooting in a hospital. It does not stop someone who wants to.
For a safe zone that actually holds, the server has to be involved โ validating damage events, checking whether the attacker and victim were both inside a protected area, and rejecting what should not be possible. Our client vs server guide explains why this split exists and protecting your server from cheaters covers the wider pattern.
When you evaluate a safe zone script, this is the question to ask: does it validate server-side, or is it a client loop with a nice UI? Most free releases are the latter.
Getting your coordinates
The traditional workflow is painful: stand in the world, run a coordinate command, copy the vector out of the console, paste it into a config, restart, discover the zone is three metres off, repeat.
For a circle that is merely tedious. For a polygon covering an irregular hospital forecourt with a car park attached, you are copying eight to sixteen vectors by hand and getting one wrong. Our guide on finding and adding coordinates covers the manual method properly.
The in-game creator approach
The alternative is drawing the zone where it lives. You walk the boundary, drop a point at each corner, and the polygon is saved for you โ no console, no config file, no restart.
The practical differences:
- Irregular shapes become trivial. Real protected areas are rarely circles. A hospital with an ambulance bay and a car park is a polygon, and drawing it takes a minute.
- You see the boundary as you place it, so "three metres off" does not happen.
- Editing is not a redeploy. Move a point, save, done.
- Non-developers can do it. An admin can add a zone without touching a Lua file.
This is what people are searching for when they look for a *safezone creator* rather than a safezone script โ the difference is whether zone creation is a config task or an in-game one.
Rules worth having per zone
Once zones are easy to create, you will want them to behave differently:
- Disable weapons โ the baseline
- Block fist fighting โ spawn areas need this, a fight club does not
- God mode โ full damage immunity, for hospitals
- Vehicle speed limit โ stops someone driving through a crowded spawn at 200 km/h
- Job whitelist โ police and EMS keep their weapons while everyone else is disarmed
That last one matters more than it sounds. A safe zone that disarms responding officers during an incident creates a worse problem than the one it solved.
Testing
- Restart and check the console for errors.
- Walk into each zone โ confirm weapons and damage are disabled and the indicator shows.
- Walk out โ confirm combat resumes. Getting stuck protected outside the zone is the classic exit-detection bug.
- Test entering mid-combat.
- Test the job whitelist with an actual whitelisted player.
- Run
resmon 1and check idle cost with nobody near a zone. It should be effectively zero.
Common issues
- Protection does not apply โ the zone coordinates do not cover where you are standing. Turn on the script's debug drawing if it has one.
- Zone feels laggy โ almost always a
Wait(0)loop running regardless of proximity. See the detection section above. - Stuck protected after leaving โ exit detection failed, usually a zone system that only checks entry.
- Players still take damage inside โ something else is applying damage server-side, or another resource is fighting yours. Finding the script conflict covers the method.
The easy path
If you would rather not build and maintain this yourself, [Viper Safe-Zone](/scripts/7623027) is an in-game safezone creator: draw polygon zones by walking the boundary, manage them live from a menu, and set weapons, fist fighting, god mode, vehicle speed limit and a job whitelist per zone. It runs on QBCore, QBox and ESX with automatic detection, and validates server-side rather than trusting the client.