Skip to content

ComboZone

mkafrin edited this page Jul 8, 2021 · 13 revisions

Creating a ComboZone

An ComboZone is a collection of other zones, which allows you to take actions on all the zones at once. It is created by invoking the ComboZone:Create() method, and passing in a table of zones, and a table of options:

local circleA = CircleZone:Create(vector3(314.94, -239.82, 54.0), 1.5, {
  name="a",
  data={foo=1}
})

local circleB = CircleZone:Create(vector3(315.41, -238.25, 54.03), 1.5, {
  name="b",
  data={foo=2}
})

local circleC = CircleZone:Create(vector3(316.8, -239.51, 54.0), 1.5, {
  name="c",
  data={foo=3}
})

local combo = ComboZone:Create({circleA, circleB, circleC}, {name="combo", debugPoly=true})
combo:onPlayerInOut(function(isPointInside, point, zone)
  print("combo: isPointInside is", isPointInside, " for point", point)
  if zone then
    print(zone.data.foo)
  end
end)

Notes:

  • ComboZones are most useful when you have a large set of zones that all do the same thing. For example you want a circle zone around every atm in the city that when a player is inside, you can trigger some UI or allow the player to press a button to open their bank account.
  • ComboZones can handle tens of thousands of zones just fine with the grid optimization, as long as they are not all clustered together. The "soft" limit you'll hit first is FiveM's memory warning banner, at 50 MiBs. This happens at 20k BoxZones and 40k CircleZones respectively. But that can be avoided by spreading the zones out into multiple resources, if you really need to.

Options for a CircleZone

Property Type     Default     Required Description
name String nil false Name of the zone
useGrid Boolean true false If true, enables the optimization grid for the ComboZone, which can greatly increase the amount of zones it can handle. This is only compatible with zones that never move or change size.
debugPoly Boolean false false If true, draws all zones in the ComboZone
data table {} false A table that can hold any arbitrary data on the zone

Helpers

AddZone(zone)

Allows you to add zones to the ComboZone after initial creation. You should generally try to avoid using this after resource load if possible, because it has a non-zero cost.

isPointInside/onPointInOut/onPlayerInOut

ComboZones have access to methods like isPointInside() and helpers like onPointInOut() and onPlayerInOut(), but there is one thing that is different about them for ComboZones: they pass back the zone that was entered (and exited for onPointInOut and onPlayerInOut).

-- insideZone=the zone the point is in (or nil if isInside=false)
local isInside, insideZone = comboZone:isPointInside(GetEntityCoords(PlayerPedId()))

-- For the onPointInOut and onPlayerInOut helpers this additional value is passed to the callback
comboZone:onPlayerInOut(function(isPointInside, point, zone)
  -- zone can be nil if the player/point starts OUTSIDE of the comboZone
  if zone then
    if isPointInside then
      -- zone=zone player just entered
    else
      -- zone=last zone player entered (aka zone player just left)
    end
  end
end)

Exhaustive Helpers

Exhaustiveness determines whether the ComboZone will stop checking after the first zone is found to contain the point, or if it will check all the zones. This could be useful if some of the zones overlap, but is more expensive (when inside at least one zone) and has a small memory churn (when inside at least one zone) because it has to always go through all the zones and allocate space to hold all the inside zones.

-- insideZones is a table of ALL zones the point is inside
local isInside, insideZones = comboZone:isPointInsideExhaustive(GetEntityCoords(PlayerPedId()))

onPointInOut() and onPlayerInOut() have this same option, and insideZones is passed to the callback function, as well as enteredZones and leftZones. All three of these will be nil if there are zero zones that apply to them, so do a nil check if you are going to use them. enteredZones holds all zones the player/point just entered, and leftZones holds all zones the player/point just left.

combo:onPlayerInOutExhaustive(function(isPointInside, point, insideZones, enteredZones, leftZones)
  print("combo: isPointInside is", isPointInside, " for point", point)
  if insideZones then
    print("Inside Zones")
    for i=1, #insideZones do
      print("    data.foo=" .. tostring(insideZones[i].data.foo))
    end
  end
  if enteredZones then
    print("Entered Zones")
    for i=1, #enteredZones do
      print("    data.foo=" .. tostring(enteredZones[i].data.foo))
    end
  end
  if leftZones then
    print("Left Zones")
    for i=1, #leftZones do
      print("    data.foo=" .. tostring(leftZones[i].data.foo))
    end
  end
end)