local function IsResourceStartedOrStarting(resource) local state = GetResourceState(resource) return state == "started" or state == "starting" end if Config.Framework == "auto" then if GetResourceState('es_extended') == 'started' then Config.Framework = "es_extended" elseif GetResourceState('qbx_core') == 'started' then Config.Framework = "qbx_core" elseif GetResourceState('qb-core') == 'started' then Config.Framework = "qb-core" else Config.Framework = "standalone" end end local InternalIntegration = { framework = true, custom = true, none = true, } --- Selects the first running resource when configured as "auto". --- Supported entries are exact FiveM resource names; framework/custom/none --- are the only built-in modes that are not resources. --- @param key string --- @param supported string[] --- @param fallback string local function SelectIntegration(key, supported, fallback) local configured = Config[key] or "auto" local valid = {} local detected for i = 1, #supported do local resource = supported[i] valid[resource] = true if not detected and not InternalIntegration[resource] and IsResourceStartedOrStarting(resource) then detected = resource end end if configured == "auto" then Config[key] = detected or fallback return end if valid[configured] then return end print(("^3[GKSPHONE] Config.%s '%s' has no adapter -- using '%s'^7") :format(key, tostring(configured), fallback)) Config[key] = fallback end SelectIntegration("Inventory", { "codem-inventory", "ox_inventory", "core_inventory", "qs-inventory", "tgiann-inventory", "qb-inventory", "framework", "custom", }, "framework") SelectIntegration("BankScript", { "Renewed-Banking", "okokBanking", "fd_banking", "ps-banking", "esx_society", "qb-banking", "M-Banking", "framework", "custom", }, "framework") SelectIntegration("GarageScript", { "cd_garage", "jg-advancedgarages", "loaf_garage", "qs-advancedgarages", "esx_garage", "qb-garages", "framework", "custom", }, "framework") SelectIntegration("LicenseScript", { "bcs_licensemanager", "cs_license", "qs-licenses", "esx_license", "framework", "custom", }, "framework") SelectIntegration("VoiceScript", { "pma-voice", "saltychat", "yaca-voice", "mumble-voip", "none", }, "none") SelectIntegration("HouseScript", { "loaf_housing", "bcs_housing", "qs-housing", "vms_housing", "ps-housing", "rtx_housing", "qb-houses", "esx_property", "none", }, "none") --- @return number function ValetSpawnRadius() return math.min(tonumber(Config.ValespawnRadius) or 150.0, 300.0) end --- @return number function ValetMaxDropOff() return ValetSpawnRadius() + 25.0 end function Debugprint(...) if Config.Debug then local args = {...} local appendedStr = '' for _, v in ipairs(args) do appendedStr = appendedStr .. ' ' .. (type(v) == "table" and json.encode(v) or tostring(v)) end local msgTemplate = '^5[GKSPHONE]^0%s^0' local msg = msgTemplate:format(appendedStr) print(msg) end end function TableFilter(tbl, func) local newtbl = {} for i, v in ipairs(tbl) do if func(v) then table.insert(newtbl, v) end end return newtbl end function MathTrim(value) value = tostring(value) return (string.gsub(value, "^%s*(.-)%s*$", "%1")) end Locales = {} local LocaleAllowed = {} for _, lang in ipairs(Config.Locales or {}) do LocaleAllowed[lang] = true end local function ensureLocale(lang) local cached = Locales[lang] if cached ~= nil then return cached or nil end if not LocaleAllowed[lang] then return nil end local raw = LoadResourceFile("gksphone", "config/locales/" .. lang .. ".json") local decoded = raw and json.decode(raw) or nil Locales[lang] = decoded or false return decoded end function LocalesSettings() if IsDuplicityVersion() then for _, lang in ipairs(Config.Locales) do ensureLocale(lang) end else ensureLocale(Config.DefaultLocale) end end function IsLocaleAvailable(lang) return LocaleAllowed[lang] == true end function _T(lang, fullPath, replacements) lang = lang or Config.DefaultLocale if not ensureLocale(lang) then return "Language not found!" end local parts = {} for part in string.gmatch(fullPath, "[^.]+") do table.insert(parts, part) end local translation = Locales[lang] for _, part in ipairs(parts) do translation = translation and translation[part] or nil end if not translation then return "Translation not found!" end if replacements then for k, v in pairs(replacements) do translation = translation:gsub("{" .. k .. "}", v) end end return translation end function _U() return "0" end function GetPinLength() return Config.PinLength == 6 and 6 or 4 end function GetDefaultPhonePassword() return string.rep("0", GetPinLength()) end function IsValidPhonePin(pin) local len = GetPinLength() return type(pin) == "string" and #pin == len and pin:match("^%d+$") ~= nil end function IsPhonePinMatch(entered, stored) entered = tostring(entered or "") stored = tostring(stored or "") if entered == stored then return true end if GetPinLength() == 4 and #stored == 6 and #entered == 4 then return entered == string.sub(stored, 1, 4) end return false end LocalesSettings()