From a888f206ff3f4b33bcab820f81930eeef3120692 Mon Sep 17 00:00:00 2001 From: DerEchteAlec Date: Mon, 17 Aug 2026 05:14:34 +0200 Subject: [PATCH] FIX - resolve radio members by character identity --- frontend/src/radioIdentity.contract.test.ts | 68 +++++++++++++++++++ .../source/bridge/server/frameworks/esx.lua | 29 ++++++++ .../source/bridge/server/frameworks/qb.lua | 8 +++ .../source/bridge/server/frameworks/qbox.lua | 8 +++ sky_phone/source/server/radio.lua | 9 ++- 5 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 frontend/src/radioIdentity.contract.test.ts diff --git a/frontend/src/radioIdentity.contract.test.ts b/frontend/src/radioIdentity.contract.test.ts new file mode 100644 index 0000000..6562abd --- /dev/null +++ b/frontend/src/radioIdentity.contract.test.ts @@ -0,0 +1,68 @@ +import { readFileSync } from 'node:fs' + +import { describe, expect, it } from 'vitest' + +const qbSource = readFileSync( + new URL( + '../../sky_phone/source/bridge/server/frameworks/qb.lua', + import.meta.url, + ), + 'utf8', +) +const qboxSource = readFileSync( + new URL( + '../../sky_phone/source/bridge/server/frameworks/qbox.lua', + import.meta.url, + ), + 'utf8', +) +const esxSource = readFileSync( + new URL( + '../../sky_phone/source/bridge/server/frameworks/esx.lua', + import.meta.url, + ), + 'utf8', +) +const radioSource = readFileSync( + new URL('../../sky_phone/source/server/radio.lua', import.meta.url), + 'utf8', +) + +describe('radio member identity contract', () => { + it('exposes a framework character-name pair for every adapter', () => { + for (const source of [esxSource, qbSource, qboxSource]) { + expect(source).toContain( + 'function Bridge.Framework.GetCharacterName(source)', + ) + } + }) + + it('matches the installed lb-phone ESX database fallback', () => { + expect(esxSource).toContain( + 'function Bridge.Framework.GetCharacterName(source)', + ) + expect(esxSource).toContain( + 'SELECT `firstname`, `lastname` FROM `users` WHERE `identifier` = ? LIMIT 1', + ) + expect(esxSource).toContain('{ identifier }') + }) + + it('prefers the radio override, then the framework identity', () => { + const memberNameStart = radioSource.indexOf( + 'local function get_radio_member_name(source)', + ) + const memberNameEnd = radioSource.indexOf( + '\nend', + radioSource.indexOf('GetPlayerName(source)', memberNameStart), + ) + const memberNameSource = radioSource.slice(memberNameStart, memberNameEnd) + + expect(memberNameSource).toContain('get_effective_display_name(source)') + expect(memberNameSource).toContain( + 'Bridge.Framework.GetCharacterName(source)', + ) + expect(memberNameSource.indexOf('GetCharacterName(source)')).toBeLessThan( + memberNameSource.indexOf('GetPlayerName(source)'), + ) + }) +}) diff --git a/sky_phone/source/bridge/server/frameworks/esx.lua b/sky_phone/source/bridge/server/frameworks/esx.lua index 1c4a820..794a9d8 100644 --- a/sky_phone/source/bridge/server/frameworks/esx.lua +++ b/sky_phone/source/bridge/server/frameworks/esx.lua @@ -89,6 +89,35 @@ function Bridge.Framework.GetLastname(source) return player and player.get("lastName") or nil end +function Bridge.Framework.GetCharacterName(source) + local player = get_player(source) + if not player then + return nil, nil + end + + local first_name = player.get and player.get("firstName") or nil + local last_name = player.get and player.get("lastName") or nil + if first_name and first_name ~= "" and last_name and last_name ~= "" then + return first_name, last_name + end + + local identifier = player.identifier + if not identifier then + return first_name, last_name + end + + local rows = Bridge.Database.Query( + "SELECT `firstname`, `lastname` FROM `users` WHERE `identifier` = ? LIMIT 1", + { identifier } + ) + local identity = rows[1] + if not identity then + return first_name, last_name + end + + return identity.firstname or first_name, identity.lastname or last_name +end + function Bridge.Framework.GetBirthdate(source) local player = get_player(source) return player and (player.get("dateofbirth") or player.get("dob")) or nil diff --git a/sky_phone/source/bridge/server/frameworks/qb.lua b/sky_phone/source/bridge/server/frameworks/qb.lua index f6608c4..d98e300 100644 --- a/sky_phone/source/bridge/server/frameworks/qb.lua +++ b/sky_phone/source/bridge/server/frameworks/qb.lua @@ -55,6 +55,14 @@ local function get_character_info(source) return player and player.PlayerData and player.PlayerData.charinfo or nil end +function Bridge.Framework.GetCharacterName(source) + local character = get_character_info(source) + if not character then + return nil, nil + end + return character.firstname, character.lastname +end + function Bridge.Framework.GetFirstname(source) local character = get_character_info(source) return character and character.firstname or nil diff --git a/sky_phone/source/bridge/server/frameworks/qbox.lua b/sky_phone/source/bridge/server/frameworks/qbox.lua index f580ea3..f9816b8 100644 --- a/sky_phone/source/bridge/server/frameworks/qbox.lua +++ b/sky_phone/source/bridge/server/frameworks/qbox.lua @@ -44,6 +44,14 @@ local function get_character_info(source) return player and player.PlayerData and player.PlayerData.charinfo or nil end +function Bridge.Framework.GetCharacterName(source) + local character = get_character_info(source) + if not character then + return nil, nil + end + return character.firstname, character.lastname +end + function Bridge.Framework.GetFirstname(source) local character = get_character_info(source) return character and character.firstname or nil diff --git a/sky_phone/source/server/radio.lua b/sky_phone/source/server/radio.lua index 09f6b32..64d5fd9 100644 --- a/sky_phone/source/server/radio.lua +++ b/sky_phone/source/server/radio.lua @@ -176,7 +176,14 @@ end local function get_radio_member_name(source) local display_name = get_effective_display_name(source) - return display_name ~= "" and display_name or GetPlayerName(source) or "Unknown" + if display_name ~= "" then + return display_name + end + + local first_name, last_name = Bridge.Framework.GetCharacterName(source) + local character_name = ("%s %s"):format(first_name or "", last_name or "") + character_name = character_name:gsub("%c", ""):gsub("%s+", " "):match("^%s*(.-)%s*$") or "" + return character_name ~= "" and character_name or GetPlayerName(source) or "Unknown" end local function frequency_set(channel)