-- ┌──────────────────────────────────────────────────────────────────┐ -- │ ERROR HANDLING │ -- └──────────────────────────────────────────────────────────────────┘ local function getErrorTitle(errorCount) if errorCount then return ('[%s v%s - %s - #%s]'):format(GetCurrentResourceName(), GetResourceMetadata(GetCurrentResourceName(), 'version', 0), 'client', errorCount or '') else return ('[%s v%s - %s]'):format(GetCurrentResourceName(), GetResourceMetadata(GetCurrentResourceName(), 'version', 0), 'client') end end local function CreateHeaderLine(max_length, errorCount) local content_length = #getErrorTitle(errorCount) if content_length >= max_length then max_length = content_length + 2 end local side = math.floor((max_length - content_length) / 2) local left = string.rep('=', side) local right = string.rep('=', max_length - content_length - side) return ('^0%s%s%s^0'):format(left, getErrorTitle(errorCount), right) end local function CreateFooterLine(length, char) char = char or '=' if length < 1 then length = 1 end return ('^0%s^0'):format(string.rep(char, length)) end local function StripColors(str) return str:gsub('%^%d', '') end local function GetMaxLineLength(...) local max = 0 for _, str in ipairs({...}) do str = tostring(str or '') local clean = StripColors(str) local len = #clean if len > max then max = len end end if max < 20 then max = 20 elseif max > 80 then max = 80 end return max end local function GetCallOrigin() local info = debug.getinfo(3, 'Sl') if not info then return ('@%s:unknown:0'):format(GetCurrentResourceName()) end return (info.short_src or ('@%s:unknown'):format(GetCurrentResourceName())) .. ':' .. (info.currentline or '0') end function WARN(error_code, explanation) error_code = error_code and tostring(error_code) or 'NULL' local origin = GetCallOrigin() or 'unknown' explanation = explanation and tostring(explanation) or 'No explanation provided' local errorCount = exports.cd_bridge:StoreError(error_code, origin, explanation) local line1 = string.format('^3%s: %s', Locale('error_code'), error_code) local line2 = string.format('^3%s: %s', Locale('origin'), origin) local line3 = string.format('^3%s: %s', Locale('explanation'), explanation) local line4 = string.format('^2%s?', Locale('need_support')) local line5 = string.format('^0%s: ^5https://discord.gg/codesign.', Locale('discord')) local line6 = string.format('^0%s: ^5https://docs.codesign.pro.', Locale('documentation')) local footerText = string.format('^3%s', Locale('warn_footer')) local maxLength = GetMaxLineLength( line1, line2, line3, line4, line5, line6, footerText ) Citizen.Trace(([[ ^0%s %s %s %s %s %s %s %s ^0%s ]] .. '^0\n'):format( CreateHeaderLine(maxLength, errorCount), line1, line2, line3, footerText, line4, line5, line6, CreateFooterLine(maxLength) )) end function ERROR(error_code, explanation) error_code = error_code and tostring(error_code) or 'NULL' local origin = GetCallOrigin() or 'unknown' explanation = explanation and tostring(explanation) or 'No explanation provided' local errorCount = exports.cd_bridge:StoreError(error_code, origin, explanation) local line1 = string.format('^1%s: %s', Locale('error_code'), error_code) local line2 = string.format('^1%s: %s', Locale('origin'), origin) local line3 = string.format('^1%s: %s', Locale('explanation'), explanation) local line4 = string.format('^2%s?', Locale('need_support')) local line5 = string.format('^0%s: ^5https://discord.gg/codesign.', Locale('discord')) local line6 = string.format('^0%s: ^5https://docs.codesign.pro.', Locale('documentation')) local footerText1 = string.format('^1%s', Locale('error_footer1')) local footerText2 = string.format('^1%s', Locale('error_footer2')) local footerText3 = string.format('^1%s', Locale('error_footer3')) local maxLength = GetMaxLineLength( line1, line2, line3, line4, line5, line6, footerText1, footerText2, footerText3 ) Citizen.Trace(([[ ^0%s %s %s %s %s %s %s %s %s %s ^0%s ]] .. '^0\n'):format( CreateHeaderLine(maxLength, errorCount), line1, line2, line3, footerText1, footerText2, footerText3, line4, line5, line6, CreateFooterLine(maxLength) )) end function TypeCheck(value, expected_type, error_code, explanation) local actual_type = type(value) if actual_type == expected_type then return true end error_code = error_code and tostring(error_code) or 'NULL' local origin = GetCallOrigin() or 'unknown' value = tostring(value) explanation = explanation and tostring(explanation) or 'No explanation provided' local errorCount = exports.cd_bridge:StoreError(error_code, origin, explanation, expected_type, actual_type, value) local line1 = string.format('^1%s: %s expected, got %s : [%s]', Locale('error'), expected_type, actual_type, value) local line2 = string.format('^1%s: %s', Locale('error_code'), error_code) local line3 = string.format('^1%s: %s', Locale('origin'), origin) local line4 = string.format('^1%s: %s', Locale('explanation'), explanation) local line5 = string.format('^2%s?', Locale('need_support')) local line6 = string.format('^0%s: ^5https://discord.gg/codesign.', Locale('discord')) local line7 = string.format('^0%s: ^5https://docs.codesign.pro.', Locale('documentation')) local footerText1 = string.format('^1%s', Locale('error_footer1')) local footerText2 = string.format('^1%s', Locale('error_footer2')) local footerText3 = string.format('^1%s', Locale('error_footer3')) local maxLength = GetMaxLineLength( line1, line2, line3, line4, line5, line6, line7, footerText1, footerText2, footerText3 ) Citizen.Trace(([[ ^0%s %s %s %s %s %s %s %s %s %s %s ^0%s ]] .. '^0\n'):format( CreateHeaderLine(maxLength, errorCount), line1, line2, line3, line4, footerText1, footerText2, footerText3, line5, line6, line7, CreateFooterLine(maxLength) )) return false end -- ┌──────────────────────────────────────────────────────────────────┐ -- │ REMOTE ERROR REPORTING │ -- └──────────────────────────────────────────────────────────────────┘ local function getCallResource() local invoking = GetInvokingResource() if invoking then return invoking end local info = debug.getinfo(3, 'Sl') if info and info.short_src then local resource = info.short_src:match('^@([^/]+)/') if resource then return resource end end return 'unknown' end local caughtErrors = {} local function storeError(error_code, origin, explanation, expected_type, actual_type, value) local type_check = expected_type and ('%s expected, got %s : [%s]'):format(expected_type, type(value), tostring(value)) or nil local callResource = getCallResource() or 'unknown' local version = callResource ~= 'unknown' and GetResourceMetadata(callResource, 'version', 0) or 'unknown' local title = ('%s v%s - client'):format(callResource, version) caughtErrors[#caughtErrors + 1] = { error_code = error_code, type_check = type_check, origin = origin, explanation = explanation, title = title, timestamp = GetCloudTimeAsInt() } return #caughtErrors end exports('StoreError', storeError) exports('GetErrors', function() return caughtErrors or {} end) -- ┌──────────────────────────────────────────────────────────────────┐ -- │ PRE START CHECKS │ -- └──────────────────────────────────────────────────────────────────┘ --framework checks CreateThread(function() if GetCurrentResourceName() ~= 'cd_bridge' then return end Wait(1000) if not HasFrameworkLoaded() then ERROR('framework_configuration_error_found', Cfg.Framework..' object not found. Please ensure that your framework resource is installed and running properly.') end end) -- ┌──────────────────────────────────────────────────────────────────┐ -- │ DEBUG │ -- └──────────────────────────────────────────────────────────────────┘ if GetCurrentResourceName() == 'cd_bridge' then RegisterNetEvent('cd_bridge:debug:charInfo', function(serverCharInfo) local clientCharInfo = { charName = GetCharacterName(), jobName = GetJobName(), jobLabel = GetJobLabel(), jobGrade = GetJobGrade(), jobGradeLabel = GetJobGradeLabel(), onDuty = GetJobDuty(), gangName = GetGangName(), gangLabel = GetGangLabel(), gangGrade = GetGangGrade() } local mismatchFound = {} for k, serverVal in pairs(serverCharInfo) do local clientVal = clientCharInfo[k] if serverVal ~= clientVal then mismatchFound[#mismatchFound + 1] = { info = k, server = serverVal, client = clientVal } end end if #mismatchFound > 0 then local message = '' for _, m in pairs(mismatchFound) do message = message .. string.format('^1%s: server=[%s] | client=[%s]\n', m.info, tostring(m.server), tostring(m.client)) end message = message:sub(1, -2) ERROR('7903', 'Character info mismatch found between server and client.\n\n'..message) TriggerServerEvent('cd_bridge:debug:charInfo', message) end end) end