Comment on page
📃

Config Walkthrough

A full walkthrough of all configuration options
Client Config
Server Config
UI Config

Client Configuration

Framework Config

Config = {}
Config.Framework = 'QBCORE' -- 'QBCORE' or 'ESX'
if Config.Framework == 'QBCORE' then
QBCore = exports['qb-core']:GetCoreObject()
elseif Config.Framework == 'ESX' then
-- Newer esx versions e.g.
ESX = exports['es_extended']:getSharedObject()
-- Older versions
-- while ESX == nil do
-- TriggerEvent('esx:getSharedObject', function(sharedObject) ESX = sharedObject end)
-- Wait(500)
-- end
end
Change this depending on which framework you use.
Keybinds
-- Default: M
RegisterKeyMapping("icy_phone_toggle", "Phone", "keyboard", "M")
RegisterCommand("icy_phone_toggle", function()
exports[GetCurrentResourceName()]:open()
end, false)
-- Default: Backspace
RegisterKeyMapping("icy_phone_hangup", "Hang up phone", "keyboard", "BACK")
RegisterCommand("icy_phone_hangup", function()
exports[GetCurrentResourceName()]:callHangup()
end, false)
-- Default: Enter
RegisterKeyMapping("icy_phone_answer", "Answer incoming call", "keyboard", "RETURN")
RegisterCommand("icy_phone_answer", function()
exports[GetCurrentResourceName()]:callAnswer()
end, false)
Customize keybinds for the phone, or even remove these completely to make your own keybind system using the exports provided.

PhotoWebook

Config.PhotoWebhook = '<webhook here>'
The photo webhook should be a private channel somewhere, this is what the phone will use to upload photos people take & act as a sort of storage for the images. If you dont understand what webhooks are:

PhoneModel

Config.PhoneModel = `prop_npc_phone_02`
If you have a custom made model for a phone, you can place it here. If you change this ensure the model is enclosed with ` instead of quotes, using ` returns the hash of the model.
AllowedPhoneItems
Config.AllowedPhoneItems = { ['phone'] = true }
The list of inventory items that count as a phone

OnPlayerDied

Config.OnPlayerDied = ''
An event that is called when the player dies (to stop music playing, hanging up phone, etc.)
OnPlayerLoaded
Config.OnPlayerLoaded = 'QBCore:Client:OnPlayerLoaded'

OnPlayerUnload

Config.OnPlayerUnload = 'QBCore:Client:OnPlayerUnload'

OnWeatherChange

Config.OnWeatherChange = 'qb-weathersync:client:SyncWeather'
An event that is called when the weather is changed, if you are using qb-weathersync leave this as is.

AddPlayerToCall

Config.AddPlayerToCall = function(callId)
exports["pma-voice"]:addPlayerToCall(callId)
-- Mumble example: exports["mumble-voip"]:addPlayerToCall(callId)
-- Saltychat example: TriggerServerEvent('icy-phone.config.AddPlayerToCall', callId)
end
Configure this function to support any VOIP script you would like, we have included an example for mumble-voip & saltychat - Saltychat will require you to configure the event provided in the server config.

RemovePlayerFromCall

Config.RemovePlayerFromCall = function(callId)
exports["pma-voice"]:removePlayerFromCall(callId)
-- Mumble example: exports["mumble-voip"]:removePlayerFromCall(callId)
-- Saltychat example: TriggerServerEvent('icy-phone.config.RemovePlayerFromCall', callId)
end
Configure this function to support any VOIP script you would like, we have included an example for mumble-voip & saltychat - Saltychat will require you to configure the event provided in the server config.
FakeTemperatures
Config.FakeTemperatures = {
['EXTRASUNNY'] = 27,
['CLEAR'] = 23,
['NEUTRAL'] = 21,
['SMOG'] = 19,
['FOGGY'] = 14,
['OVERCAST'] = 13,
['CLOUDS'] = 16,
['CLEARING'] = 17,
['RAIN'] = 12,
['THUNDER'] = 11,
['SNOW'] = -2,
['BLIZZARD'] = -5,
['SNOWLIGHT'] = -3,
['XMAS'] = -5,
['HALLOWEEN'] = 20,
}
The temperatures that are shown on the phone for each of the different weather types

