# API

{% hint style="warning" %}
This resource now exposes a small public export and event API in addition to the handler set in `configs/server_config.lua` and `configs/client_config.lua`.
{% endhint %}

## Main

Most custom logic should be added through the `SB.*` hook functions that are already declared in the config files.

These handlers let you:

* override notifications
* intercept permission checks
* react to project lifecycle events
* react to zone lifecycle events
* attach client-side effects to UI and entity actions

## Public exports

### Client exports

Use these from another client resource.

```lua
exports['sb-map-editor']:openEditor()
exports['sb-map-editor']:closeEditor()

local state = exports['sb-map-editor']:getEditorState()
print(state.status, state.uiOpen, state.projectId, state.zoneId)
```

Available client exports:

* `openEditor()`
  * opens the editor UI if it is not already open
* `closeEditor()`
  * closes the editor UI if it is currently open
* `getEditorState()`
  * returns the current local editor state snapshot

### Server exports

Use these from another server resource.

```lua
local state = exports['sb-map-editor']:getPlayerEditorState(source)
if state and state.editingProject then
        print(('Player %s is editing project %s'):format(source, state.projectId))
end
```

Available server exports:

* `getPlayerEditorState(playerId)`
  * returns the most recent mirrored editor state for that player
  * returns an idle state if the player has not opened the editor yet

## Editor state shape

Both `getEditorState()` and `getPlayerEditorState(playerId)` use the same state fields:

```lua
{
        uiOpen = true,
        uiFocused = true,
        freecamActive = true,
        pauseMenuHidden = false,
        editingProject = true,
        editingZone = false,
        zoneEditMode = false,
        projectId = 12,
        zoneId = nil,
        idle = false,
        status = 'editing_project'
}
```

`status` will be one of:

* `idle`
* `ui_open`
* `editing_project`
* `editing_zone`
* `editing_project_zone`

## Public events

### Client trigger: open editor

This event is registered on the client, so you can trigger it locally or from the server.

```lua
TriggerEvent('sb-map-editor:cl:openEditor')
```

```lua
TriggerClientEvent('sb-map-editor:cl:openEditor', playerId)
```

### Client event: state changed

This fires locally on the client whenever the local editor state changes.

```lua
AddEventHandler('sb-map-editor:cl:stateChanged', function(state, previousState)
        print(state.status)
end)
```

### Server event: player state changed

This fires on the server whenever a client mirrors a new editor state.

```lua
AddEventHandler('sb-map-editor:sv:playerStateChanged', function(source, state, previousState)
        print(('Player %s editor state: %s'):format(source, state.status))
end)
```

## Server-side handlers

### OnNotify

```lua
---@param source number
---@param msg string
---@return string|nil
SB.OnNotify = function(source, msg)
    return "[Map Editor] " .. msg
end
```

Allows you to change or wrap outgoing messages before they are sent to a player.

***

### OnPermissionCheck

```lua
---@param source number
---@param permission string
---@return boolean|nil
SB.OnPermissionCheck = function(source, permission)
    if permission == 'project.delete' and IsPlayerAceAllowed(source, 'mapeditor.delete') then
        return true
    end

    return nil
end
```

Called before the default permission logic runs.

Return values:

* `true`
  * grants access immediately
* `false`
  * denies access immediately
* `nil`
  * falls back to the built-in permission system

***

### OnProjectCreated

```lua
---@param source number
---@param project table
SB.OnProjectCreated = function(source, project)
    print(('Project created: %s (%s)'):format(project.label, project.id))
end
```

Runs after a new project has been created and saved.

***

### OnProjectSaved

```lua
---@param source number
---@param project table
SB.OnProjectSaved = function(source, project)
    PerformHttpRequest('https://example.com/map-editor/project', function() end,
        'POST', json.encode(project), { ['Content-Type'] = 'application/json' })
end
```

Runs whenever a project is updated successfully.

***

### OnProjectDeleted

```lua
---@param source number
---@param projectId number|string
SB.OnProjectDeleted = function(source, projectId)
    print(('Project deleted: %s'):format(projectId))
end
```

Runs after a project is deleted.

***

### OnProjectSpawned

```lua
---@param project table
SB.OnProjectSpawned = function(project)
    print(('Project spawned: %s'):format(project.label))
end
```

Runs after a `map` project is spawned for all players.

***

### OnProjectDespawned

```lua
---@param projectId number|string
SB.OnProjectDespawned = function(projectId)
    print(('Project despawned: %s'):format(projectId))
end
```

Runs after a spawned project is removed from the world.

***

### OnZoneCreated

```lua
---@param source number
---@param zone table
SB.OnZoneCreated = function(source, zone)
    print(('Zone created: %s'):format(zone.label))
end
```

Runs after a new zone is created.

***

### OnZoneSaved

```lua
---@param source number
---@param zone table
SB.OnZoneSaved = function(source, zone)
    print(('Zone saved: %s'):format(zone.label))
end
```

Runs after a zone is updated.

***

### OnZoneDeleted

