# Developer > Developer API reference for ML Survival Kit Category: SURVIVAL KIT · Source: https://miciomods.it/docs/ml-custommap-developer · Last updated: 2026-07-09 ## Overview ml_custommap is mostly configuration-driven. It exposes two client exports for the postal radar position (below); beyond that, map access is gated through usable items registered with `Bridge.BindUsableItem`, and the full state (markers, radii, labels, images, fog bitmask) lives in the `ml_custommap_data` table keyed by serial. Developers extending the script work at three touch points: placing default markers through `Config.DefaultMarkers` or the `/addblip` admin command, adding new map images with the `/mapcalibrate` flow, and reading the database table directly for cross-resource integrations. ## Client Exports Two client exports adjust the postal radar position at runtime. They apply only when `Config.MinimapControl` is on. ```lua -- Shift the postal radar by a pixel offset exports.ml_custommap:SetMinimapOffset(offsetX, offsetY) -- Read the current offset local x, y = exports.ml_custommap:GetMinimapOffset() ``` - `SetMinimapOffset(offsetX, offsetY)`: moves the radar by the given pixel offset. - `GetMinimapOffset()`: returns the current x, y pixel offset. ## Net Events The events below are internal but may be useful for debugging or writing external tooling. They enforce rate limiting and zero-trust payload validation server-side: triggering them manually from a client will not bypass the checks. ### Server-Bound - `ml_custommap:server:saveMapData(serial, payload)`: Persists marker / radius / label / image changes. Requires an active registered serial for the source. - `ml_custommap:server:openByKeybind(mapId, itemName?)`: Keybind path. Resolves serial, loads cache, triggers `client:open`. - `ml_custommap:server:startFogSession(mapId, itemName?)`: Auto-bootstrap for background fog tracking on player load. - `ml_custommap:server:fogSync(serial, delta)`: Client pushes newly-discovered cell indices. Server OR-merges into the stored bitmask. - `ml_custommap:server:setWaypoint(data)`: Creates or replaces the player's 3D Bridge waypoint. - `ml_custommap:server:removeWaypoint(wpId?)`: Removes one or all Bridge waypoints. - `ml_custommap:server:adminOpen(mapId)`: Permission-gated admin-mode open. - `ml_custommap:server:calibrateCheck(mapId)`: Permission-gated calibration trigger. - `ml_custommap:server:debugOpen(mapId)`: Debug-only open. Requires `Config.Debug = true`. ### Client-Bound - `ml_custommap:client:open(mapId, serial, savedData)`: Opens the map UI with markers, labels, radii, images, and fog bitmask. - `ml_custommap:client:startFogSession(mapId, serial, discoveredBytes?)`: Starts background fog tracking without opening the UI. - `ml_custommap:client:setNativeMap(state)`: Toggles the client-side native map suppression. - `ml_custommap:client:calibrateAllowed(mapId)`: Server grants calibration access after the permission check. - `ml_custommap:client:adminOpen(mapId)`: Server grants admin-mode access after the permission check. ## Database Schema ```sql CREATE TABLE IF NOT EXISTS `ml_custommap_data` ( `serial` VARCHAR(64) NOT NULL, `owner_identifier` VARCHAR(100) DEFAULT NULL, `map_id` VARCHAR(64) NOT NULL, `markers` MEDIUMTEXT DEFAULT '[]', `labels` MEDIUMTEXT DEFAULT '[]', `radii` MEDIUMTEXT DEFAULT '[]', `images` MEDIUMTEXT DEFAULT '[]', `discovered` MEDIUMTEXT DEFAULT NULL, `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`serial`), INDEX `idx_owner` (`owner_identifier`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` - `serial`: Primary key. In `metadata` mode it's a generated string stored in item metadata. In `database` mode it's `license::`. - `owner_identifier`: Player license or framework identifier. Useful for cross-referencing. - `map_id`: Which `Config.Maps` entry this row belongs to. - `markers`, `labels`, `radii`, `images`: JSON arrays of sanitized objects. Schema below. - `discovered`: Base64-encoded fog-of-war bitmask. One bit per grid cell. ### JSON Shapes ```json markers: [ { "id": "m_1700000000000", "worldX": 0.0, "worldY": 0.0, "presetId": "camp", "customName": "string?", "customIcon": "fa-class?", "customColor": "#hex?", "bgCircle": true, "customSize": 28, "customAlpha": 100 } ] radii: [ { "id": "r_...", "worldX": 0.0, "worldY": 0.0, "presetId": "small", "radiusPixels": 120, "customColor": "#hex?", "customAlpha": 20 } ] labels: [ { "id": "l_...", "text": "string", "pixelX": 0.0, "pixelY": 0.0, "fontSize": 14, "color": "#hex", "rotation": 0 } ] images: [ { "id": "img_...", "url": "https://...", "pixelX": 0.0, "pixelY": 0.0, "width": 256, "height": 256, "alpha": 100, "rotation": 0 } ] ``` ## Map Calibration The `/mapcalibrate` workflow computes the affine transform between GTA world coordinates and image pixel coordinates using least-squares regression on 4+ collected points: ```lua -- Output written to clipboard calibration = { scaleX = 0.65830, offsetX = 3755.58, scaleY = -0.65836, offsetY = 5526.30, imageSize = 8192, } ``` Given world `(x, y)` the image pixel is `(x * scaleX + offsetX, y * scaleY + offsetY)`. The inverse is used when converting clicks back to world coordinates (for waypoints, radii, labels). > **TIP:** Shared calibration across maps > > Multiple map images that share the same projection and dimensions (e.g. satellite, road, atlas) can reuse the same calibration block. Calibrate one, paste into all. ## Admin Default Marker Command `/addblip [mapId]` opens the map in admin mode. Placing a marker does not persist: instead the map interface sends `adminMarkerCreated` to the client, which prints the config block to the F8 console and copies it to clipboard: ```lua { mapId = 'tourist', worldX = 2194.1, worldY = 3179.7, preset = 'camp', label = 'Zenit', }, ``` Paste the block into `Config.DefaultMarkers` and restart to make it visible to all players. ## Examples **Grant a map item from server script** ```lua -- Add a usable map item (registered via inventory system first) -- then give it to the player: Bridge.GiveItem(src, 'tourist_map', 1) ``` The first time the player uses the item with `Config.StorageMode = 'metadata'`, the script generates a unique serial, writes it to the item metadata, and creates a row in `ml_custommap_data`. Subsequent opens reuse the same serial. **Read a player's discovered zones from another resource** ```lua local rows = MySQL.query.await([[ SELECT map_id, discovered FROM ml_custommap_data WHERE owner_identifier = ? ]], { playerLicense }) for _, row in ipairs(rows) do -- row.discovered is base64-encoded. Decode to bytes, then -- iterate bits using the cellPx computed from the map's -- scaleX and Config.FogOfWar.revealRadius. end ``` **Add a new map layer** **shared/config.lua** ```lua Config.Items = { ['tourist_map'] = 'tourist', ['treasure_map'] = 'treasure', } Config.Maps['treasure'] = { label = 'Treasure Map', image = 'map_treasure.webp', imageWidth = 2048, imageHeight = 2048, calibration = { scaleX = 0, offsetX = 0, scaleY = 0, offsetY = 0, imageSize = 2048 }, defaultZoom = 1.5, maxZoom = 6.0, minZoom = 0.4, fogRevealRadius = 400, } ``` Register `treasure_map` in the inventory, drop `map_treasure.webp` into `maps/`, run `/mapcalibrate treasure` in-game, paste the values. Done.