HasPhone

Config.HasPhone = function()
if Config.Framework == 'QBCORE' then
local PlayerData = QBCore.Functions.GetPlayerData()
if PlayerData == nil or PlayerData.items == nil then return false end
for idx, item in pairs(PlayerData.items) do
if Config.AllowedPhoneItems[item.name] then return true end
end
return false
elseif Config.Framework == 'ESX' then
local PlayerData = ESX.GetPlayerData()
if PlayerData == nil or PlayerData.inventory == nil then return false end
for idx, item in pairs(PlayerData.inventory) do
if Config.AllowedPhoneItems[item.name] then return true end
end
return false
end
end
This function is configurable and its purpose is to check if the player has a phone, you can change this however you would like but make sure you know what you're doing.
CanOpenPhone
Config.CanOpenPhone = function()
if IsPauseMenuActive() then return false end
if not Config.HasPhone() then
Config.Notify('You do not have a phone', 'error', 3500)
return false
end
return true
end
This function is configurable and its purpose is to check if the player can open their phone, add checks in here like if the player is dead, handcuffed, etc.

GetPlayerJob

Config.GetPlayerJob = function()
if Config.Framework == 'QBCORE' then
local PlayerData = QBCore.Functions.GetPlayerData()
return PlayerData.job.name
elseif Config.Framework == 'ESX' then
local PlayerData = ESX.GetPlayerData()
return PlayerData.job.name
end
end

Notify

Config.Notify = function(message, notificationType, duration)
if Config.Framework == 'QBCORE' then
QBCore.Functions.Notify(message, notificationType, duration)
elseif Config.Framework == 'ESX' then
ESX.ShowNotification(message, notificationType, duration)
end
end
Notification function, change this if you use a different type of notification resource.

StopIdleCam

Config.StopIdleCam = true
Stop the camera from going into idle mode when using the phone

DynamicFocus

Config.DynamicFocus = true
Dynamic UI focus, enables walking around, driving etc whilst using the phone.

ShareContactMinDistance

Config.ShareContactMinDistance = 4.0

AirdropImageMinDistance

Config.AirdropImageMinDistance = 4.0

MusicInterval

Config.MusicInterval = 200
Our music system is highly optimized & you can choose how fast the music is updated, we recommend 200, but if you'd like to change it, heres an idea of performance:
  • Lower number = faster update but greater performance loss
  • Higher number = slower update but little performance loss

DisableControls

Config.DisableControls = function() -- Disable controls whilst phone is open
DisableControlAction(0, 1, true) -- INPUT_LOOK_LR
DisableControlAction(0, 2, true) -- INPUT_LOOK_UD
DisableControlAction(0, 25, true) -- INPUT_AIM
DisableControlAction(0, 24, true) -- INPUT_ATTACK
DisableControlAction(0, 257, true) -- INPUT_ATTACK2
DisableControlAction(0, 263, true) -- INPUT_MELEE_ATTACK1
DisableControlAction(0, 264, true) -- INPUT_MELEE_ATTACK2
DisableControlAction(0, 140, true) -- INPUT_MELEE_ATTACK_LIGHT
DisableControlAction(0, 141, true) -- INPUT_MELEE_ATTACK_HEAVY
DisableControlAction(0, 142, true) -- DINPUT_MELEE_ATTACK_ALTERNATE
DisableControlAction(0, 45, true) -- INPUT_RELOAD
DisableControlAction(0, 44, true) -- INPUT_COVER
DisableControlAction(2, 36, true) -- INPUT_DUCK
DisableControlAction(0, 47, true) -- INPUT_DETONATE
DisableControlAction(0, 73, true) -- INPUT_VEH_DUCK
DisableControlAction(0, 26, true) -- INPUT_LOOK_BEHIND
DisableControlAction(0, 37, true) -- INPUT_SELECT_WEAPON
DisableControlAction(2, 199, true) -- INPUT_FRONTEND_PAUSE
DisableControlAction(0, 81, true) -- INPUT_VEH_NEXT_RADIO
DisableControlAction(0, 244, true) -- INPUT_INTERACTION_MENU
DisableControlAction(0, 170, true) -- INPUT_SAVE_REPLAY_CLIP
DisableControlAction(0, 245, true) -- INPUT_MP_TEXT_CHAT_ALL
DisableControlAction(0, 303, true) -- INPUT_REPLAY_SCREENSHOT
DisableControlAction(0, 167, true) -- INPUT_SELECT_CHARACTER_FRANKLIN
DisableControlAction(0, 29, true) -- INPUT_SPECIAL_ABILITY_SECONDARY
DisableControlAction(0, 288, true) -- INPUT_REPLAY_START_STOP_RECORDING
DisableControlAction(0, 289, true) -- INPUT_REPLAY_START_STOP_RECORDING_SECONDARY
DisableControlAction(0, 200, true) -- INPUT_FRONTEND_PAUSE_ALTERNATE
DisableControlAction(0, 210, true) -- INPUT_FRONTEND_RS
end
This function is called in a loop whilst the phone is open, add whatever controls you would like to disable whilst the player has their phone open.

