if not lib then return end
require 'modules.bridge.client'
require 'modules.interface.client'
local Utils = require 'modules.utils.client'
local Weapon = require 'modules.weapon.client'
local currentWeapon
exports('getCurrentWeapon', function()
return currentWeapon
end)
RegisterNetEvent('ox_inventory:disarm', function(noAnim)
currentWeapon = Weapon.Disarm(currentWeapon, noAnim)
end)
RegisterNetEvent('ox_inventory:clearWeapons', function()
Weapon.ClearAll(currentWeapon)
end)
local StashTarget
exports('setStashTarget', function(id, owner)
StashTarget = id and {id=id, owner=owner}
end)
---@type boolean | number
local invBusy = true
---@type boolean?
local invOpen = false
local plyState = LocalPlayer.state
local IsPedCuffed = IsPedCuffed
local playerPed = cache.ped
lib.onCache('ped', function(ped)
playerPed = ped
Utils.WeaponWheel()
end)
plyState:set('invBusy', true, true)
plyState:set('invHotkeys', false, false)
plyState:set('canUseWeapons', false, false)
local function canOpenInventory()
if not PlayerData.loaded then
return shared.info('cannot open inventory', '(player inventory has not loaded)')
end
if IsPauseMenuActive() then return end
if invBusy or invOpen == nil or (currentWeapon?.timer or 0) > 0 then
return shared.info('cannot open inventory', '(is busy)')
end
if PlayerData.dead or IsPedFatallyInjured(playerPed) then
return shared.info('cannot open inventory', '(fatal injury)')
end
if PlayerData.cuffed or IsPedCuffed(playerPed) then
return shared.info('cannot open inventory', '(cuffed)')
end
return true
end
---@param ped number
---@return boolean
local function canOpenTarget(ped)
return IsPedFatallyInjured(ped)
or IsEntityPlayingAnim(ped, 'dead', 'dead_a', 3)
or IsPedCuffed(ped)
or IsEntityPlayingAnim(ped, 'mp_arresting', 'idle', 3)
or IsEntityPlayingAnim(ped, 'missminuteman_1ig_2', 'handsup_base', 3)
or IsEntityPlayingAnim(ped, 'missminuteman_1ig_2', 'handsup_enter', 3)
or IsEntityPlayingAnim(ped, 'random@mugging3', 'handsup_standing_base', 3)
end
local defaultInventory = {
type = 'newdrop',
slots = shared.playerslots,
weight = 0,
maxWeight = shared.playerweight,
items = {}
}
local currentInventory = defaultInventory
local function closeTrunk()
if currentInventory?.type == 'trunk' then
local coords = GetEntityCoords(playerPed, true)
---@todo animation for vans?
Utils.PlayAnimAdvanced(0, 'anim@heists@fleeca_bank@scope_out@return_case', 'trevor_action', coords.x, coords.y, coords.z, 0.0, 0.0, GetEntityHeading(playerPed), 2.0, 2.0, 1000, 49, 0.25)
CreateThread(function()
local entity = currentInventory.entity
local door = currentInventory.door
Wait(900)
if type(door) == 'table' then
for i = 1, #door do
SetVehicleDoorShut(entity, door[i], false)
end
else
SetVehicleDoorShut(entity, door, false)
end
end)
end
end
local CraftingBenches = require 'modules.crafting.client'
local Vehicles = lib.load('data.vehicles')
local Inventory = require 'modules.inventory.client'
---@param inv string?
---@param data any?
---@return boolean?
function client.openInventory(inv, data)
if invOpen then
if not inv and currentInventory.type == 'newdrop' then
return client.closeInventory()
end
if IsNuiFocused() then
if inv == 'container' and currentInventory.id == PlayerData.inventory[data].metadata.container then
return client.closeInventory()
end
if currentInventory.type == 'drop' and (not data or currentInventory.id == (type(data) == 'table' and data.id or data)) then
return client.closeInventory()
end
if inv ~= 'drop' and inv ~= 'container' then
if (data?.id or data) == currentInventory?.id then
-- Triggering exports.ox_inventory:openInventory('stash', 'mystash') twice in rapid succession is weird behaviour
return warn(("script tried to open inventory, but it is already open\n%s"):format(Citizen.InvokeNative(`FORMAT_STACK_TRACE` & 0xFFFFFFFF, nil, 0, Citizen.ResultAsString())))
else
return client.closeInventory()
end
end
end
elseif IsNuiFocused() then
-- If triggering from another nui, may need to wait for focus to end.
Wait(100)
-- People still complain about this being an "error" and ask "how fix" despite being a warning
-- for people with above room-temperature iqs to look into resource conflicts on their own.
-- if IsNuiFocused() then
-- warn('other scripts have nui focus and may cause issues (e.g. disable focus, prevent input, overlap inventory window)')
-- end
end
if inv == 'dumpster' and cache.vehicle then
return lib.notify({ id = 'inventory_right_access', type = 'error', description = locale('inventory_right_access') })
end
if not canOpenInventory() then
return lib.notify({ id = 'inventory_player_access', type = 'error', description = locale('inventory_player_access') })
end
local left, right, accessError
if inv == 'player' and data ~= cache.serverId then
local targetId, targetPed
if not data then
targetId, targetPed = Utils.GetClosestPlayer()
data = targetId and GetPlayerServerId(targetId)
else
local serverId = type(data) == 'table' and data.id or data
if serverId == cache.serverId then return end
targetId = serverId and GetPlayerFromServerId(serverId)
targetPed = targetId and GetPlayerPed(targetId)
end
local targetCoords = targetPed and GetEntityCoords(targetPed)
if not targetCoords or #(targetCoords - GetEntityCoords(playerPed)) > 1.8 or not (client.hasGroup(shared.police) or canOpenTarget(targetPed)) then
return lib.notify({ id = 'inventory_right_access', type = 'error', description = locale('inventory_right_access') })
end
end
if inv == 'shop' and invOpen == false then
if cache.vehicle then
return lib.notify({ id = 'cannot_perform', type = 'error', description = locale('cannot_perform') })
end
left, right, accessError = lib.callback.await('ox_inventory:openShop', 200, data)
elseif inv == 'crafting' then
if cache.vehicle then
return lib.notify({ id = 'cannot_perform', type = 'error', description = locale('cannot_perform') })
end
left, right, accessError = lib.callback.await('ox_inventory:openCraftingBench', 200, data.id, data.index)
if left then
right = CraftingBenches[data.id]
if not right?.items then return end
local coords, distance
if not right.zones and not right.points then
coords = GetEntityCoords(cache.ped)
distance = 2
else
coords = shared.target and right.zones and right.zones[data.index].coords or right.points and right.points[data.index]
distance = coords and shared.target and right.zones[data.index].distance or 2
end
right = {
type = 'crafting',
id = data.id,
label = right.label or locale('crafting_bench'),
index = data.index,
slots = right.slots,
items = right.items,
coords = coords,
distance = distance
}
end
elseif invOpen ~= nil then
if inv == 'policeevidence' then
if not data then
local input = lib.inputDialog(locale('police_evidence'), {
{ label = locale('locker_number'), type = 'number', required = true, icon = 'calculator' }
}) --[[@as number[]? ]]
if not input then return end
data = input[1]
end
end
left, right, accessError = lib.callback.await('ox_inventory:openInventory', false, inv, data)
end
if accessError then
return lib.notify({ id = accessError, type = 'error', description = locale(accessError) })
end
-- Stash does not exist
if not left then
if left == false then return false end
if invOpen == false then
return lib.notify({ id = 'inventory_right_access', type = 'error', description = locale('inventory_right_access') })
end
if invOpen then return client.closeInventory() end
end
if not cache.vehicle then
if inv == 'player' then
Utils.PlayAnim(0, 'mp_common', 'givetake1_a', 8.0, 1.0, 2000, 50, 0.0, 0, 0, 0)
elseif inv ~= 'trunk' then
Utils.PlayAnim(0, 'pickup_object', 'putdown_low', 5.0, 1.5, 1000, 48, 0.0, 0, 0, 0)
end
end
plyState.invOpen = true
SetInterval(client.interval, 100)
SetNuiFocus(true, true)
SetNuiFocusKeepInput(true)
closeTrunk()
if client.screenblur then TriggerScreenblurFadeIn(0) end
currentInventory = right or defaultInventory
left.items = PlayerData.inventory
left.groups = PlayerData.groups
SendNUIMessage({
action = 'setupInventory',
data = {
leftInventory = left,
rightInventory = currentInventory
}
})
if not currentInventory.coords and not inv == 'container' then
currentInventory.coords = GetEntityCoords(playerPed)
end
if inv == 'trunk' then
SetTimeout(200, function()
---@todo animation for vans?
Utils.PlayAnim(0, 'anim@heists@prison_heiststation@cop_reactions', 'cop_b_idle', 3.0, 3.0, -1, 49, 0.0, 0, 0, 0)
local entity = data.entity or NetworkGetEntityFromNetworkId(data.netid)
currentInventory.entity = entity
currentInventory.door = data.door
if not currentInventory.door then
local vehicleHash = GetEntityModel(entity)
local vehicleClass = GetVehicleClass(entity)
currentInventory.door = vehicleClass == 12 and { 2, 3 } or Vehicles.Storage[vehicleHash] and 4 or 5
end
while currentInventory?.entity == entity and invOpen and DoesEntityExist(entity) and Inventory.CanAccessTrunk(entity) do
Wait(100)
end
if invOpen then client.closeInventory() end
end)
end
return true
end
RegisterNetEvent('ox_inventory:openInventory', client.openInventory)
exports('openInventory', client.openInventory)
RegisterNetEvent('ox_inventory:forceOpenInventory', function(left, right)
if source == '' then return end
plyState.invOpen = true
SetInterval(client.interval, 100)
SetNuiFocus(true, true)
SetNuiFocusKeepInput(true)
closeTrunk()
if client.screenblur then TriggerScreenblurFadeIn(0) end
currentInventory = right or defaultInventory
currentInventory.ignoreSecurityChecks = true
left.items = PlayerData.inventory
left.groups = PlayerData.groups
SendNUIMessage({
action = 'setupInventory',
data = {
leftInventory = left,
rightInventory = currentInventory
}
})
end)
local Animations = lib.load('data.animations')
local Items = require 'modules.items.client'
local usingItem = false
---@param data { name: string, label: string, count: number, slot: number, metadata: table<string, any>, weight: number }
lib.callback.register('ox_inventory:usingItem', function(data, noAnim)
local item = Items[data.name]
if item and usingItem then
if not item.client then return true end
---@cast item +OxClientProps
item = item.client
if type(item.anim) == 'string' then
item.anim = Animations.anim[item.anim]
end
if item.prop then
if item.prop[1] then
for i = 1, #item.prop do
if type(item.prop) == 'string' then
item.prop = Animations.prop[item.prop[i]]
end
end
elseif type(item.prop) == 'string' then
item.prop = Animations.prop[item.prop]
end
end
if not item.disable then
item.disable = { combat = true }
elseif item.disable.combat == nil then
-- Backwards compatibility; you probably don't want people shooting while eating and bandaging anyway
item.disable.combat = true
end
local success = (not item.usetime or noAnim or lib.progressBar({
duration = item.usetime,
label = item.label or locale('using', data.metadata.label or data.label),
useWhileDead = item.useWhileDead,
canCancel = item.cancel,
disable = item.disable,
anim = item.anim or item.scenario,
prop = item.prop --[[@as ProgressProps]]
})) and not PlayerData.dead
if success then
if item.notification then
lib.notify({ description = item.notification })
end
if item.status then
if client.setPlayerStatus then
client.setPlayerStatus(item.status)
end
end
return true
end
end
end)
local function canUseItem(isAmmo)
local ped = cache.ped
return not usingItem
and (not isAmmo or currentWeapon)
and PlayerData.loaded
and not PlayerData.dead
and not invBusy
and not lib.progressActive()
and not IsPedRagdoll(ped)
and not IsPedFalling(ped)
and not IsPedShooting(playerPed)
end
---@param data table
---@param cb fun(response: SlotWithItem | false)?
---@param noAnim? boolean
local function useItem(data, cb, noAnim)
local slotData, result = PlayerData.inventory[data.slot]
if not slotData or not canUseItem(data.ammo and true) then
if currentWeapon then
return lib.notify({ id = 'cannot_perform', type = 'error', description = locale('cannot_perform') })
end
return
end
if currentWeapon and currentWeapon.timer ~= 0 then
if not currentWeapon.timer or currentWeapon.timer - GetGameTimer() > 100 then return end
DisablePlayerFiring(cache.playerId, true)
end
if invOpen and data.close then client.closeInventory() end
usingItem = true
---@type boolean?
result = lib.callback.await('ox_inventory:useItem', 200, data.name, data.slot, slotData.metadata, noAnim)
if result and cb then
local success, response = pcall(cb, result and slotData)
if not success and response then
warn(('^1An error occurred while calling item "%s" callback!\n^1SCRIPT ERROR: %s^0'):format(slotData.name, response))
end
end
if result then
TriggerEvent('ox_inventory:usedItem', slotData.name, slotData.slot, next(slotData.metadata) and slotData.metadata)
end
Wait(500)
usingItem = false
end
AddEventHandler('ox_inventory:usedItem', function(name, slot, metadata)
TriggerServerEvent('ox_inventory:usedItemInternal', slot)
end)
AddEventHandler('ox_inventory:item', useItem)
exports('useItem', useItem)
---@param slot number
---@return boolean?
local function useSlot(slot, noAnim)
local item = PlayerData.inventory[slot]
if not item then return end
local data = Items[item.name]
if not data then return end
if canUseItem(data.ammo and true) then
if data.component and not currentWeapon then
return lib.notify({ id = 'weapon_hand_required', type = 'error', description = locale('weapon_hand_required') })
end
local durability = item.metadata.durability --[[@as number?]]
local consume = data.consume --[[@as number?]]
local label = item.metadata.label or item.label --[[@as string]]
-- Naive durability check to get an early exit
-- People often don't call the 'useItem' export and then complain about "broken" items being usable
-- This won't work with degradation since we need access to os.time on the server
if durability and durability <= 100 and consume then
if durability <= 0 then
return lib.notify({ type = 'error', description = locale('no_durability', label) })
elseif consume ~= 0 and consume < 1 and durability < consume * 100 then
return lib.notify({ type = 'error', description = locale('not_enough_durability', label) })
end
end
data.slot = slot
if item.metadata.container then
return client.openInventory('container', item.slot)
elseif data.client then
if invOpen and data.close then client.closeInventory() end
if data.export then
return data.export(data, {name = item.name, slot = item.slot, metadata = item.metadata})
elseif data.client.event then -- re-add it, so I don't need to deal with morons taking screenshots of errors when using trigger event
return TriggerEvent(data.client.event, data, {name = item.name, slot = item.slot, metadata = item.metadata})
end
end
if data.effect then
data:effect({name = item.name, slot = item.slot, metadata = item.metadata})
elseif data.weapon then
if EnableWeaponWheel or not plyState.canUseWeapons then return end
if IsCinematicCamRendering() then SetCinematicModeActive(false) end
if currentWeapon then
if not currentWeapon.timer or currentWeapon.timer ~= 0 then return end
local weaponSlot = currentWeapon.slot
currentWeapon = Weapon.Disarm(currentWeapon)
if weaponSlot == data.slot then return end
end
GiveWeaponToPed(playerPed, data.hash, 0, false, true)
SetCurrentPedWeapon(playerPed, data.hash, false)
if data.hash ~= GetSelectedPedWeapon(playerPed) then
lib.print.info(('failed to equip %s (cause unknown)'):format(item.name))
return lib.notify({ type = 'error', description = locale('cannot_use', data.label) })
end
RemoveWeaponFromPed(cache.ped, data.hash)
useItem(data, function(result)
if result then
local sleep
currentWeapon, sleep = Weapon.Equip(item, data, noAnim)
if sleep then Wait(sleep) end
end
end, noAnim)
elseif currentWeapon then
if data.ammo then
if EnableWeaponWheel or currentWeapon.metadata.durability <= 0 then return end
local clipSize = GetMaxAmmoInClip(playerPed, currentWeapon.hash, true)
local currentAmmo = GetAmmoInPedWeapon(playerPed, currentWeapon.hash)
local _, maxAmmo = GetMaxAmmo(playerPed, currentWeapon.hash)
if maxAmmo < clipSize then clipSize = maxAmmo end
if currentAmmo == clipSize then return end
useItem(data, function(resp)
if not resp or resp.name ~= currentWeapon?.ammo then return end
if currentWeapon.metadata.specialAmmo ~= resp.metadata.type and type(currentWeapon.metadata.specialAmmo) == 'string' then
local clipComponentKey = ('%s_CLIP'):format(Items[currentWeapon.name].model:gsub('WEAPON_', 'COMPONENT_'))
local specialClip = ('%s_%s'):format(clipComponentKey, (resp.metadata.type or currentWeapon.metadata.specialAmmo):upper())
if type(resp.metadata.type) == 'string' then
if not HasPedGotWeaponComponent(playerPed, currentWeapon.hash, specialClip) then
if not DoesWeaponTakeWeaponComponent(currentWeapon.hash, specialClip) then
warn('cannot use clip with this weapon')
return
end
local defaultClip = ('%s_01'):format(clipComponentKey)
if not HasPedGotWeaponComponent(playerPed, currentWeapon.hash, defaultClip) then
warn('cannot use clip with currently equipped clip')
return
end
if currentAmmo > 0 then
warn('cannot mix special ammo with base ammo')
return
end
currentWeapon.metadata.specialAmmo = resp.metadata.type
GiveWeaponComponentToPed(playerPed, currentWeapon.hash, specialClip)
end
elseif HasPedGotWeaponComponent(playerPed, currentWeapon.hash, specialClip) then
if currentAmmo > 0 then
warn('cannot mix special ammo with base ammo')
return
end
currentWeapon.metadata.specialAmmo = nil
RemoveWeaponComponentFromPed(playerPed, currentWeapon.hash, specialClip)
end
end
if maxAmmo > clipSize then
clipSize = GetMaxAmmoInClip(playerPed, currentWeapon.hash, true)
end
currentAmmo = GetAmmoInPedWeapon(playerPed, currentWeapon.hash)
local missingAmmo = clipSize - currentAmmo
local addAmmo = resp.count > missingAmmo and missingAmmo or resp.count
local newAmmo = currentAmmo + addAmmo
if newAmmo == currentAmmo then return end
AddAmmoToPed(playerPed, currentWeapon.hash, addAmmo)
if cache.vehicle then
if cache.seat > -1 or IsVehicleStopped(cache.vehicle) then
TaskReloadWeapon(playerPed, true)
else
-- This is a hacky solution for forcing ammo to properly load into the
-- weapon clip while driving; without it, ammo will be added but won't
-- load until the player stops doing anything. i.e. if you keep shooting,
-- the weapon will not reload until the clip empties.
-- And yes - for some reason RefillAmmoInstantly needs to run in a loop.
lib.waitFor(function()
RefillAmmoInstantly(playerPed)
local _, ammo = GetAmmoInClip(playerPed, currentWeapon.hash)
return ammo == newAmmo or nil
end)
end
else
Wait(100)
MakePedReload(playerPed)
SetTimeout(100, function()
while IsPedReloading(playerPed) do
DisableControlAction(0, 22, true)
Wait(0)
end
end)
end
lib.callback.await('ox_inventory:updateWeapon', false, 'load', newAmmo, false, currentWeapon.metadata.specialAmmo)
end)
elseif data.component then
local components = data.client.component
if not components then return end
local componentType = data.type
local weaponComponents = PlayerData.inventory[currentWeapon.slot].metadata.components
-- Checks if the weapon already has the same component type attached
for componentIndex = 1, #weaponComponents do
if componentType == Items[weaponComponents[componentIndex]].type then
return lib.notify({ id = 'component_slot_occupied', type = 'error', description = locale('component_slot_occupied', componentType) })
end
end
for i = 1, #components do
local component = components[i]
if DoesWeaponTakeWeaponComponent(currentWeapon.hash, component) then
if HasPedGotWeaponComponent(playerPed, currentWeapon.hash, component) then
lib.notify({ id = 'component_has', type = 'error', description = locale('component_has', label) })
else
useItem(data, function(data)
if data then
local success = lib.callback.await('ox_inventory:updateWeapon', false, 'component', tostring(data.slot), currentWeapon.slot)
if success then
GiveWeaponComponentToPed(playerPed, currentWeapon.hash, component)
TriggerEvent('ox_inventory:updateWeaponComponent', 'added', component, data.name)
end
end
end)
end
return
end
end
lib.notify({ id = 'component_invalid', type = 'error', description = locale('component_invalid', label) })
elseif data.allowArmed then
useItem(data)
else
return lib.notify({ id = 'cannot_perform', type = 'error', description = locale('cannot_perform') })
end
elseif not data.ammo and not data.component then
useItem(data)
end
end
end
exports('useSlot', useSlot)
---@param id number
---@param slot number
local function useButton(id, slot)
if PlayerData.loaded and not invBusy and not lib.progressActive() then
local item = PlayerData.inventory[slot]
if not item then return end
local data = Items[item.name]
local buttons = data?.buttons
if buttons and buttons[id]?.action then
buttons[id].action(slot)
end
end
end
local function openNearbyInventory() client.openInventory('player') end
exports('openNearbyInventory', openNearbyInventory)
local currentInstance
local playerCoords
local Shops = require 'modules.shops.client'
---@todo remove or replace when the bridge module gets restructured
function OnPlayerData(key, val)
if key ~= 'groups' and key ~= 'ped' and key ~= 'dead' then return end
if key == 'groups' then
Inventory.Stashes()
Inventory.Evidence()
Shops.refreshShops()
elseif key == 'dead' and val then
currentWeapon = Weapon.Disarm(currentWeapon)
client.closeInventory()
end
Utils.WeaponWheel()
end
-- People consistently ignore errors when one of the "modules" failed to load
if not Utils or not Weapon or not Items or not Inventory then return end
local invHotkeys = false
---@type function?
local function registerCommands()
RegisterCommand('steal', openNearbyInventory, false)
local function openGlovebox(vehicle)
if not IsPedInAnyVehicle(playerPed, false) or not NetworkGetEntityIsNetworked(vehicle) then return end
local vehicleHash = GetEntityModel(vehicle)
local vehicleClass = GetVehicleClass(vehicle)
local checkVehicle = Vehicles.Storage[vehicleHash]
-- No storage or no glovebox
if (checkVehicle == 0 or checkVehicle == 2) or (not Vehicles.glovebox[vehicleClass] and not Vehicles.glovebox.models[vehicleHash]) then return end
local isOpen = client.openInventory('glovebox', { netid = NetworkGetNetworkIdFromEntity(vehicle) })
if isOpen then
currentInventory.entity = vehicle
end
end
local primary = lib.addKeybind({
name = 'inv',
description = locale('open_player_inventory'),
defaultKey = client.keys[1],
onPressed = function()
if invOpen then
return client.closeInventory()
end
if cache.vehicle then
return openGlovebox(cache.vehicle)
end
local closest = lib.points.getClosestPoint()
if closest and closest.currentDistance < 1.2 and (not closest.instance or closest.instance == currentInstance) then
if closest.inv == 'crafting' then
return client.openInventory('crafting', { id = closest.id, index = closest.index })
elseif closest.inv ~= 'license' and closest.inv ~= 'policeevidence' then
return client.openInventory(closest.inv or 'drop', { id = closest.invId, type = closest.type })
end
end
return client.openInventory()
end
})
lib.addKeybind({
name = 'inv2',
description = locale('open_secondary_inventory'),
defaultKey = client.keys[2],
onPressed = function(self)
if primary:getCurrentKey() == self:getCurrentKey() then
return warn(("secondary inventory keybind '%s' disabled (keybind cannot match primary inventory keybind)"):format(self:getCurrentKey()))
end
if invOpen then
return client.closeInventory()
end
if invBusy or not canOpenInventory() then
return lib.notify({ id = 'inventory_player_access', type = 'error', description = locale('inventory_player_access') })
end
if StashTarget then
return client.openInventory('stash', StashTarget)
end
if cache.vehicle then
return openGlovebox(cache.vehicle)
end
local entity, entityType = Utils.Raycast(2|16)
if not entity then return end
if not shared.target and entityType == 3 then
local model = GetEntityModel(entity)
if Inventory.Dumpsters[model] then
return Inventory.OpenDumpster(entity)
end
end
if entityType ~= 2 then return end
Inventory.OpenTrunk(entity)
end
})
lib.addKeybind({
name = 'reloadweapon',
description = locale('reload_weapon'),
defaultKey = 'r',
onPressed = function(self)
if not currentWeapon or EnableWeaponWheel or not canUseItem(true) then return end
if currentWeapon.ammo then
if currentWeapon.metadata.durability > 0 then
local slotId = Inventory.GetSlotIdWithItem(currentWeapon.ammo, { type = currentWeapon.metadata.specialAmmo }, false)
if slotId then
useSlot(slotId)
end
else
lib.notify({ id = 'no_durability', type = 'error', description = locale('no_durability', currentWeapon.label) })
end
end
end
})
lib.addKeybind({
name = 'hotbar',
description = locale('disable_hotbar'),
defaultKey = client.keys[3],
onPressed = function()
if EnableWeaponWheel or IsNuiFocused() or lib.progressActive() then return end
SendNUIMessage({ action = 'toggleHotbar' })
end
})
for i = 1, 5 do
lib.addKeybind({
name = ('hotkey%s'):format(i),
description = locale('use_hotbar', i),
defaultKey = tostring(i),
onPressed = function()
if invOpen or EnableWeaponWheel or not invHotkeys or IsNuiFocused() then return end
useSlot(i)
end
})
end
registerCommands = nil
end
function client.closeInventory(server)
-- because somehow people are triggering this when the inventory isn't loaded
-- and they're incapable of debugging, and I can't repro on a fresh install
if not client.interval then return end
if invOpen then
invOpen = nil
SetNuiFocus(false, false)
SetNuiFocusKeepInput(false)
TriggerScreenblurFadeOut(0)
closeTrunk()
SendNUIMessage({ action = 'closeInventory' })
SetInterval(client.interval, 200)
Wait(200)
if invOpen ~= nil then return end
if not server and currentInventory then
TriggerServerEvent('ox_inventory:closeInventory')
end
currentInventory = nil
plyState.invOpen = false
defaultInventory.coords = nil
end
end
RegisterNetEvent('ox_inventory:closeInventory', client.closeInventory)
exports('closeInventory', client.closeInventory)
---@param data updateSlot[]
---@param weight number
local function updateInventory(data, weight)
local changes = {}
---@type table<string, number>
local itemCount = {}
for i = 1, #data do
local v = data[i]
if not v.inventory or v.inventory == cache.serverId then
v.inventory = 'player'
local item = v.item
if currentWeapon?.slot == item?.slot then
if item.metadata then
currentWeapon.metadata = item.metadata
TriggerEvent('ox_inventory:currentWeapon', currentWeapon)
else
currentWeapon = Weapon.Disarm(currentWeapon, true)
end
end
local curItem = PlayerData.inventory[item.slot]
if curItem and curItem.name then
itemCount[curItem.name] = (itemCount[curItem.name] or 0) - curItem.count
end
if item.count then
itemCount[item.name] = (itemCount[item.name] or 0) + item.count
end
changes[item.slot] = item.count and item or false
if not item.count then item.name = nil end
PlayerData.inventory[item.slot] = item.name and item or nil
end
end
SendNUIMessage({ action = 'refreshSlots', data = { items = data, itemCount = itemCount} })
if weight ~= PlayerData.weight then client.setPlayerData('weight', weight) end
for itemName, count in pairs(itemCount) do
local item = Items(itemName)
if item then
item.count += count
TriggerEvent('ox_inventory:itemCount', item.name, item.count)
if count < 0 then
if shared.framework == 'esx' then
TriggerEvent('esx:removeInventoryItem', item.name, item.count)
end
if item.client?.remove then
item.client.remove(item.count)
end
elseif count > 0 then
if shared.framework == 'esx' then
TriggerEvent('esx:addInventoryItem', item.name, item.count)
end
if item.client?.add then
item.client.add(item.count)
end
end
end
end
client.setPlayerData('inventory', PlayerData.inventory)
TriggerEvent('ox_inventory:updateInventory', changes)
end
RegisterNetEvent('ox_inventory:updateSlots', function(items, weights)
if source ~= '' and next(items) then updateInventory(items, weights) end
end)
RegisterNetEvent('ox_inventory:inventoryReturned', function(data)
if source == '' then return end
if currentWeapon then currentWeapon = Weapon.Disarm(currentWeapon) end
lib.notify({ description = locale('items_returned') })
client.closeInventory()
local num, items = 0, {}
for _, slotData in pairs(data[1]) do
num += 1
items[num] = { item = slotData, inventory = cache.serverId }
end
updateInventory(items, data[3])
end)
RegisterNetEvent('ox_inventory:inventoryConfiscated', function(message)
if source == '' then return end
if message then lib.notify({ description = locale('items_confiscated') }) end
if currentWeapon then currentWeapon = Weapon.Disarm(currentWeapon) end
client.closeInventory()
local num, items = 0, {}
for slot in pairs(PlayerData.inventory) do
num += 1
items[num] = { item = { slot = slot }, inventory = cache.serverId }
end
updateInventory(items, 0)
end)
---@param point CPoint
local function nearbyDrop(point)
if not point.instance or point.instance == currentInstance then
---@diagnostic disable-next-line: param-type-mismatch
DrawMarker(2, point.coords.x, point.coords.y, point.coords.z, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.3, 0.2, 0.15, 150, 30, 30, 222, false, false, 0, true, false, false, false)
end
end
---@param point CPoint
local function onEnterDrop(point)
if not point.instance or point.instance == currentInstance and not point.entity then
local model = point.model or client.dropmodel
-- Prevent breaking inventory on invalid point.model instead use default client.dropmodel
if not IsModelValid(model) and not IsModelInCdimage(model) then
model = client.dropmodel
end
lib.requestModel(model)
local entity = CreateObject(model, point.coords.x, point.coords.y, point.coords.z, false, true, true)
SetModelAsNoLongerNeeded(model)
PlaceObjectOnGroundProperly(entity)
FreezeEntityPosition(entity, true)
SetEntityCollision(entity, false, true)
point.entity = entity
end
end
local function onExitDrop(point)
local entity = point.entity
if entity then
Utils.DeleteEntity(entity)
point.entity = nil
end
end
local function createDrop(dropId, data)
local point = lib.points.new({
coords = data.coords,
distance = 16,
invId = dropId,
instance = data.instance,
model = data.model
})
if point.model or client.dropprops then
point.distance = 30
point.onEnter = onEnterDrop
point.onExit = onExitDrop
else
point.nearby = nearbyDrop
end
client.drops[dropId] = point
end
RegisterNetEvent('ox_inventory:createDrop', function(dropId, data, owner, slot)
if client.drops then
createDrop(dropId, data)
end
if owner == cache.serverId then
if currentWeapon?.slot == slot then
currentWeapon = Weapon.Disarm(currentWeapon)
end
if invOpen and #(GetEntityCoords(playerPed) - data.coords) <= 1 then
if not cache.vehicle then
client.openInventory('drop', dropId)
else
SendNUIMessage({
action = 'setupInventory',
data = { rightInventory = currentInventory }
})
end
end
end
end)
RegisterNetEvent('ox_inventory:removeDrop', function(dropId)
if client.drops then
local point = client.drops[dropId]
if point then
client.drops[dropId] = nil
point:remove()
if point.entity then Utils.DeleteEntity(point.entity) end
end
end
end)
---@type function?
local function setStateBagHandler(stateId)
AddStateBagChangeHandler('invOpen', stateId, function(_, _, value)
invOpen = value
end)
AddStateBagChangeHandler('invBusy', stateId, function(_, _, value)
invBusy = value
end)
AddStateBagChangeHandler('canUseWeapons', stateId, function(_, _, value)
if not value and currentWeapon then
currentWeapon = Weapon.Disarm(currentWeapon)
end
end)
AddStateBagChangeHandler('instance', stateId, function(_, _, value)
currentInstance = value
if client.drops then
-- Iterate over known drops and remove any points in a different instance (ignoring no instance)
for dropId, point in pairs(client.drops) do
if point.instance then
if point.instance ~= value then
if point.entity then
Utils.DeleteEntity(point.entity)
point.entity = nil
end
point:remove()
else
-- Recreate the drop using data from the old point
createDrop(dropId, point)
end
end
end
end
end)
AddStateBagChangeHandler('dead', stateId, function(_, _, value)
Utils.WeaponWheel()
PlayerData.dead = value
end)
AddStateBagChangeHandler('invHotkeys', stateId, function(_, _, value)
invHotkeys = value
end)
setStateBagHandler = nil
end
lib.onCache('seat', function(seat)
if seat then
local hasWeapon = GetCurrentPedVehicleWeapon(cache.ped)
if hasWeapon then
return Utils.WeaponWheel(true)
end
end
Utils.WeaponWheel(false)
end)
lib.onCache('vehicle', function()
if invOpen and (not currentInventory.entity or currentInventory.entity == cache.vehicle) then
return client.closeInventory()
end
end)
RegisterNetEvent('ox_inventory:setPlayerInventory', function(currentDrops, inventory, weight, player)
if source == '' then return end
---@class PlayerData
---@field inventory table<number, SlotWithItem?>
---@field weight number
---@field groups table<string, number>
PlayerData = player
PlayerData.id = cache.playerId
PlayerData.source = cache.serverId
PlayerData.maxWeight = shared.playerweight
setmetatable(PlayerData, {
__index = function(self, key)
if key == 'ped' then
return PlayerPedId()
end
end
})
if setStateBagHandler then setStateBagHandler(('player:%s'):format(cache.serverId)) end
local ItemData = table.create(0, #Items)
for _, v in pairs(Items --[[@as table<string, OxClientItem>]]) do
local buttons = v.buttons and {} or nil
if buttons then
for i = 1, #v.buttons do
buttons[i] = {label = v.buttons[i].label, group = v.buttons[i].group}
end
end
ItemData[v.name] = {
label = v.label,
stack = v.stack,
close = v.close,
count = 0,
description = v.description,
buttons = buttons,
ammoName = v.ammoname,
image = v.client?.image
}
end
for _, data in pairs(inventory) do
local item = Items[data.name]
if item then
item.count += data.count
ItemData[data.name].count += data.count
local add = item.client?.add
if add then
add(item.count)
end
end
end
local phone = Items.phone
if phone and phone.count < 1 then
pcall(function()
return exports.npwd:setPhoneDisabled(true)
end)
end
client.setPlayerData('inventory', inventory)
client.setPlayerData('weight', weight)
currentWeapon = nil
Weapon.ClearAll()
local uiLocales = {}
local locales = lib.getLocales()
for k, v in pairs(locales) do
if k:find('^ui_')then
uiLocales[k] = v
end
end
uiLocales['$'] = locales['$']
uiLocales.ammo_type = locales.ammo_type
client.drops = currentDrops
for dropId, data in pairs(currentDrops) do
createDrop(dropId, data)
end
local hasTextUi
local uiOptions = { icon = 'fa-id-card' }
---@param point CPoint
local function nearbyLicense(point)
---@diagnostic disable-next-line: param-type-mismatch
DrawMarker(2, point.coords.x, point.coords.y, point.coords.z, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.3, 0.2, 0.15, 30, 150, 30, 222, false, false, 0, true, false, false, false)
if point.isClosest and point.currentDistance < 1.2 then
if not hasTextUi then
hasTextUi = point
lib.showTextUI(point.message, uiOptions)
end
if IsControlJustReleased(0, 38) then
lib.callback('ox_inventory:buyLicense', 1000, function(success, message)
if success ~= nil then
lib.notify({
id = message,
type = success == false and 'error' or 'success',
description = locale(message, locale('license', point.type:gsub("^%l", string.upper)))
})
end
end, point.invId)
end
elseif hasTextUi == point then
hasTextUi = false
lib.hideTextUI()
end
end
for id, data in pairs(lib.load('data.licenses') or {}) do
lib.points.new({
coords = data.coords,
distance = 16,
inv = 'license',
type = data.name,
price = data.price,
invId = id,
nearby = nearbyLicense,
message = ('**%s** \n%s'):format(locale('purchase_license', data.name), locale('interact_prompt', GetControlInstructionalButton(0, 38, true):sub(3)))
})
end
while not client.uiLoaded do Wait(50) end
SendNUIMessage({
action = 'init',
data = {
locale = uiLocales,
items = ItemData,
leftInventory = {
id = cache.playerId,
slots = shared.playerslots,
items = PlayerData.inventory,
maxWeight = shared.playerweight,
},
imagepath = client.imagepath
}
})
PlayerData.loaded = true
lib.notify({ description = locale('inventory_setup') })
Shops.refreshShops()
Inventory.Stashes()
Inventory.Evidence()
if registerCommands then registerCommands() end
TriggerEvent('ox_inventory:updateInventory', PlayerData.inventory)
client.interval = SetInterval(function()
if invOpen == false then
playerCoords = GetEntityCoords(playerPed)
if currentWeapon and IsPedUsingActionMode(playerPed) then
SetPedUsingActionMode(playerPed, false, -1, 'DEFAULT_ACTION')
end
elseif invOpen == true then
if not canOpenInventory() then
client.closeInventory()
else
playerCoords = GetEntityCoords(playerPed)
if currentInventory and not currentInventory.ignoreSecurityChecks then
local maxDistance = (currentInventory.distance or currentInventory.type == 'stash' and 4.8 or 1.8) + 0.2
if currentInventory.type == 'otherplayer' then
local id = GetPlayerFromServerId(currentInventory.id)
local ped = GetPlayerPed(id)
local pedCoords = GetEntityCoords(ped)
if not id or #(playerCoords - pedCoords) > maxDistance or not (client.hasGroup(shared.police) or canOpenTarget(ped)) then
client.closeInventory()
lib.notify({ id = 'inventory_lost_access', type = 'error', description = locale('inventory_lost_access') })
else
TaskTurnPedToFaceCoord(playerPed, pedCoords.x, pedCoords.y, pedCoords.z, 50)
end
elseif currentInventory.coords and (#(playerCoords - currentInventory.coords) > maxDistance or canOpenTarget(playerPed)) then
client.closeInventory()
lib.notify({ id = 'inventory_lost_access', type = 'error', description = locale('inventory_lost_access') })
end
end
end
end
if client.parachute and GetPedParachuteState(playerPed) ~= -1 then
Utils.DeleteEntity(client.parachute[1])
client.parachute = false
end
if EnableWeaponWheel then return end
local weaponHash = GetSelectedPedWeapon(playerPed)
if currentWeapon then
if weaponHash ~= currentWeapon.hash and currentWeapon.timer then
local weaponCount = Items[currentWeapon.name]?.count
if weaponCount > 0 then
SetCurrentPedWeapon(playerPed, currentWeapon.hash, true)
SetAmmoInClip(playerPed, currentWeapon.hash, currentWeapon.metadata.ammo)
SetPedCurrentWeaponVisible(playerPed, true, false, false, false)
weaponHash = GetSelectedPedWeapon(playerPed)
end
if weaponHash ~= currentWeapon.hash then
lib.print.info(('%s was forcibly unequipped (caused by game behaviour or another resource)'):format(currentWeapon.name))
currentWeapon = Weapon.Disarm(currentWeapon, true)
end
end
elseif client.weaponmismatch and not client.ignoreweapons[weaponHash] then
local weaponType = GetWeapontypeGroup(weaponHash)
if weaponType ~= 0 and weaponType ~= `GROUP_UNARMED` then
Weapon.Disarm(currentWeapon, true)
end
end
end, 200)
local playerId = cache.playerId
local EnableKeys = client.enablekeys
local DisablePlayerVehicleRewards = DisablePlayerVehicleRewards
local DisableAllControlActions = DisableAllControlActions
local HideHudAndRadarThisFrame = HideHudAndRadarThisFrame
local EnableControlAction = EnableControlAction
local DisablePlayerFiring = DisablePlayerFiring
local HudWeaponWheelIgnoreSelection = HudWeaponWheelIgnoreSelection
local DisableControlAction = DisableControlAction
local IsPedShooting = IsPedShooting
local IsControlJustReleased = IsControlJustReleased
client.tick = SetInterval(function()
DisablePlayerVehicleRewards(playerId)
if invOpen then
DisableAllControlActions(0)
HideHudAndRadarThisFrame()
for i = 1, #EnableKeys do
EnableControlAction(0, EnableKeys[i], true)
end
if currentInventory.type == 'newdrop' then
EnableControlAction(0, 30, true)
EnableControlAction(0, 31, true)
end
else
if invBusy then
DisableControlAction(0, 23, true)
DisableControlAction(0, 36, true)
end
if usingItem or invOpen or IsPedCuffed(playerPed) then
DisablePlayerFiring(playerId, true)
end
if not EnableWeaponWheel then
HudWeaponWheelIgnoreSelection()
DisableControlAction(0, 37, true)
end
if currentWeapon and currentWeapon.timer then
DisableControlAction(0, 80, true)
DisableControlAction(0, 140, true)
if currentWeapon.metadata.durability <= 0 or not currentWeapon.timer then
DisablePlayerFiring(playerId, true)
elseif client.aimedfiring and not currentWeapon.melee and currentWeapon.group ~= `GROUP_PETROLCAN` and not IsPlayerFreeAiming(playerId) then
DisablePlayerFiring(playerId, true)
end
local weaponAmmo = currentWeapon.metadata.ammo
if not invBusy and currentWeapon.timer ~= 0 and currentWeapon.timer < GetGameTimer() then
currentWeapon.timer = 0
if weaponAmmo then
TriggerServerEvent('ox_inventory:updateWeapon', 'ammo', weaponAmmo)
if client.autoreload and currentWeapon.ammo and GetAmmoInPedWeapon(playerPed, currentWeapon.hash) == 0 then
local slotId = Inventory.GetSlotIdWithItem(currentWeapon.ammo, { type = currentWeapon.metadata.specialAmmo }, false)
if slotId then
CreateThread(function() useSlot(slotId) end)
end
end
elseif currentWeapon.metadata.durability then
TriggerServerEvent('ox_inventory:updateWeapon', 'melee', currentWeapon.melee)
currentWeapon.melee = 0
end
elseif weaponAmmo then
if IsPedShooting(playerPed) then
local currentAmmo
local durabilityDrain = Items[currentWeapon.name].durability
if currentWeapon.group == `GROUP_PETROLCAN` or currentWeapon.group == `GROUP_FIREEXTINGUISHER` then
currentAmmo = weaponAmmo - durabilityDrain < 0 and 0 or weaponAmmo - durabilityDrain
currentWeapon.metadata.durability = currentAmmo
currentWeapon.metadata.ammo = (weaponAmmo < currentAmmo) and 0 or currentAmmo
if currentAmmo <= 0 then
SetPedInfiniteAmmo(playerPed, false, currentWeapon.hash)
end
else
currentAmmo = GetAmmoInPedWeapon(playerPed, currentWeapon.hash)
if currentAmmo < weaponAmmo then
currentAmmo = (weaponAmmo < currentAmmo) and 0 or currentAmmo
currentWeapon.metadata.ammo = currentAmmo
currentWeapon.metadata.durability = currentWeapon.metadata.durability - (durabilityDrain * math.abs((weaponAmmo or 0.1) - currentAmmo))
end
end
if currentAmmo <= 0 then
if cache.vehicle then
TaskSwapWeapon(playerPed, true)
end
currentWeapon.timer = GetGameTimer() + 200
else currentWeapon.timer = GetGameTimer() + (GetWeaponTimeBetweenShots(currentWeapon.hash) * 1000) + 100 end
end
elseif currentWeapon.throwable then
if not invBusy and IsControlPressed(0, 24) then
invBusy = 1
CreateThread(function()
local weapon = currentWeapon
while currentWeapon and (not IsPedWeaponReadyToShoot(cache.ped) or IsDisabledControlPressed(0, 24)) and GetSelectedPedWeapon(playerPed) == weapon.hash do
Wait(0)
end
if GetSelectedPedWeapon(playerPed) == weapon.hash then Wait(700) end
while IsPedPlantingBomb(playerPed) do Wait(0) end
TriggerServerEvent('ox_inventory:updateWeapon', 'throw', nil, weapon.slot)
plyState:set('invBusy', false, true)
currentWeapon = nil
RemoveWeaponFromPed(playerPed, weapon.hash)
TriggerEvent('ox_inventory:currentWeapon')
end)
end
elseif currentWeapon.melee and IsControlJustReleased(0, 24) and IsPedPerformingMeleeAction(playerPed) then
currentWeapon.melee += 1
currentWeapon.timer = GetGameTimer() + 200
end
end
end
end)
plyState:set('invBusy', false, true)
plyState:set('invOpen', false, false)
plyState:set('invHotkeys', true, false)
plyState:set('canUseWeapons', true, false)
collectgarbage('collect')
end)
AddEventHandler('onResourceStop', function(resourceName)
if shared.resource == resourceName then
client.onLogout()
end
end)
RegisterNetEvent('ox_inventory:viewInventory', function(left, right)
if source == '' then return end
plyState.invOpen = true
SetInterval(client.interval, 100)
SetNuiFocus(true, true)
SetNuiFocusKeepInput(true)
closeTrunk()
if client.screenblur then TriggerScreenblurFadeIn(0) end
currentInventory = right or defaultInventory
currentInventory.ignoreSecurityChecks = true
currentInventory.type = 'inspect'
left.items = PlayerData.inventory
left.groups = PlayerData.groups
SendNUIMessage({
action = 'setupInventory',
data = {
leftInventory = left,
rightInventory = currentInventory
}
})
end)
RegisterNUICallback('uiLoaded', function(_, cb)
client.uiLoaded = true
cb(1)
end)
RegisterNUICallback('getItemData', function(itemName, cb)
cb(Items[itemName])
end)
RegisterNUICallback('removeComponent', function(data, cb)
cb(1)
if not currentWeapon then
return TriggerServerEvent('ox_inventory:updateWeapon', 'component', data)
end
if data.slot ~= currentWeapon.slot then
return lib.notify({ id = 'weapon_hand_wrong', type = 'error', description = locale('weapon_hand_wrong') })
end
local itemSlot = PlayerData.inventory[currentWeapon.slot]
if not itemSlot then return end
for _, component in pairs(Items[data.component].client.component) do
if HasPedGotWeaponComponent(playerPed, currentWeapon.hash, component) then
for k, v in pairs(itemSlot.metadata.components) do
if v == data.component then
local success = lib.callback.await('ox_inventory:updateWeapon', false, 'component', k)
if success then
RemoveWeaponComponentFromPed(playerPed, currentWeapon.hash, component)
TriggerEvent('ox_inventory:updateWeaponComponent', 'removed', component, data.component)
end
break
end
end
end
end
end)
RegisterNUICallback('removeAmmo', function(slot, cb)
cb(1)
local slotData = PlayerData.inventory[slot]
if not slotData or not slotData.metadata.ammo or slotData.metadata.ammo == 0 then return end
local success = lib.callback.await('ox_inventory:removeAmmoFromWeapon', false, slot)
if success and slot == currentWeapon?.slot then
SetPedAmmo(playerPed, currentWeapon.hash, 0)
end
end)
RegisterNUICallback('useItem', function(slot, cb)
useSlot(slot --[[@as number]])
cb(1)
end)
local function giveItemToTarget(serverId, slotId, count)
if type(slotId) ~= 'number' then return TypeError('slotId', 'number', type(slotId)) end
if count and type(count) ~= 'number' then return TypeError('count', 'number', type(count)) end
if slotId == currentWeapon?.slot then
currentWeapon = Weapon.Disarm(currentWeapon)
end
Utils.PlayAnim(0, 'mp_common', 'givetake1_a', 1.0, 1.0, 2000, 50, 0.0, 0, 0, 0)
local notification = lib.callback.await('ox_inventory:giveItem', false, slotId, serverId, count or 0)
if notification then
lib.notify({ type = 'error', description = locale(table.unpack(notification)) })
end
end
exports('giveItemToTarget', giveItemToTarget)
local function isGiveTargetValid(ped, coords)
if cache.vehicle and GetVehiclePedIsIn(ped, false) == cache.vehicle then
return true
end
local entity = Utils.Raycast(1|2|4|8|16, coords + vec3(0, 0, 0.5), 0.2)
return entity == ped and IsEntityVisible(ped)
end
RegisterNUICallback('giveItem', function(data, cb)
cb(1)
if usingItem then return end
if client.giveplayerlist then
local nearbyPlayers = lib.getNearbyPlayers(GetEntityCoords(playerPed), 3.0)
local nearbyCount = #nearbyPlayers
if nearbyCount == 0 then return end
if nearbyCount == 1 then
local option = nearbyPlayers[1]
if not isGiveTargetValid(option.ped, option.coords) then return end
return giveItemToTarget(GetPlayerServerId(option.id), data.slot, data.count)
end
local giveList, n = {}, 0
for i = 1, #nearbyPlayers do
local option = nearbyPlayers[i]
if isGiveTargetValid(option.ped, option.coords) then
local playerName = GetPlayerName(option.id)
option.id = GetPlayerServerId(option.id)
---@diagnostic disable-next-line: inject-field
option.label = ('[%s] %s'):format(option.id, playerName)
n += 1
giveList[n] = option
end
end
if n == 0 then return end
lib.registerMenu({
id = 'ox_inventory:givePlayerList',
title = 'Give item',
options = giveList,
}, function(selected)
giveItemToTarget(giveList[selected].id, data.slot, data.count)
end)
return lib.showMenu('ox_inventory:givePlayerList')
end
if cache.vehicle then
local seats = GetVehicleMaxNumberOfPassengers(cache.vehicle) - 1
if seats >= 0 then
local passenger = GetPedInVehicleSeat(cache.vehicle, cache.seat - 2 * (cache.seat % 2) + 1)
if passenger ~= 0 and IsEntityVisible(passenger) then
return giveItemToTarget(GetPlayerServerId(NetworkGetPlayerIndexFromPed(passenger)), data.slot, data.count)
end
end
return
end
local entity = Utils.Raycast(1|2|4|8|16, GetOffsetFromEntityInWorldCoords(cache.ped, 0.0, 3.0, 0.5), 0.2)
if entity and IsPedAPlayer(entity) and IsEntityVisible(entity) and #(GetEntityCoords(playerPed, true) - GetEntityCoords(entity, true)) < 3.0 then
return giveItemToTarget(GetPlayerServerId(NetworkGetPlayerIndexFromPed(entity)), data.slot, data.count)
end
end)
RegisterNUICallback('useButton', function(data, cb)
useButton(data.id, data.slot)
cb(1)
end)
RegisterNUICallback('exit', function(_, cb)
client.closeInventory()
cb(1)
end)
lib.callback.register('ox_inventory:startCrafting', function(id, recipe)
recipe = CraftingBenches[id].items[recipe]
return lib.progressCircle({
label = locale('crafting_item', recipe.metadata?.label or Items[recipe.name].label),
duration = recipe.duration or 3000,
canCancel = true,
disable = {
move = true,
combat = true,
},
anim = {
dict = 'anim@amb@clubhouse@tutorial@bkr_tut_ig3@',
clip = 'machinic_loop_mechandplayer',
}
})
end)
local swapActive = false
---Synchronise and validate all item movement between the NUI and server.
RegisterNUICallback('swapItems', function(data, cb)
if swapActive or not invOpen or invBusy or usingItem then return cb(false) end
swapActive = true
if data.toType == 'newdrop' then
if cache.vehicle or IsPedFalling(playerPed) then
swapActive = false
return cb(false)
end
local coords = GetEntityCoords(playerPed)
if IsEntityInWater(playerPed) then
local destination = vec3(coords.x, coords.y, -200)
local handle = StartShapeTestLosProbe(coords.x, coords.y, coords.z, destination.x, destination.y, destination.z, 511, cache.ped, 4)
while true do
Wait(0)
local retval, hit, endCoords = GetShapeTestResult(handle)
if retval ~= 1 then
if not hit then return end
data.coords = vec3(endCoords.x, endCoords.y, endCoords.z + 1.0)
break
end
end
else
data.coords = coords
end
end
if currentInstance then
data.instance = currentInstance
end
if currentWeapon and data.fromType ~= data.toType then
if (data.fromType == 'player' and data.fromSlot == currentWeapon.slot) or (data.toType == 'player' and data.toSlot == currentWeapon.slot) then
currentWeapon = Weapon.Disarm(currentWeapon, true)
end
end
local success, response, weaponSlot = lib.callback.await('ox_inventory:swapItems', false, data)
swapActive = false
cb(success or false)
if success then
if weaponSlot and currentWeapon then
currentWeapon.slot = weaponSlot
end
if response then
updateInventory(response.items, response.weight)
end
elseif response then
if type(response) == 'table' then
SendNUIMessage({ action = 'refreshSlots', data = { items = response } })
else
lib.notify({ type = 'error', description = locale(response) })
end
end
end)
RegisterNUICallback('buyItem', function(data, cb)
---@type boolean, false | { [1]: number, [2]: SlotWithItem, [3]: SlotWithItem | false, [4]: number}, NotifyProps
local response, data, message = lib.callback.await('ox_inventory:buyItem', 100, data)
if data then
updateInventory({
{
item = data[2],
inventory = cache.serverId
}
}, data[4])
if data[3] then
SendNUIMessage({
action = 'refreshSlots',
data = {
items = {
{
item = data[3],
inventory = 'shop'
}
}
}
})
end
end
if message then
lib.notify(message)
end
cb(response)
end)
RegisterNUICallback('craftItem', function(data, cb)
cb(true)
local id, index = currentInventory.id, currentInventory.index
for i = 1, data.count do
local success, response = lib.callback.await('ox_inventory:craftItem', 200, id, index, data.fromSlot, data.toSlot)
if not success then
if response then lib.notify({ type = 'error', description = locale(response or 'cannot_perform') }) end
break
end
end
if not currentInventory or currentInventory.type ~= 'crafting' then
client.openInventory('crafting', { id = id, index = index })
end
end)
lib.callback.register('ox_inventory:getVehicleData', function(netid)
local entity = NetworkGetEntityFromNetworkId(netid)
if entity then
return GetEntityModel(entity), GetVehicleClass(entity)
end
end)