```lua
---@param source number
---@param zoneId number|string
SB.OnZoneDeleted = function(source, zoneId)
    print(('Zone deleted: %s'):format(zoneId))
end
```

Runs after a zone is deleted.

## Client-side handlers

{% hint style="info" %}
Client handlers are useful for HUD integration, custom sounds, local analytics, or reacting to editor state changes.
{% endhint %}

### OnNotify

```lua
---@param msg string|table
SB.OnNotify = function(msg)
    print('Notification:', msg)
end
```

Runs when a notification is shown on the client.

***

### OnUIToggle

```lua
---@param visible boolean
SB.OnUIToggle = function(visible)
    TriggerEvent('my-hud:toggle', not visible)
end
```

Runs when the editor UI opens or closes.

***

### OnEntityCreated

```lua
---@param entity table
---@param entityHandle number
SB.OnEntityCreated = function(entity, entityHandle)
    PlaySoundFrontend(-1, 'SELECT', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true)
end
```

Runs after an entity is created in the currently opened project.

***

### OnEntityMoved

```lua
---@param entity table
---@param entityHandle number
---@param position table
---@param rotation table
SB.OnEntityMoved = function(entity, entityHandle, position, rotation)
    print('Entity moved to', position.x, position.y, position.z)
end
```

Runs when a project entity is moved or rotated.

***

### OnEntityDeleted

```lua
---@param entity table
---@param entityHandle number
SB.OnEntityDeleted = function(entity, entityHandle)
    print('Entity deleted', entityHandle)
end
```

Runs after an entity is removed from the current project.

***

### OnProjectOpened

```lua
---@param project table
SB.OnProjectOpened = function(project)
    print(('Opened project: %s'):format(project.label))
end
```

Runs when a project is opened inside the editor.

***

### OnProjectClosed

```lua
---@param project table
SB.OnProjectClosed = function(project)
    print('Project closed')
end
```

Runs when the current project is closed.

***

### OnProjectMapSpawned

```lua
---@param project table
SB.OnProjectMapSpawned = function(project)
    print(('Preview spawned: %s'):format(project.label))
end
```

Runs when a project map is spawned on the client.

***

### OnProjectMapDespawned

```lua
---@param projectId number|string
SB.OnProjectMapDespawned = function(projectId)
    print(('Preview despawned: %s'):format(projectId))
end
```

Runs when a project map is despawned on the client.

***

### OnZoneOpened

```lua
---@param zone table
SB.OnZoneOpened = function(zone)
    print(('Zone opened: %s'):format(zone.label))
end
```

Runs when a zone is opened in the zone editor.

***

### OnZoneClosed

```lua
---@param zone table
SB.OnZoneClosed = function(zone)
    print('Zone closed')
end
```

Runs when the current zone is closed.

***

### OnZonePointAdded

```lua
---@param zone table
---@param point table
SB.OnZonePointAdded = function(zone, point)
    print(('Zone point added: %s'):format(point.id))
end
```

Runs after a point is added to the current zone.

***

### OnZonePointMoved

```lua
---@param zone table
---@param point table
---@param pointIndex number
SB.OnZonePointMoved = function(zone, point, pointIndex)
    print(('Zone point moved: %s'):format(pointIndex))
end
```

Runs after a zone point is moved.

***

### OnZonePointRemoved

```lua
---@param zone table
---@param pointIndex number
SB.OnZonePointRemoved = function(zone, pointIndex)
    print(('Zone point removed: %s'):format(pointIndex))
end
```

Runs after a point is removed from the current zone.

## Internal callback surface

The resource also uses an internal client/server callback bus through `Callback(...)`, `CallbackAwait(...)`, and `CallbackRegister(...)`.

This is mainly used by the resource itself for actions such as:

* loading projects
* saving projects
* spawning projects
* creating and editing zones
* fetching player permissions and online players

If you extend the resource directly, reuse this pattern carefully and keep callback names under the `sb-map-editor:` namespace.

### OnEntityCreated

Called when an entity is created in the project.

```lua
---@param entity table The entity data that was created
---@param entityHandle number The game entity handle
SB.OnEntityCreated = function(entity, entityHandle)
    -- Play sound effect
    PlaySoundFrontend(-1, "PLACE_PROP", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
    
    -- Show notification
    TriggerEvent('chat:addMessage', {
        args = {'Map Editor', 'Entity created: ' .. entity.model}
    })
end
```

***

### OnEntityMoved

Called when an entity is moved or rotated.

```lua
---@param entity table The entity data
---@param entityHandle number The game entity handle
---@param position table New position {x, y, z}
---@param rotation table New rotation {x, y, z}
SB.OnEntityMoved = function(entity, entityHandle, position, rotation)
    -- Update custom collision system
    PlaySoundFrontend(-1, "NAV_UP_DOWN", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
end
```

***

### OnEntityDeleted

Called when an entity is deleted from the project.

{% hint style="warning" %}
This handler is currently a placeholder for future implementation.
{% endhint %}

```lua
---@param entity table The entity data that was deleted
---@param entityHandle number The game entity handle
SB.OnEntityDeleted = function(entity, entityHandle)
    -- Cleanup custom data
    print(string.format("Entity deleted: %s", entity.model))
end
```