DebugMode

Config.DebugMode = false
Only enable this if advised in a ticket

Server Configuration

Framework Config

Config = {}
Config.Framework = 'QBCORE' -- 'QBCORE' or 'ESX'
if Config.Framework == 'QBCORE' then
QBCore = exports['qb-core']:GetCoreObject()
elseif Config.Framework == 'ESX' then
-- Newer esx versions e.g.
ESX = exports['es_extended']:getSharedObject()
-- Older versions
-- while ESX == nil do
-- TriggerEvent('esx:getSharedObject', function(sharedObject) ESX = sharedObject end)
-- Wait(500)
-- end
end
Change this depending on which framework you use.

QueryExport

Config.QueryExport = 'oxmysql' -- oxmysql | ghmattimysql

GetPlayerMoney

Config.GetPlayerMoney = function(src, moneyType) -- 'bank' or 'cash'
if Config.Framework == 'QBCORE' then
local Player = QBCore.Functions.GetPlayer(src)
return Player.PlayerData.money[moneyType]
elseif Config.Framework == 'ESX' then
local xPlayer = ESX.GetPlayerFromId(src)
return xPlayer.getAccount(moneyType).money
end
end

RemovePlayerMoney

Config.RemovePlayerMoney = function(src, moneyType, amount, reason) -- 'bank' or 'cash'
if Config.Framework == 'QBCORE' then
local Player = QBCore.Functions.GetPlayer(src)
return Player.Functions.RemoveMoney(moneyType, amount, reason)
elseif Config.Framework == 'ESX' then
local xPlayer = ESX.GetPlayerFromId(src)
return xPlayer.removeAccountMoney(moneyType, amount)
end
end

AddPlayerMoney

Config.AddPlayerMoney = function(src, moneyType, amount, reason) -- 'bank' or 'cash'
if Config.Framework == 'QBCORE' then
local Player = QBCore.Functions.GetPlayer(src)
return Player.Functions.AddMoney(moneyType, amount, reason)
elseif Config.Framework == 'ESX' then
local xPlayer = ESX.GetPlayerFromId(src)
return xPlayer.addAccountMoney(moneyType, amount)
end
end
GetPlayerIdentifier
Config.GetPlayerIdentifier = function(src)
if Config.Framework == 'QBCORE' then
local Player = QBCore.Functions.GetPlayer(src)
return Player.PlayerData.citizenid
elseif Config.Framework == 'ESX' then
local xPlayer = ESX.GetPlayerFromId(src)
return xPlayer.identifier
end
end
Configure this function to get a players identifier.

GetPhoneNumber

Config.GetPhoneNumber = function(src)
if Config.Framework == 'QBCORE' then
local Player = QBCore.Functions.GetPlayer(src)
return Player.PlayerData.charinfo.phone
elseif Config.Framework == 'ESX' then
local xPlayer = ESX.GetPlayerFromId(src)
if xPlayer == nil then return nil end
local phoneNumber = exports[Config.QueryExport]:scalarSync('SELECT `phone_number` FROM `users` WHERE `identifier` = ?', { xPlayer.identifier })
if phoneNumber == nil or phoneNumber == "" then
phoneNumber = Config.GeneratePhoneNumber(xPlayer.identifier)
end
return phoneNumber
end
end
This function is configurable and its purpose is to return the players phone number, you can change this however you would like but make sure you know what you're doing.

