2025-02-02 10:40:42 +01:00
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
2025-03-17 18:03:20 +00:00
lib.print . info ( ( ' failed to equip %s (cause unknown) ' ) : format ( item.name ) )
2025-02-02 10:40:42 +01:00
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
2025-03-17 18:03:20 +00:00
local isOpen = client.openInventory ( ' glovebox ' , { netid = NetworkGetNetworkIdFromEntity ( vehicle ) } )
2025-02-02 10:40:42 +01:00
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 )
2025-03-17 18:03:20 +00:00
if Inventory.Dumpsters : includes ( model ) then
2025-02-02 10:40:42 +01:00
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
2025-03-17 18:03:20 +00:00
---@field groups table<string, number>
2025-02-02 10:40:42 +01:00
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
2025-03-17 18:03:20 +00:00
for id , data in pairs ( lib.load ( ' data.licenses ' ) or { } ) do
2025-02-02 10:40:42 +01:00
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
2025-03-17 18:03:20 +00:00
lib.print . info ( ( ' %s was forcibly unequipped (caused by game behaviour or another resource) ' ) : format ( currentWeapon.name ) )
2025-02-02 10:40:42 +01:00
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 )