***

## Project Handlers (Client)

### OnProjectOpened

Called when a project is opened for editing.

```lua
---@param project table The project data that was opened
SB.OnProjectOpened = function(project)
    local editingStartTime = GetGameTimer()
    print(string.format("Started editing project: %s", project.label))
end
```

***

### OnProjectClosed

Called when a project is closed.

```lua
---@param project table The project data that was closed (may be nil)
SB.OnProjectClosed = function(project)
    if project then
        print(string.format("Finished editing project: %s", project.label))
    end
end
```

***

### OnProjectMapSpawned

Called when a project map is spawned in the world.

```lua
---@param project table The project being spawned
SB.OnProjectMapSpawned = function(project)
    print(string.format("Map '%s' spawned with %d entities", 
        project.label, #project.entities))
end
```

***

### OnProjectMapDespawned

Called when a project map is despawned from the world.

```lua
---@param projectId number|string The ID of the project being despawned
SB.OnProjectMapDespawned = function(projectId)
    print(string.format("Map with ID %s despawned", projectId))
end
```

***

## Zone Handlers (Client)

### OnZoneOpened

Called when a zone is opened for editing.

```lua
---@param zone table The zone data that was opened
SB.OnZoneOpened = function(zone)
    print(string.format("Opened zone: %s with %d points", 
        zone.label, #zone.points))
end
```

***

### OnZoneClosed

Called when a zone is closed.

```lua
---@param zone table The zone data that was closed (may be nil)
SB.OnZoneClosed = function(zone)
    if zone then
        print(string.format("Closed zone: %s", zone.label))
    end
end
```

***

### OnZonePointAdded

Called when a point is added to a zone.

```lua
---@param zone table The current zone data
---@param point table The point that was added {id, position}
SB.OnZonePointAdded = function(zone, point)
    -- Play sound effect
    PlaySoundFrontend(-1, "WAYPOINT_SET", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
    
    print(string.format("Zone point added at: %.2f, %.2f, %.2f", 
        point.position.x, point.position.y, point.position.z))
end
```

***

### OnZonePointMoved

Called when a zone point is moved.

```lua
---@param zone table The current zone data
---@param point table The point that was moved {id, position}
---@param pointIndex number The index of the point in the points array
SB.OnZonePointMoved = function(zone, point, pointIndex)
    -- Update custom visualizations
    print(string.format("Zone point %d moved", pointIndex))
end
```

***

### OnZonePointRemoved

Called when a zone point is removed.

```lua
---@param zone table The current zone data
---@param pointIndex number The index of the removed point
SB.OnZonePointRemoved = function(zone, pointIndex)
    -- Update visualizations
    print(string.format("Zone point %d removed", pointIndex))
end
```

***

## Types

### Project

```lua
---@class Project
---@field id number|string Project identifier
---@field label string Project name
---@field description string Project description
---@field zone number|string|nil Associated zone ID (optional)
---@field entities table[] Array of entity objects
---@field folders table[] Array of folder objects
---@field autoSpawn boolean Whether project spawns automatically
---@field dateCreated number Creation timestamp
---@field dateUpdated number Last update timestamp
---@field userCreated string Creator name
---@field userIdentifierCreated string Creator identifier
```

### Zone

```lua
---@class Zone
---@field id number|string Zone identifier
---@field label string Zone name
---@field points table[] Array of zone points
---@field access table[] Access control list
---@field blacklist boolean Whether zone is a blacklist zone
```

### Entity

```lua
---@class Entity
---@field entity number Game entity handle
---@field model string Model name/hash
---@field position table Position {x, y, z}
---@field rotation table Rotation {x, y, z}
---@field collision boolean Whether entity has collision
---@field renderDistance number Render distance in units
---@field folder string|nil Parent folder (optional)
```

### ZonePoint

```lua
---@class ZonePoint
---@field id number Point identifier
---@field position table Position {x, y, z}
```

## Best Practices

{% hint style="success" %}
**Keep handlers lightweight** - Avoid blocking operations. Use `Citizen.CreateThread()` for heavy tasks.
{% endhint %}

{% hint style="success" %}
**Check data existence** - Always verify data exists before accessing it: `if project then ... end`
{% endhint %}

{% hint style="success" %}
**Return values matter** - Some handlers affect behavior:

* `OnPermissionCheck`: return `true`/`false`/`nil`
* `OnNotify` (server): return modified message or `nil`
  {% endhint %}

{% hint style="danger" %}
**Don't remove handlers** - Leave them empty if not needed. Removing them is safe but not recommended.
{% endhint %}

## Testing Your Handlers

To test if your handlers are working:

1. Add print statements in the handlers
2. Perform the corresponding action in-game
3. Check your server console or F8 console for output

### Example Test Handler

```lua
SB.OnProjectCreated = function(source, project)
    print("=== HANDLER TEST ===")
    print("Project created:", json.encode(project))
    print("===================")
end
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://squirrel-bracket.gitbook.io/squirrel-bracket-documentation/map-editor/api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