GeneratePhoneNumber

Config.GeneratePhoneNumber = function(identifier)
-- ESX - Generates a phone number that is not in use, change this if you want a different format for numbers
local desiredNumber = '04' .. math.random(11111, 99999)
local existingIdentifier = exports[Config.QueryExport]:scalarSync('SELECT `identifier` FROM `users` WHERE `phone_number` = ?', { desiredNumber })
while existingIdentifier ~= nil do
desiredNumber = '04' .. math.random(11111, 99999)
existingIdentifier = exports[Config.QueryExport]:scalarSync('SELECT `identifier` FROM `users` WHERE `phone_number` = ?', { desiredNumber })
Wait(50)
end
exports[Config.QueryExport]:executeSync('UPDATE `users` SET `phone_number` = ? WHERE `identifier` = ?', { desiredNumber, identifier })
return desiredNumber
end
This function is configurable and its purpose is to generate a new phone number for a player - ONLY USED WITH ESX (QBCore creates player phone numbers when the character is created.)

GetPlayerByPhoneNumber

Config.GetPlayerByPhoneNumber = function(phoneNumber)
if Config.Framework == 'QBCORE' then
return QBCore.Functions.GetPlayerByPhone(phoneNumber)
elseif Config.Framework == 'ESX' then
local identifier = exports[Config.QueryExport]:scalarSync('SELECT `identifier` FROM `users` WHERE `phone_number` = ?', { phoneNumber })
if identifier ~= nil then
return ESX.GetPlayerFromIdentifier(identifier)
end
end
return nil
end

GetPlayerByIdentifier

Config.GetPlayerByIdentifier = function(identifier)
if Config.Framework == 'QBCORE' then
return QBCore.Functions.GetPlayerByCitizenId(identifier)
elseif Config.Framework == 'ESX' then
return ESX.GetPlayerFromIdentifier(identifier)
end
end

GetPlayerFullName

Config.GetPlayerFullname = function(src)
local Player = QBCore.Functions.GetPlayer(src)
return Player.PlayerData.charinfo.firstname.." "..Player.PlayerData.charinfo.lastname
end
Get a players formatted full name, used for banking logs, etc.

GetPlayerJob

Config.GetPlayerJob = function(src)
if Config.Framework == 'QBCORE' then
local Player = QBCore.Functions.GetPlayer(src)
return Player.PlayerData.job.name
elseif Config.Framework == 'ESX' then
local xPlayer = ESX.GetPlayerFromId(src)
return xPlayer.job.name
end
end

GetPlayerJobGrade

Config.GetPlayerJobGrade = function(src)
if Config.Framework == 'QBCORE' then
local Player = QBCore.Functions.GetPlayer(src)
return Player.PlayerData.job.grade.level
elseif Config.Framework == 'ESX' then
local xPlayer = ESX.GetPlayerFromId(src)
return xPlayer.job.grade
end
end

GetPlayerJobIsBoss

Config.GetPlayerJobIsBoss = function(src)
if Config.Framework == 'QBCORE' then
local Player = QBCore.Functions.GetPlayer(src)
return Player.PlayerData.job.isboss
elseif Config.Framework == 'ESX' then
-- TODO: Could not find a way to get this in base ESX, add whatever check you have here
return false
end
end

GetJobLabel

Config.GetJobLabel = function(job)
if Config.Framework == 'QBCORE' then
return QBCore.Shared.Jobs[job].label
elseif Config.Framework == 'ESX' then
local jobs = ESX.GetJobs()
return jobs[job].label
end
end

GetPlayerFullname

Config.GetPlayerFullname = function(src)
if Config.Framework == 'QBCORE' then
local Player = QBCore.Functions.GetPlayer(src)
return Player.PlayerData.charinfo.firstname.." "..Player.PlayerData.charinfo.lastname
elseif Config.Framework == 'ESX' then
local xPlayer = ESX.GetPlayerFromId(src)
return xPlayer.name
end
end

GetOfflinePlayerFullName

