FM.player = {} function string.split(str, delimiter) local result = {} local from = 1 local delim_from, delim_to = string.find(str, delimiter, from) while delim_from do table.insert(result, string.sub(str, from, delim_from - 1)) from = delim_to + 1 delim_from, delim_to = string.find(str, delimiter, from) end table.insert(result, string.sub(str, from)) return result end local function isNewQBInv() local version = GetResourceMetadata(Resources.QBInv or 'qb-inventory', 'version', 0) if not version then return false end local vNums = {} for num in version:gmatch("(%d+)") do vNums[#vNums + 1] = tonumber(num) end return vNums and vNums[1] >= 2 end local function getPlayerBySrc(src) if not src then return end local _fwp = ESX and ESX.GetPlayerFromId(src) or QB and QB.Functions.GetPlayer(src) or nil if not _fwp or not type(_fwp) == 'table' then return end _fwp.source = QB and _fwp.PlayerData.source or _fwp.source return _fwp end local function getPlayerByIdentifier(identifier) if not identifier then return end local _fwp = ESX and ESX.GetPlayerFromIdentifier(identifier) or QB and QB.Functions.GetPlayerByCitizenId(identifier) or nil if not _fwp or not type(_fwp) == 'table' then return end _fwp.source = QB and _fwp.PlayerData.source or _fwp.source return _fwp end ---@param id number|string function FM.player.get(id) local _fwp = type(id) == 'number' and getPlayerBySrc(id) or type(id) == 'string' and getPlayerByIdentifier(id) or nil if not _fwp or type(_fwp) ~= 'table' then return end local p = { src = _fwp.source } ---@param item string ---@param amount number ---@param metadata? any ---@param ignoreCheck? boolean p.addItem = function(item, amount, metadata, ignoreCheck) if not item or not amount then return false end if not ignoreCheck and not p.canAddItem(item, amount) then return false end if OXInv then OXInv:AddItem(_fwp.source, item, amount, metadata) return true elseif ESX then if CHEZZAInv and string.find(item:lower(), 'weapon') then _fwp.addWeapon(item, 0) else _fwp.addInventoryItem(item, amount) end return true elseif QB then return _fwp.Functions.AddItem(item, amount, nil, metadata) end end ---@param amount number ---@param moneyType? string ---@param transactionData? { type?: 'deposit' | 'withdraw' | 'transfer' | 'interest' | 'payment', reason?: string, fromIban?: string } p.addMoney = function(amount, moneyType, transactionData) moneyType = moneyType or Defaults.MONEY if not amount then return end if transactionData and moneyType == 'bank' then if GetResourceState(Resources.RX_BANKING.name) == 'started' then local personalAcc = exports[Resources.RX_BANKING.name]:GetPlayerPersonalAccount(p.getIdentifier()) if personalAcc then exports[Resources.RX_BANKING.name]:CreateTransaction(amount, transactionData.type, transactionData.fromIban, personalAcc.iban, transactionData.reason) end end end if ESX then _fwp.addAccountMoney(moneyType, amount) elseif QB then _fwp.Functions.AddMoney(moneyType, amount) end end p.canAddItem = function(item, amount) if not item or not amount then return false end if OXInv then return OXInv:CanCarryItem(_fwp.source, item, amount) elseif QBInv and isNewQBInv() then return QBInv:CanAddItem(_fwp.source, item, amount) elseif QSInv then return QSInv:CanCarryItem(_fwp.source, item, amount) elseif ESX then return _fwp.canCarryItem(item, amount) elseif QB then return true end end ---@param moneyType? string ---@return number | nil amount p.getMoney = function(moneyType) moneyType = moneyType or Defaults.MONEY if ESX then local acc = _fwp.getAccount(moneyType) if not acc then FM.console.err('Money Type not found: '..moneyType) return 0 end return acc.money elseif QB then local money = _fwp.PlayerData.money[moneyType] if money == nil then FM.console.err('Money Type not found: '..moneyType) return 0 end return money end end ---@return string identifier p.getIdentifier = function() if ESX then return _fwp.getIdentifier() elseif QB then return _fwp.PlayerData.citizenid end end ---@return { name: string, label: string, grade: number, gradeLabel: string } job p.getJob = function() if ESX then return { name = _fwp.job.name, label = _fwp.job.label, grade = _fwp.job.grade, gradeLabel = _fwp.job.grade_label } elseif QB then return { name = _fwp.PlayerData.job.name, label = _fwp.PlayerData.job.label, grade = _fwp.PlayerData.job.grade.level, gradeLabel = _fwp.PlayerData.job.grade.name } end end ---@return { name: string, label: string, grade: number, gradeLabel: string } | nil gang p.getGang = function() if ESX then local job = p.getJob() if not job then return end return { name = job.name, label = job.label, grade = job.grade, gradeLabel = job.gradeLabel } elseif QB then return { name = _fwp.PlayerData.gang.name, label = _fwp.PlayerData.gang.label, grade = _fwp.PlayerData.gang.grade.level, gradeLabel = _fwp.PlayerData.gang.grade.name } end end ---@param item string ---@return { name: string, label: string, amount: number } item p.getItem = function(item) if not item then return end if OXInv then item = OXInv:GetItem(_fwp.source, item, nil, false) if not item then return end return { name = item.name, label = item.label, amount = item.count } elseif ESX then if CHEZZAInv and string.find(item:lower(), 'weapon') then local loadoutNum, weapon = _fwp.getWeapon(item) if weapon then item = weapon item.count = 1 -- CHEZZAInv compatibility fix end else item = _fwp.getInventoryItem(item) end if not item then return end return { name = item.name, label = item.label, amount = item.count } elseif QB then item = _fwp.Functions.GetItemByName(item) if not item then return end return { name = item.name, label = item.label, amount = item.amount } end end ---@return { [slot]: { name: string, amount: number, label: string, metadata?: any } } inventory p.getItems = function() local inventory = {} if OXInv then local items = OXInv:GetInventory(_fwp.source).items for slot, item in pairs(items) do inventory[slot] = { name = item.name, label = item.label, amount = item.count, metadata = item.metadata, } end elseif QSInv then local items = QSInv:GetInventory(_fwp.source) for itemName, itemData in pairs(items) do inventory[itemData.slot] = { name = itemName, label = itemData.label, amount = itemData.count, metadata = itemData.info, } end elseif COREInv then local items = COREInv:getInventory() for _, item in pairs(items) do inventory[item.slot] = { name = item.name, label = item.label, amount = item.amount, metadata = item.metadata, } end elseif ESX then local items = _fwp.getInventory() for slot, item in pairs(items) do inventory[slot] = { name = item.name, label = item.label, amount = item.count } end elseif QB then local items = _fwp.PlayerData.items for slot, item in pairs(items) do if item.amount == nil then item.amount = item.count end -- Simple QBox compatibility fix inventory[slot] = { name = item.name, label = item.label, amount = item.amount, metadata = item.info, } end end return inventory end ---@return string firstName p.getFirstName = function() if ESX then return string.split(_fwp.getName(), ' ')[1] elseif QB then return _fwp.PlayerData.charinfo.firstname end end ---@return string lastName p.getLastName = function() if ESX then return string.split(_fwp.getName(), ' ')[2] elseif QB then return _fwp.PlayerData.charinfo.lastname end end ---@return string fullName p.getFullName = function() if ESX then return _fwp.getName() elseif QB then return _fwp.PlayerData.charinfo.firstname .. ' ' .. _fwp.PlayerData.charinfo.lastname end end ---@param item string ---@param amount number ---@return boolean p.hasItemAmount = function(item, amount) if not item then return end item = p.getItem(item) return item and item.amount >= amount or false end ---@return boolean p.isAdmin = function() if ESX then if _fwp.getGroup() == Defaults.ADMIN_ESX then return true end elseif QB then if QB.Functions.HasPermission(_fwp.source, Defaults.ADMIN_QB) or QB.Functions.HasPermission(_fwp.source, Defaults.GOD_QB) then return true end end return IsPlayerAceAllowed(_fwp.source, 'command') -- Want custom admin group? Uncomment below and add the group in server.cfg -- IN SERVER.CFG: add_ace group.admin fmLib.admin allow -- return IsPlayerAceAllowed(_fwp.source, 'fmLib.admin') end ---@return string | table group p.getGroup = function() if ESX then return _fwp.getGroup() elseif QB then return QB.Functions.GetPermission(_fwp.source) end end ---@param message string ---@param type? 'success'|'error' p.notify = function(message, type) if not message then return end if ESX then TriggerClientEvent('esx:showNotification', _fwp.source, message, type) elseif QB then TriggerClientEvent('QBCore:Notify', _fwp.source, message, type) end end ---@param item string ---@param amount number ---@param slotId? number ---@param metadata? any p.removeItem = function(item, amount, slotId, metadata) if not item or not amount then return end if OXInv then OXInv:RemoveItem(_fwp.source, item, amount, metadata, slotId) elseif ESX then _fwp.removeInventoryItem(item, amount) elseif QB then _fwp.Functions.RemoveItem(item, amount) end end ---@param amount number ---@param moneyType? string ---@param transactionData? { type?: 'deposit' | 'withdraw' | 'transfer' | 'interest' | 'payment', reason?: string, toIban?: string } p.removeMoney = function(amount, moneyType, transactionData) moneyType = moneyType or Defaults.MONEY if not amount then return end if transactionData and moneyType == 'bank' then if GetResourceState(Resources.RX_BANKING.name) == 'started' then local personalAcc = exports[Resources.RX_BANKING.name]:GetPlayerPersonalAccount(p.getIdentifier()) if personalAcc then exports[Resources.RX_BANKING.name]:CreateTransaction(amount, transactionData.type, personalAcc.iban, transactionData.toIban, transactionData.reason) end end end if ESX then _fwp.removeAccountMoney(moneyType, amount) elseif QB then _fwp.Functions.RemoveMoney(moneyType, amount) end end return p end --[[ INTERNAL EVENT HANDLERS DO NOT USE --]] FM.callback.register('fm:internal:getGang', function(src) return FM.player.get(src).getGang() end) -- Aliases FM.p = FM.player