Config.GetOfflinePlayerFullname = function(identifier)
if Config.Framework == 'QBCORE' then
local result = exports[Config.QueryExport]:scalarSync("SELECT `charinfo` FROM `players` WHERE `citizenid` = ?", { identifier })
if result == nil then return "Unknown Person" end
local charinfo = json.decode(result)
return charinfo.firstname.." "..charinfo.lastname
elseif Config.Framework == 'ESX' then
local result = exports[Config.QueryExport]:executeSync("SELECT `firstname`, `lastname` FROM `users` WHERE `identifier` = ?", { identifier })
if result[1] == nil then return "Unknown Person" end
return result[1].firstname.." "..result[1].lastname
end
end
Get an offline players full name, used for banking logs, etc.

IsPlayerAdmin

Config.IsPlayerAdmin = function(src)
if Config.Framework == 'QBCORE' then
return QBCore.Functions.IsOptin(src)
elseif Config.Framework == 'ESX' then
local xPlayer = ESX.GetPlayerFromId(src)
return xPlayer.getGroup() == 'admin'
end
end
Check if a player is an admin, if you do not want to use any admin specific tools within the phone just make this function return false.
IsPlayerOnDuty
Config.IsPlayerOnDuty = function(src)
if Config.Framework == 'QBCORE' then
local Player = QBCore.Functions.GetPlayer(src)
return Player.PlayerData.job.onduty
elseif Config.Framework == 'ESX' then
return true
end
end
Check if a player is on duty in their current job
GetJobDutyCount
Config.GetJobDutyCount = function(job)
if Config.Framework == 'QBCORE' then
local players = QBCore.Functions.GetQBPlayers()
local count = 0
for idx, player in pairs(players) do
if player.PlayerData.job.name == job and player.PlayerData.job.onduty then
count = count + 1
end
end
return count
elseif Config.Framework == 'ESX' then
-- Newer versions of ESX have a GetExtendedPlayers function
local xPlayers = ESX.GetExtendedPlayers("job", job)
return #xPlayers
-- Older versions of ESX:
-- local count = 0
-- local xPlayers = ESX.GetPlayers()
-- for i = 1, #xPlayers, 1 do
-- local xPlayer = ESX.GetPlayerFromId(xPlayers[i])
-- if xPlayer.job.name == job then count = count + 1 end
-- end
-- return count
end
end
Returns the number of people currently on duty in a job, this is used for the Services app.
Voice Events
RegisterNetEvent("icy-phone.config.AddPlayerToCall", function(callId)
local src = source
-- e.g. exports["saltychat"]:AddPlayerToCall(callId, src)
end)
RegisterNetEvent("icy-phone.config.RemovePlayerFromCall", function(callId)
local src = source
-- e.g. exports["saltychat"]:RemovePlayerFromCall(callId, src)
end)
If you need to use a server export or something similar instead of the client exports, configure these, if you havent changed the voice config ignore this.

BankAccountCreateCost

Config.BankAccountCreateCost = 5000
The cost to open a new bank account. NOTE: if you change this, make sure you change the locales for the banking page, a tutorial for changing locales is located here:

RemoveAdvertOnDisconnect

Config.RemoveAdvertOnDisconnect = true
Whether or not to remove adverts when a player disconnects

BusinessInvoices

-- allowed = true/false - Can the job create invoices
-- minimumGrade = 0 - Minimum grade required to create invoices
-- deadlineHours = 24 - How many hours until the invoice expires (E.g. police warrant issued for no payment)
Config.BusinessInvoices = {
["police"] = { allowed = true, minimumGrade = 0, },
["ambulance"] = { allowed = true, minimumGrade = 0, },
["mechanic"] = { allowed = true, minimumGrade = 0, },
}
Define which jobs can create invoices, this will also let the business access their account in the banking app.
Services
Config.Services = {
["police"] = {
label = "Victoria Police",
enabled = true,
},
["ambulance"] = {
label = "Ambulance Victoria",
enabled = true,
},
["mechanic"] = {
label = "RACV",
enabled = true,
},
}
Define which jobs show in the services app

Garages

Config.Garages = {
["pillboxgarage"] = { label = "Pillbox Parking", position = vector4(1032.52, -772.18, 60.68, 143.06) },
}
Input your garages here, this is used in the garages app so that when a person clicks their vehicle, it will set a waypoint to the garage it is stored at.

Crypto

Configure crypto to your liking, change how coin prices move each day.

Crypto.ConsoleOutput

Config.Crypto.ConsoleOutput = true
Whether or not to print, price changes & other messages to the console

Crypto.PriceUpdateSpeed

Config.Crypto.PriceUpdateSpeed = 60000
The speed in which crypto will update all coin pricings, lower the number, the faster it will update, we recommend leaving this setting default at 60000 miliseconds.

Config.Pricing

Config.Crypto.Pricing = {
["bitcoin"] = {
pricingType = "dynamic",
defaultPrice = 25000,
dynamicVolatility = 5,
dynamicSpacing = { min = -25, max = 25 },
pricingLimits = { min = 22750, max = 27250 },
},
["ethereum"] = {
pricingType = "dynamic",
defaultPrice = 4500,
dynamicVolatility = 5,
dynamicSpacing = { min = -25, max = 25 },
pricingLimits = { min = 4175, max = 4825 },
},
["bnb"] = {
pricingType = "dynamic",
defaultPrice = 450,
dynamicVolatility = 5,
dynamicSpacing = { min = -5, max = 5 },
pricingLimits = { min = 415, max = 485 },
}
}
Some default cryptos have been added to help you understand how to make your own, the coin labelled "default" represents the base QBCore Crypto e.g. PlayerData.money["crypto"]. By default this coin is set to static, which means the price wont move from what you set it to in the SQL table. The other settings determine how the price moves at each update, read above to get a summary.
If you would like to make your own coin go ahead! but make sure you:
  • Create new locales for the coin in page-crypto.json
  • Create an image for the crypto in build/assets/crypto
Once you have done that, add your new crypto to the list & the pricing will be automatically populated once you restart the resource / your server.

Webhooks

Config.Webhooks = {
-- Regular 'Public' channels - Shows ingame tweets in discord
["twitter"] = {
enabled = true,
url = ''
},
["advertisements"] = {
enabled = true,
url = ''
},
-- Private 'Logging' channels - For moderation
["logs-twitter"] = {
enabled = true,
url = ''
},
["logs-twitter-accounts"] = {
enabled = true,
url = ''
},
["logs-advertisements"] = {
enabled = true,
url = ''
},
["logs-calls"] = {
enabled = true,
url = ''
},
["logs-messages"] = {
enabled = true,
url = ''
},
["logs-darkchat"] = {
enabled = true,
url = ''
},
}
Here, you can set the webhooks for the moderation & public channels. If you dont understand what webhooks are:

DebugMode

Config.DebugMode = false
Only enable this if advised in a ticket

UI Configuration

interface/config/config.json

showWeatherWidget

"showWeatherWidget": true
Use this to toggle if the weather widget is shown on the homepage.

applications

"applications": {
"CALLS": true,
"MESSAGES": true,
"SETTINGS": true,
"CONTACTS": true,
...
}
Here you can disable any app you would like, just set the value to false and the app wont appear on the homescreen.

phoneFrames

"phoneFrames": [
{ "value": "iphone14_silver", "label": "iPhone 14 Silver" },
{ "value": "iphone14_black", "label": "iPhone 14 Space Grey" },
{ "value": "iphone14_orange", "label": "iPhone 14 Orange" },
{ "value": "iphone14_blue", "label": "iPhone 14 Blue" },
{ "value": "iphone14_red", "label": "iPhone 14 Red" },
{ "value": "iphone14pro_silver", "label": "iPhone 14 Pro Silver" },
{ "value": "iphone14pro_black", "label": "iPhone 14 Pro Space Grey" },
{ "value": "iphone14pro_red", "label": "iPhone 14 Pro Red" }
]
Here you can configure the available phone frames, view our guide for customization to be able to make fully custom frames for your server:
languages
"languages": [
{ "value": "en", "label": "English" }
]
To add new languages to the locale, add a folder to the locales directory, for example: "build/locales/fr", copy the translation files over from the "en" folder and change any locale you would like. If you have translated a full set of locales & want to share it please do so in our discord so we can add it to the official build of the phone.