├── .gitignore ├── web ├── src │ ├── vite-env.d.ts │ ├── store │ │ ├── locale.ts │ │ ├── imagepath.ts │ │ ├── items.ts │ │ ├── index.ts │ │ ├── contextMenu.ts │ │ ├── tooltip.ts │ │ └── inventory.ts │ ├── typings │ │ ├── index.ts │ │ ├── item.ts │ │ ├── dnd.ts │ │ ├── inventory.ts │ │ ├── state.ts │ │ └── slot.ts │ ├── components │ │ ├── utils │ │ │ ├── Divider.tsx │ │ │ ├── KeyPress.tsx │ │ │ ├── transitions │ │ │ │ ├── Fade.tsx │ │ │ │ └── SlideUp.tsx │ │ │ ├── icons │ │ │ │ └── ClockIcon.tsx │ │ │ ├── Tooltip.tsx │ │ │ ├── WeightBar.tsx │ │ │ ├── DragPreview.tsx │ │ │ └── ItemNotifications.tsx │ │ └── inventory │ │ │ ├── LeftInventory.tsx │ │ │ ├── RightInventory.tsx │ │ │ ├── index.tsx │ │ │ ├── InventoryGrid.tsx │ │ │ ├── InventoryControl.tsx │ │ │ ├── InventoryHotbar.tsx │ │ │ └── UsefulControls.tsx │ ├── utils │ │ ├── misc.ts │ │ ├── setClipboard.ts │ │ ├── debugData.ts │ │ └── fetchNui.ts │ ├── dnd │ │ ├── onUse.ts │ │ ├── onGive.ts │ │ ├── onCraft.ts │ │ ├── onBuy.ts │ │ └── onDrop.ts │ ├── reducers │ │ ├── index.ts │ │ ├── swapSlots.ts │ │ ├── stackSlots.ts │ │ ├── moveSlots.ts │ │ ├── setupInventory.ts │ │ └── refreshSlots.ts │ ├── hooks │ │ ├── useDebounce.ts │ │ ├── useKeyPress.ts │ │ ├── useQueue.ts │ │ ├── useIntersection.ts │ │ ├── useExitListener.ts │ │ └── useNuiEvent.ts │ ├── thunks │ │ ├── craftItem.ts │ │ ├── buyItem.ts │ │ └── validateItems.ts │ ├── main.tsx │ └── App.tsx ├── images │ ├── key.png │ ├── ammo-9.png │ ├── armour.png │ ├── burger.png │ ├── carkey.png │ ├── donut.png │ ├── fries.png │ ├── meth.png │ ├── money.png │ ├── oldkey.png │ ├── phone.png │ ├── radio.png │ ├── sprunk.png │ ├── trash.png │ ├── water.png │ ├── weed.png │ ├── ziptie.png │ ├── ammo-22.png │ ├── ammo-38.png │ ├── ammo-44.png │ ├── ammo-45.png │ ├── ammo-50.png │ ├── ammo-emp.png │ ├── at_grip.png │ ├── bandage.png │ ├── card_id.png │ ├── cocaine.png │ ├── garbage.png │ ├── lockpick.png │ ├── medikit.png │ ├── mustard.png │ ├── panties.png │ ├── paperbag.png │ ├── WEAPON_BALL.png │ ├── WEAPON_BAT.png │ ├── WEAPON_GAS.PNG │ ├── WEAPON_MG.png │ ├── WEAPON_RPG.png │ ├── WEAPON_SMG.png │ ├── advancedkit.png │ ├── ammo-flare.png │ ├── ammo-laser.png │ ├── ammo-musket.png │ ├── ammo-rifle.png │ ├── ammo-rifle2.png │ ├── ammo-rocket.png │ ├── ammo-sniper.png │ ├── at_barrel.png │ ├── at_scope_nv.png │ ├── black_money.png │ ├── card_bank.png │ ├── cigarette.png │ ├── parachute.png │ ├── pizza_ham.png │ ├── scrapmetal.png │ ├── trash_bread.png │ ├── trash_can.png │ ├── trash_chips.png │ ├── usb_black.png │ ├── WEAPON_BOTTLE.png │ ├── WEAPON_BREAD.PNG │ ├── WEAPON_BZGAS.png │ ├── WEAPON_DAGGER.PNG │ ├── WEAPON_FLARE.png │ ├── WEAPON_HAMMER.png │ ├── WEAPON_KNIFE.png │ ├── WEAPON_MUSKET.png │ ├── WEAPON_PISTOL.png │ ├── WEAPON_WRENCH.png │ ├── ammo-beanbag.png │ ├── ammo-firework.PNG │ ├── ammo-grenade.png │ ├── ammo-railgun.png │ ├── ammo-shotgun.png │ ├── at_clip_drum.png │ ├── at_flashlight.png │ ├── at_muzzle_fat.png │ ├── at_scope_holo.png │ ├── at_suppressor.png │ ├── pizza_ham_box.png │ ├── trash_burger.png │ ├── WEAPON_APPISTOL.png │ ├── WEAPON_BATTLEAXE.png │ ├── WEAPON_BRIEFCASE.PNG │ ├── WEAPON_CANDYCANE.png │ ├── WEAPON_COMBATMG.png │ ├── WEAPON_COMBATPDW.png │ ├── WEAPON_CROWBAR.png │ ├── WEAPON_DBSHOTGUN.png │ ├── WEAPON_FIREWORK.png │ ├── WEAPON_FLAREGUN.png │ ├── WEAPON_GOLFCLUB.png │ ├── WEAPON_GRENADE.png │ ├── WEAPON_GUSENBERG.png │ ├── WEAPON_HANDCUFFS.PNG │ ├── WEAPON_HATCHET.png │ ├── WEAPON_HAZARDCAN.png │ ├── WEAPON_KNUCKLE.png │ ├── WEAPON_MACHETE.png │ ├── WEAPON_MICROSMG.png │ ├── WEAPON_MINIGUN.png │ ├── WEAPON_MINISMG.png │ ├── WEAPON_MOLOTOV.png │ ├── WEAPON_PETROLCAN.png │ ├── WEAPON_PIPEBOMB.png │ ├── WEAPON_PISTOL50.png │ ├── WEAPON_PISTOLXM3.png │ ├── WEAPON_POOLCUE.png │ ├── WEAPON_PROXMINE.png │ ├── WEAPON_RAILGUN.png │ ├── WEAPON_RAYPISTOL.png │ ├── WEAPON_REVOLVER.png │ ├── WEAPON_SMG_MK2.png │ ├── WEAPON_SNOWBALL.png │ ├── WEAPON_SNSPISTOL.png │ ├── WEAPON_STUNGUN.png │ ├── WEAPON_TECPISTOL.png │ ├── ammo-heavysniper.png │ ├── at_clip_extended.png │ ├── at_muzzle_bell.png │ ├── at_muzzle_flat.png │ ├── at_muzzle_heavy.png │ ├── at_muzzle_split.png │ ├── at_scope_large.png │ ├── at_scope_medium.png │ ├── at_scope_small.png │ ├── at_scope_thermal.png │ ├── burger_chicken.png │ ├── pizza_ham_slice.png │ ├── WEAPON_ACIDPACKAGE.PNG │ ├── WEAPON_ASSAULTSMG.png │ ├── WEAPON_AUTOSHOTGUN.png │ ├── WEAPON_DIGISCANNER.png │ ├── WEAPON_EMPLAUNCHER.png │ ├── WEAPON_FLASHLIGHT.png │ ├── WEAPON_GARBAGEBAG.PNG │ ├── WEAPON_HEAVYPISTOL.png │ ├── WEAPON_HEAVYRIFLE.png │ ├── WEAPON_HEAVYSNIPER.png │ ├── WEAPON_NIGHTSTICK.png │ ├── WEAPON_PISTOL_MK2.png │ ├── WEAPON_PUMPSHOTGUN.png │ ├── WEAPON_RAILGUNXM3.png │ ├── WEAPON_RAYCARBINE.png │ ├── WEAPON_RAYMINIGUN.png │ ├── WEAPON_SNIPERRIFLE.png │ ├── WEAPON_STICKYBOMB.png │ ├── WEAPON_SWITCHBLADE.png │ ├── at_clip_extended2.png │ ├── at_muzzle_slanted.png │ ├── at_muzzle_squared.png │ ├── at_muzzle_tactical.png │ ├── at_scope_advanced.png │ ├── cigarettes_redwood.png │ ├── WEAPON_ADVANCEDRIFLE.png │ ├── WEAPON_ASSAULTRIFLE.png │ ├── WEAPON_ASSAULTSHOTGUN.png │ ├── WEAPON_BRIEFCASE_02.PNG │ ├── WEAPON_BULLPUPRIFLE.png │ ├── WEAPON_BULLPUPSHOTGUN.png │ ├── WEAPON_CARBINERIFLE.png │ ├── WEAPON_CERAMICPISTOL.png │ ├── WEAPON_COMBATMG_MK2.png │ ├── WEAPON_COMBATPISTOL.png │ ├── WEAPON_COMBATSHOTGUN.png │ ├── WEAPON_COMPACTRIFLE.png │ ├── WEAPON_DOUBLEACTION.png │ ├── WEAPON_FERTILIZERCAN.png │ ├── WEAPON_GADGETPISTOL.png │ ├── WEAPON_HEAVYSHOTGUN.png │ ├── WEAPON_HOMINGLAUNCHER.png │ ├── WEAPON_MACHINEPISTOL.png │ ├── WEAPON_MARKSMANPISTOL.png │ ├── WEAPON_MARKSMANRIFLE.png │ ├── WEAPON_METALDETECTOR.png │ ├── WEAPON_MILITARYRIFLE.png │ ├── WEAPON_NAVYREVOLVER.png │ ├── WEAPON_PRECISIONRIFLE.png │ ├── WEAPON_REVOLVER_MK2.png │ ├── WEAPON_SAWNOFFSHOTGUN.png │ ├── WEAPON_SMOKEGRENADE.png │ ├── WEAPON_SNSPISTOL_MK2.png │ ├── WEAPON_SPECIALCARBINE.png │ ├── WEAPON_STONE_HATCHET.png │ ├── WEAPON_VINTAGEPISTOL.png │ ├── at_muzzle_precision'.png │ ├── WEAPON_ASSAULTRIFLE_MK2.png │ ├── WEAPON_BULLPUPRIFLE_MK2.png │ ├── WEAPON_CARBINERIFLE_MK2.png │ ├── WEAPON_COMPACTLAUNCHER.png │ ├── WEAPON_FIREEXTINGUISHER.png │ ├── WEAPON_GRENADELAUNCHER.png │ ├── WEAPON_HEAVYSNIPER_MK2.png │ ├── WEAPON_PUMPSHOTGUN_MK2.png │ ├── WEAPON_MARKSMANRIFLE_MK2.png │ ├── WEAPON_SPECIALCARBINE_MK2.png │ ├── WEAPON_GRENADELAUNCHER_SMOKE.PNG │ └── readme.md ├── tsconfig.node.json ├── .prettierrc ├── .gitignore ├── vite.config.ts ├── index.html ├── tsconfig.json ├── LICENSE └── package.json ├── .github ├── FUNDING.yml ├── actions │ └── bump-manifest-version.js ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── codeql-analysis.yml │ └── release.yml ├── .vscode └── extensions.json ├── data ├── licenses.lua ├── animations.lua ├── evidence.lua ├── crafting.lua ├── stashes.lua └── vehicles.lua ├── .editorconfig ├── modules ├── interface │ └── client.lua ├── bridge │ ├── qbx │ │ └── client.lua │ ├── ox │ │ ├── client.lua │ │ └── server.lua │ ├── nd │ │ └── client.lua │ ├── esx │ │ └── client.lua │ ├── server.lua │ └── client.lua ├── utils │ └── server.lua ├── pefcl │ └── server.lua ├── items │ ├── containers.lua │ └── shared.lua ├── crafting │ └── client.lua └── hooks │ └── server.lua ├── fxmanifest.lua ├── CONTRIBUTING.md ├── locales ├── zh-cn.json ├── zh-tw.json ├── ko.json ├── ar.json ├── sv.json ├── hr.json ├── sl.json ├── ru.json ├── lt.json ├── pt-br.json └── sr.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | modules/logs/**/ 2 | .cfg 3 | node_modules 4 | .idea -------------------------------------------------------------------------------- /web/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /web/src/store/locale.ts: -------------------------------------------------------------------------------- 1 | export const Locale: { 2 | [key: string]: string; 3 | } = {}; 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | ko_fi: thelindat 4 | -------------------------------------------------------------------------------- /web/images/key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/key.png -------------------------------------------------------------------------------- /web/images/ammo-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/ammo-9.png -------------------------------------------------------------------------------- /web/images/armour.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/armour.png -------------------------------------------------------------------------------- /web/images/burger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/burger.png -------------------------------------------------------------------------------- /web/images/carkey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/carkey.png -------------------------------------------------------------------------------- /web/images/donut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/donut.png -------------------------------------------------------------------------------- /web/images/fries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/fries.png -------------------------------------------------------------------------------- /web/images/meth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/meth.png -------------------------------------------------------------------------------- /web/images/money.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/money.png -------------------------------------------------------------------------------- /web/images/oldkey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/oldkey.png -------------------------------------------------------------------------------- /web/images/phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/phone.png -------------------------------------------------------------------------------- /web/images/radio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/radio.png -------------------------------------------------------------------------------- /web/images/sprunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/sprunk.png -------------------------------------------------------------------------------- /web/images/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/trash.png -------------------------------------------------------------------------------- /web/images/water.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/water.png -------------------------------------------------------------------------------- /web/images/weed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/weed.png -------------------------------------------------------------------------------- /web/images/ziptie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/ziptie.png -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["overextended.cfxlua-vscode", "sumneko.lua"] 3 | } 4 | -------------------------------------------------------------------------------- /web/images/ammo-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/ammo-22.png -------------------------------------------------------------------------------- /web/images/ammo-38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/ammo-38.png -------------------------------------------------------------------------------- /web/images/ammo-44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/ammo-44.png -------------------------------------------------------------------------------- /web/images/ammo-45.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/ammo-45.png -------------------------------------------------------------------------------- /web/images/ammo-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/ammo-50.png -------------------------------------------------------------------------------- /web/images/ammo-emp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/ammo-emp.png -------------------------------------------------------------------------------- /web/images/at_grip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/at_grip.png -------------------------------------------------------------------------------- /web/images/bandage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/bandage.png -------------------------------------------------------------------------------- /web/images/card_id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/card_id.png -------------------------------------------------------------------------------- /web/images/cocaine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/cocaine.png -------------------------------------------------------------------------------- /web/images/garbage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/garbage.png -------------------------------------------------------------------------------- /web/images/lockpick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/lockpick.png -------------------------------------------------------------------------------- /web/images/medikit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/medikit.png -------------------------------------------------------------------------------- /web/images/mustard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/mustard.png -------------------------------------------------------------------------------- /web/images/panties.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/panties.png -------------------------------------------------------------------------------- /web/images/paperbag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/paperbag.png -------------------------------------------------------------------------------- /web/images/WEAPON_BALL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_BALL.png -------------------------------------------------------------------------------- /web/images/WEAPON_BAT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_BAT.png -------------------------------------------------------------------------------- /web/images/WEAPON_GAS.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_GAS.PNG -------------------------------------------------------------------------------- /web/images/WEAPON_MG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_MG.png -------------------------------------------------------------------------------- /web/images/WEAPON_RPG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_RPG.png -------------------------------------------------------------------------------- /web/images/WEAPON_SMG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_SMG.png -------------------------------------------------------------------------------- /web/images/advancedkit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/advancedkit.png -------------------------------------------------------------------------------- /web/images/ammo-flare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/ammo-flare.png -------------------------------------------------------------------------------- /web/images/ammo-laser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/ammo-laser.png -------------------------------------------------------------------------------- /web/images/ammo-musket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/ammo-musket.png -------------------------------------------------------------------------------- /web/images/ammo-rifle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/ammo-rifle.png -------------------------------------------------------------------------------- /web/images/ammo-rifle2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/ammo-rifle2.png -------------------------------------------------------------------------------- /web/images/ammo-rocket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/ammo-rocket.png -------------------------------------------------------------------------------- /web/images/ammo-sniper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/ammo-sniper.png -------------------------------------------------------------------------------- /web/images/at_barrel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/at_barrel.png -------------------------------------------------------------------------------- /web/images/at_scope_nv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/at_scope_nv.png -------------------------------------------------------------------------------- /web/images/black_money.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/black_money.png -------------------------------------------------------------------------------- /web/images/card_bank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/card_bank.png -------------------------------------------------------------------------------- /web/images/cigarette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/cigarette.png -------------------------------------------------------------------------------- /web/images/parachute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/parachute.png -------------------------------------------------------------------------------- /web/images/pizza_ham.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/pizza_ham.png -------------------------------------------------------------------------------- /web/images/scrapmetal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/scrapmetal.png -------------------------------------------------------------------------------- /web/images/trash_bread.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/trash_bread.png -------------------------------------------------------------------------------- /web/images/trash_can.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/trash_can.png -------------------------------------------------------------------------------- /web/images/trash_chips.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/trash_chips.png -------------------------------------------------------------------------------- /web/images/usb_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/usb_black.png -------------------------------------------------------------------------------- /data/licenses.lua: -------------------------------------------------------------------------------- 1 | return { 2 | { name = 'weapon', coords = vec3(12.42198, -1105.82, 29.7854), price = 5000 } 3 | } -------------------------------------------------------------------------------- /web/images/WEAPON_BOTTLE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_BOTTLE.png -------------------------------------------------------------------------------- /web/images/WEAPON_BREAD.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_BREAD.PNG -------------------------------------------------------------------------------- /web/images/WEAPON_BZGAS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_BZGAS.png -------------------------------------------------------------------------------- /web/images/WEAPON_DAGGER.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_DAGGER.PNG -------------------------------------------------------------------------------- /web/images/WEAPON_FLARE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_FLARE.png -------------------------------------------------------------------------------- /web/images/WEAPON_HAMMER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_HAMMER.png -------------------------------------------------------------------------------- /web/images/WEAPON_KNIFE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_KNIFE.png -------------------------------------------------------------------------------- /web/images/WEAPON_MUSKET.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_MUSKET.png -------------------------------------------------------------------------------- /web/images/WEAPON_PISTOL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_PISTOL.png -------------------------------------------------------------------------------- /web/images/WEAPON_WRENCH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_WRENCH.png -------------------------------------------------------------------------------- /web/images/ammo-beanbag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/ammo-beanbag.png -------------------------------------------------------------------------------- /web/images/ammo-firework.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/ammo-firework.PNG -------------------------------------------------------------------------------- /web/images/ammo-grenade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/ammo-grenade.png -------------------------------------------------------------------------------- /web/images/ammo-railgun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/ammo-railgun.png -------------------------------------------------------------------------------- /web/images/ammo-shotgun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/ammo-shotgun.png -------------------------------------------------------------------------------- /web/images/at_clip_drum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/at_clip_drum.png -------------------------------------------------------------------------------- /web/images/at_flashlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/at_flashlight.png -------------------------------------------------------------------------------- /web/images/at_muzzle_fat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/at_muzzle_fat.png -------------------------------------------------------------------------------- /web/images/at_scope_holo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/at_scope_holo.png -------------------------------------------------------------------------------- /web/images/at_suppressor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/at_suppressor.png -------------------------------------------------------------------------------- /web/images/pizza_ham_box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/pizza_ham_box.png -------------------------------------------------------------------------------- /web/images/trash_burger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/trash_burger.png -------------------------------------------------------------------------------- /web/images/WEAPON_APPISTOL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_APPISTOL.png -------------------------------------------------------------------------------- /web/images/WEAPON_BATTLEAXE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_BATTLEAXE.png -------------------------------------------------------------------------------- /web/images/WEAPON_BRIEFCASE.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_BRIEFCASE.PNG -------------------------------------------------------------------------------- /web/images/WEAPON_CANDYCANE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_CANDYCANE.png -------------------------------------------------------------------------------- /web/images/WEAPON_COMBATMG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_COMBATMG.png -------------------------------------------------------------------------------- /web/images/WEAPON_COMBATPDW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_COMBATPDW.png -------------------------------------------------------------------------------- /web/images/WEAPON_CROWBAR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_CROWBAR.png -------------------------------------------------------------------------------- /web/images/WEAPON_DBSHOTGUN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_DBSHOTGUN.png -------------------------------------------------------------------------------- /web/images/WEAPON_FIREWORK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_FIREWORK.png -------------------------------------------------------------------------------- /web/images/WEAPON_FLAREGUN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_FLAREGUN.png -------------------------------------------------------------------------------- /web/images/WEAPON_GOLFCLUB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_GOLFCLUB.png -------------------------------------------------------------------------------- /web/images/WEAPON_GRENADE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_GRENADE.png -------------------------------------------------------------------------------- /web/images/WEAPON_GUSENBERG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_GUSENBERG.png -------------------------------------------------------------------------------- /web/images/WEAPON_HANDCUFFS.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_HANDCUFFS.PNG -------------------------------------------------------------------------------- /web/images/WEAPON_HATCHET.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_HATCHET.png -------------------------------------------------------------------------------- /web/images/WEAPON_HAZARDCAN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_HAZARDCAN.png -------------------------------------------------------------------------------- /web/images/WEAPON_KNUCKLE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_KNUCKLE.png -------------------------------------------------------------------------------- /web/images/WEAPON_MACHETE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_MACHETE.png -------------------------------------------------------------------------------- /web/images/WEAPON_MICROSMG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_MICROSMG.png -------------------------------------------------------------------------------- /web/images/WEAPON_MINIGUN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_MINIGUN.png -------------------------------------------------------------------------------- /web/images/WEAPON_MINISMG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_MINISMG.png -------------------------------------------------------------------------------- /web/images/WEAPON_MOLOTOV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_MOLOTOV.png -------------------------------------------------------------------------------- /web/images/WEAPON_PETROLCAN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_PETROLCAN.png -------------------------------------------------------------------------------- /web/images/WEAPON_PIPEBOMB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_PIPEBOMB.png -------------------------------------------------------------------------------- /web/images/WEAPON_PISTOL50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_PISTOL50.png -------------------------------------------------------------------------------- /web/images/WEAPON_PISTOLXM3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_PISTOLXM3.png -------------------------------------------------------------------------------- /web/images/WEAPON_POOLCUE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_POOLCUE.png -------------------------------------------------------------------------------- /web/images/WEAPON_PROXMINE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_PROXMINE.png -------------------------------------------------------------------------------- /web/images/WEAPON_RAILGUN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_RAILGUN.png -------------------------------------------------------------------------------- /web/images/WEAPON_RAYPISTOL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_RAYPISTOL.png -------------------------------------------------------------------------------- /web/images/WEAPON_REVOLVER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_REVOLVER.png -------------------------------------------------------------------------------- /web/images/WEAPON_SMG_MK2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_SMG_MK2.png -------------------------------------------------------------------------------- /web/images/WEAPON_SNOWBALL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_SNOWBALL.png -------------------------------------------------------------------------------- /web/images/WEAPON_SNSPISTOL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_SNSPISTOL.png -------------------------------------------------------------------------------- /web/images/WEAPON_STUNGUN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_STUNGUN.png -------------------------------------------------------------------------------- /web/images/WEAPON_TECPISTOL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_TECPISTOL.png -------------------------------------------------------------------------------- /web/images/ammo-heavysniper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/ammo-heavysniper.png -------------------------------------------------------------------------------- /web/images/at_clip_extended.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/at_clip_extended.png -------------------------------------------------------------------------------- /web/images/at_muzzle_bell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/at_muzzle_bell.png -------------------------------------------------------------------------------- /web/images/at_muzzle_flat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/at_muzzle_flat.png -------------------------------------------------------------------------------- /web/images/at_muzzle_heavy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/at_muzzle_heavy.png -------------------------------------------------------------------------------- /web/images/at_muzzle_split.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/at_muzzle_split.png -------------------------------------------------------------------------------- /web/images/at_scope_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/at_scope_large.png -------------------------------------------------------------------------------- /web/images/at_scope_medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/at_scope_medium.png -------------------------------------------------------------------------------- /web/images/at_scope_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/at_scope_small.png -------------------------------------------------------------------------------- /web/images/at_scope_thermal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/at_scope_thermal.png -------------------------------------------------------------------------------- /web/images/burger_chicken.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/burger_chicken.png -------------------------------------------------------------------------------- /web/images/pizza_ham_slice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/pizza_ham_slice.png -------------------------------------------------------------------------------- /web/images/WEAPON_ACIDPACKAGE.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_ACIDPACKAGE.PNG -------------------------------------------------------------------------------- /web/images/WEAPON_ASSAULTSMG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_ASSAULTSMG.png -------------------------------------------------------------------------------- /web/images/WEAPON_AUTOSHOTGUN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_AUTOSHOTGUN.png -------------------------------------------------------------------------------- /web/images/WEAPON_DIGISCANNER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_DIGISCANNER.png -------------------------------------------------------------------------------- /web/images/WEAPON_EMPLAUNCHER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_EMPLAUNCHER.png -------------------------------------------------------------------------------- /web/images/WEAPON_FLASHLIGHT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_FLASHLIGHT.png -------------------------------------------------------------------------------- /web/images/WEAPON_GARBAGEBAG.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_GARBAGEBAG.PNG -------------------------------------------------------------------------------- /web/images/WEAPON_HEAVYPISTOL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_HEAVYPISTOL.png -------------------------------------------------------------------------------- /web/images/WEAPON_HEAVYRIFLE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_HEAVYRIFLE.png -------------------------------------------------------------------------------- /web/images/WEAPON_HEAVYSNIPER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_HEAVYSNIPER.png -------------------------------------------------------------------------------- /web/images/WEAPON_NIGHTSTICK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_NIGHTSTICK.png -------------------------------------------------------------------------------- /web/images/WEAPON_PISTOL_MK2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_PISTOL_MK2.png -------------------------------------------------------------------------------- /web/images/WEAPON_PUMPSHOTGUN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_PUMPSHOTGUN.png -------------------------------------------------------------------------------- /web/images/WEAPON_RAILGUNXM3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_RAILGUNXM3.png -------------------------------------------------------------------------------- /web/images/WEAPON_RAYCARBINE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_RAYCARBINE.png -------------------------------------------------------------------------------- /web/images/WEAPON_RAYMINIGUN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_RAYMINIGUN.png -------------------------------------------------------------------------------- /web/images/WEAPON_SNIPERRIFLE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_SNIPERRIFLE.png -------------------------------------------------------------------------------- /web/images/WEAPON_STICKYBOMB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_STICKYBOMB.png -------------------------------------------------------------------------------- /web/images/WEAPON_SWITCHBLADE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_SWITCHBLADE.png -------------------------------------------------------------------------------- /web/images/at_clip_extended2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/at_clip_extended2.png -------------------------------------------------------------------------------- /web/images/at_muzzle_slanted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/at_muzzle_slanted.png -------------------------------------------------------------------------------- /web/images/at_muzzle_squared.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/at_muzzle_squared.png -------------------------------------------------------------------------------- /web/images/at_muzzle_tactical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/at_muzzle_tactical.png -------------------------------------------------------------------------------- /web/images/at_scope_advanced.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/at_scope_advanced.png -------------------------------------------------------------------------------- /web/images/cigarettes_redwood.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/cigarettes_redwood.png -------------------------------------------------------------------------------- /web/images/WEAPON_ADVANCEDRIFLE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_ADVANCEDRIFLE.png -------------------------------------------------------------------------------- /web/images/WEAPON_ASSAULTRIFLE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_ASSAULTRIFLE.png -------------------------------------------------------------------------------- /web/images/WEAPON_ASSAULTSHOTGUN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_ASSAULTSHOTGUN.png -------------------------------------------------------------------------------- /web/images/WEAPON_BRIEFCASE_02.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_BRIEFCASE_02.PNG -------------------------------------------------------------------------------- /web/images/WEAPON_BULLPUPRIFLE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_BULLPUPRIFLE.png -------------------------------------------------------------------------------- /web/images/WEAPON_BULLPUPSHOTGUN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_BULLPUPSHOTGUN.png -------------------------------------------------------------------------------- /web/images/WEAPON_CARBINERIFLE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_CARBINERIFLE.png -------------------------------------------------------------------------------- /web/images/WEAPON_CERAMICPISTOL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_CERAMICPISTOL.png -------------------------------------------------------------------------------- /web/images/WEAPON_COMBATMG_MK2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_COMBATMG_MK2.png -------------------------------------------------------------------------------- /web/images/WEAPON_COMBATPISTOL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_COMBATPISTOL.png -------------------------------------------------------------------------------- /web/images/WEAPON_COMBATSHOTGUN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_COMBATSHOTGUN.png -------------------------------------------------------------------------------- /web/images/WEAPON_COMPACTRIFLE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_COMPACTRIFLE.png -------------------------------------------------------------------------------- /web/images/WEAPON_DOUBLEACTION.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_DOUBLEACTION.png -------------------------------------------------------------------------------- /web/images/WEAPON_FERTILIZERCAN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_FERTILIZERCAN.png -------------------------------------------------------------------------------- /web/images/WEAPON_GADGETPISTOL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_GADGETPISTOL.png -------------------------------------------------------------------------------- /web/images/WEAPON_HEAVYSHOTGUN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_HEAVYSHOTGUN.png -------------------------------------------------------------------------------- /web/images/WEAPON_HOMINGLAUNCHER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_HOMINGLAUNCHER.png -------------------------------------------------------------------------------- /web/images/WEAPON_MACHINEPISTOL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_MACHINEPISTOL.png -------------------------------------------------------------------------------- /web/images/WEAPON_MARKSMANPISTOL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_MARKSMANPISTOL.png -------------------------------------------------------------------------------- /web/images/WEAPON_MARKSMANRIFLE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_MARKSMANRIFLE.png -------------------------------------------------------------------------------- /web/images/WEAPON_METALDETECTOR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_METALDETECTOR.png -------------------------------------------------------------------------------- /web/images/WEAPON_MILITARYRIFLE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_MILITARYRIFLE.png -------------------------------------------------------------------------------- /web/images/WEAPON_NAVYREVOLVER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_NAVYREVOLVER.png -------------------------------------------------------------------------------- /web/images/WEAPON_PRECISIONRIFLE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_PRECISIONRIFLE.png -------------------------------------------------------------------------------- /web/images/WEAPON_REVOLVER_MK2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_REVOLVER_MK2.png -------------------------------------------------------------------------------- /web/images/WEAPON_SAWNOFFSHOTGUN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_SAWNOFFSHOTGUN.png -------------------------------------------------------------------------------- /web/images/WEAPON_SMOKEGRENADE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_SMOKEGRENADE.png -------------------------------------------------------------------------------- /web/images/WEAPON_SNSPISTOL_MK2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_SNSPISTOL_MK2.png -------------------------------------------------------------------------------- /web/images/WEAPON_SPECIALCARBINE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_SPECIALCARBINE.png -------------------------------------------------------------------------------- /web/images/WEAPON_STONE_HATCHET.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_STONE_HATCHET.png -------------------------------------------------------------------------------- /web/images/WEAPON_VINTAGEPISTOL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_VINTAGEPISTOL.png -------------------------------------------------------------------------------- /web/images/at_muzzle_precision'.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/at_muzzle_precision'.png -------------------------------------------------------------------------------- /web/images/WEAPON_ASSAULTRIFLE_MK2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_ASSAULTRIFLE_MK2.png -------------------------------------------------------------------------------- /web/images/WEAPON_BULLPUPRIFLE_MK2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_BULLPUPRIFLE_MK2.png -------------------------------------------------------------------------------- /web/images/WEAPON_CARBINERIFLE_MK2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_CARBINERIFLE_MK2.png -------------------------------------------------------------------------------- /web/images/WEAPON_COMPACTLAUNCHER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_COMPACTLAUNCHER.png -------------------------------------------------------------------------------- /web/images/WEAPON_FIREEXTINGUISHER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_FIREEXTINGUISHER.png -------------------------------------------------------------------------------- /web/images/WEAPON_GRENADELAUNCHER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_GRENADELAUNCHER.png -------------------------------------------------------------------------------- /web/images/WEAPON_HEAVYSNIPER_MK2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_HEAVYSNIPER_MK2.png -------------------------------------------------------------------------------- /web/images/WEAPON_PUMPSHOTGUN_MK2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_PUMPSHOTGUN_MK2.png -------------------------------------------------------------------------------- /web/images/WEAPON_MARKSMANRIFLE_MK2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_MARKSMANRIFLE_MK2.png -------------------------------------------------------------------------------- /web/images/WEAPON_SPECIALCARBINE_MK2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_SPECIALCARBINE_MK2.png -------------------------------------------------------------------------------- /web/images/WEAPON_GRENADELAUNCHER_SMOKE.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/overextended/ox_inventory/HEAD/web/images/WEAPON_GRENADELAUNCHER_SMOKE.PNG -------------------------------------------------------------------------------- /web/src/store/imagepath.ts: -------------------------------------------------------------------------------- 1 | export let imagepath = 'images'; 2 | 3 | export function setImagePath(path: string) { 4 | if (path && path !== '') imagepath = path; 5 | } 6 | -------------------------------------------------------------------------------- /web/src/typings/index.ts: -------------------------------------------------------------------------------- 1 | export * from './state'; 2 | export * from './inventory'; 3 | export * from './item'; 4 | export * from './slot'; 5 | export * from './dnd'; 6 | -------------------------------------------------------------------------------- /web/src/components/utils/Divider.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Divider: React.FC = () => { 4 | return
; 5 | }; 6 | 7 | export default Divider; 8 | -------------------------------------------------------------------------------- /web/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | }, 7 | "include": ["vite.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /web/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "singleQuote": true, 4 | "useTabs": false, 5 | "tabWidth": 2, 6 | "semi": true, 7 | "bracketSpacing": true, 8 | "trailingComma": "es5" 9 | } 10 | -------------------------------------------------------------------------------- /web/src/utils/misc.ts: -------------------------------------------------------------------------------- 1 | // Will return whether the current environment is in a regular browser 2 | // and not CEF 3 | export const isEnvBrowser = (): boolean => !(window as any).invokeNative; 4 | 5 | // Basic no operation function 6 | export const noop = () => {}; 7 | -------------------------------------------------------------------------------- /data/animations.lua: -------------------------------------------------------------------------------- 1 | return { 2 | anim = { 3 | ['eating'] = { dict = 'mp_player_inteat@burger', clip = 'mp_player_int_eat_burger_fp' }, 4 | }, 5 | prop = { 6 | ['burger'] = { model = `prop_cs_burger_01`, pos = vec3(0.02, 0.02, -0.02), rot = vec3(0.0, 0.0, 0.0) }, 7 | } 8 | } -------------------------------------------------------------------------------- /web/src/dnd/onUse.ts: -------------------------------------------------------------------------------- 1 | //import toast from "react-hot-toast"; 2 | import { fetchNui } from '../utils/fetchNui'; 3 | import { Slot } from '../typings'; 4 | 5 | export const onUse = (item: Slot) => { 6 | //toast.success(`Use ${item.name}`); 7 | fetchNui('useItem', item.slot); 8 | }; 9 | -------------------------------------------------------------------------------- /web/src/typings/item.ts: -------------------------------------------------------------------------------- 1 | export type ItemData = { 2 | name: string; 3 | label: string; 4 | stack: boolean; 5 | usable: boolean; 6 | close: boolean; 7 | count: number; 8 | description?: string; 9 | buttons?: string[]; 10 | ammoName?: string; 11 | image?: string; 12 | }; 13 | -------------------------------------------------------------------------------- /web/src/reducers/index.ts: -------------------------------------------------------------------------------- 1 | export { setupInventoryReducer } from './setupInventory'; 2 | export { refreshSlotsReducer } from './refreshSlots'; 3 | 4 | export { swapSlotsReducer } from './swapSlots'; 5 | export { stackSlotsReducer } from './stackSlots'; 6 | export { moveSlotsReducer } from './moveSlots'; 7 | -------------------------------------------------------------------------------- /data/evidence.lua: -------------------------------------------------------------------------------- 1 | return { 2 | { 3 | coords = vec3(458.97, -982.79, 30.68), 4 | target = { -- qtarget support 5 | name = 'mrpd_evidence', -- name of zone must be uniuqe 6 | loc = vec3(459.07, -984.07, 30.69), 7 | length = 1.4, 8 | width = 3.2, 9 | heading = 0, 10 | minZ = 29.09, 11 | maxZ = 31.89 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /web/src/dnd/onGive.ts: -------------------------------------------------------------------------------- 1 | import { store } from '../store'; 2 | import { Slot } from '../typings'; 3 | import { fetchNui } from '../utils/fetchNui'; 4 | 5 | export const onGive = (item: Slot) => { 6 | const { 7 | inventory: { itemAmount }, 8 | } = store.getState(); 9 | fetchNui('giveItem', { slot: item.slot, count: itemAmount }); 10 | }; 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | insert_final_newline = true 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | indent_size = 2 12 | indent_style = space 13 | 14 | [*.lua] 15 | indent_size = 4 16 | indent_style = space 17 | -------------------------------------------------------------------------------- /web/src/typings/dnd.ts: -------------------------------------------------------------------------------- 1 | import { Inventory } from './inventory'; 2 | import { Slot, SlotWithItem } from './slot'; 3 | 4 | export type DragSource = { 5 | item: Pick; 6 | inventory: Inventory['type']; 7 | image?: string; 8 | }; 9 | 10 | export type DropTarget = { 11 | item: Pick; 12 | inventory: Inventory['type']; 13 | }; 14 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | .pnpm-debug.log 23 | -------------------------------------------------------------------------------- /web/src/typings/inventory.ts: -------------------------------------------------------------------------------- 1 | import { Slot } from './slot'; 2 | 3 | export enum InventoryType { 4 | PLAYER = 'player', 5 | SHOP = 'shop', 6 | CONTAINER = 'container', 7 | CRAFTING = 'crafting', 8 | } 9 | 10 | export type Inventory = { 11 | id: string; 12 | type: string; 13 | slots: number; 14 | items: Slot[]; 15 | maxWeight?: number; 16 | label?: string; 17 | groups?: Record; 18 | }; 19 | -------------------------------------------------------------------------------- /web/src/components/inventory/LeftInventory.tsx: -------------------------------------------------------------------------------- 1 | import InventoryGrid from './InventoryGrid'; 2 | import { useAppSelector } from '../../store'; 3 | import { selectLeftInventory } from '../../store/inventory'; 4 | 5 | const LeftInventory: React.FC = () => { 6 | const leftInventory = useAppSelector(selectLeftInventory); 7 | 8 | return ; 9 | }; 10 | 11 | export default LeftInventory; 12 | -------------------------------------------------------------------------------- /web/src/components/inventory/RightInventory.tsx: -------------------------------------------------------------------------------- 1 | import InventoryGrid from './InventoryGrid'; 2 | import { useAppSelector } from '../../store'; 3 | import { selectRightInventory } from '../../store/inventory'; 4 | 5 | const RightInventory: React.FC = () => { 6 | const rightInventory = useAppSelector(selectRightInventory); 7 | 8 | return ; 9 | }; 10 | 11 | export default RightInventory; 12 | -------------------------------------------------------------------------------- /.github/actions/bump-manifest-version.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | 3 | const version = process.env.TGT_RELEASE_VERSION; 4 | const newVersion = version.replace("v", ""); 5 | 6 | const manifestFile = fs.readFileSync("fxmanifest.lua", { encoding: "utf8" }); 7 | 8 | const newFileContent = manifestFile.replace( 9 | /\bversion\s+(.*)$/gm, 10 | `version '${newVersion}'` 11 | ); 12 | 13 | fs.writeFileSync("fxmanifest.lua", newFileContent); 14 | -------------------------------------------------------------------------------- /web/src/typings/state.ts: -------------------------------------------------------------------------------- 1 | import { Inventory } from './inventory'; 2 | import { Slot } from './slot'; 3 | 4 | export type State = { 5 | leftInventory: Inventory; 6 | rightInventory: Inventory; 7 | itemAmount: number; 8 | shiftPressed: boolean; 9 | isBusy: boolean; 10 | additionalMetadata: Array<{ metadata: string; value: string }>; 11 | history?: { 12 | leftInventory: Inventory; 13 | rightInventory: Inventory; 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /web/src/utils/setClipboard.ts: -------------------------------------------------------------------------------- 1 | // yoinked from https://github.com/project-error/npwd/blob/d8dc5b7f47faf5fc581ffee30f31ff61d184cfe7/phone/src/os/phone/hooks/useClipboard.ts#L1 2 | export const setClipboard = (value: string) => { 3 | const clipElem = document.createElement('input'); 4 | clipElem.value = value; 5 | document.body.appendChild(clipElem); 6 | clipElem.select(); 7 | document.execCommand('copy'); 8 | document.body.removeChild(clipElem); 9 | }; 10 | -------------------------------------------------------------------------------- /web/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | base: './', 8 | publicDir: false, 9 | build: { 10 | outDir: 'build', 11 | target: 'esnext', 12 | }, 13 | define: { 14 | 'process.env': {}, 15 | }, 16 | esbuild: { 17 | logOverride: { 'this-is-undefined-in-esm': 'silent' }, 18 | }, 19 | }); 20 | -------------------------------------------------------------------------------- /web/src/store/items.ts: -------------------------------------------------------------------------------- 1 | import { ItemData } from '../typings/item'; 2 | 3 | export const Items: { 4 | [key: string]: ItemData | undefined; 5 | } = { 6 | water: { 7 | name: 'water', 8 | close: false, 9 | label: 'VODA', 10 | stack: true, 11 | usable: true, 12 | count: 0, 13 | }, 14 | burger: { 15 | name: 'burger', 16 | close: false, 17 | label: 'BURGR', 18 | stack: false, 19 | usable: false, 20 | count: 0, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /web/src/hooks/useDebounce.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | export function useDebounce(value: T, delay: number = 500): T { 4 | const [debouncedValue, setDebouncedValue] = useState(value); 5 | 6 | useEffect(() => { 7 | const timer = setTimeout(() => { 8 | setDebouncedValue(value); 9 | }, delay); 10 | 11 | return () => { 12 | clearTimeout(timer); 13 | }; 14 | }, [value, delay]); 15 | 16 | return debouncedValue; 17 | } 18 | -------------------------------------------------------------------------------- /modules/interface/client.lua: -------------------------------------------------------------------------------- 1 | if not lib then return end 2 | 3 | -- Module is deprecated and provided for compatibility 4 | -- All functions are now part of with ox_lib 5 | 6 | exports('Keyboard', lib.inputDialog) 7 | 8 | exports('Progress', function(options, completed) 9 | local success = lib.progressBar(options) 10 | 11 | if completed then 12 | completed(not success) 13 | end 14 | end) 15 | 16 | exports('CancelProgress', lib.cancelProgress) 17 | exports('ProgressActive', lib.progressActive) 18 | -------------------------------------------------------------------------------- /web/src/typings/slot.ts: -------------------------------------------------------------------------------- 1 | export type Slot = { 2 | slot: number; 3 | name?: string; 4 | count?: number; 5 | weight?: number; 6 | metadata?: { 7 | [key: string]: any; 8 | }; 9 | durability?: number; 10 | }; 11 | 12 | export type SlotWithItem = Slot & { 13 | name: string; 14 | count: number; 15 | weight: number; 16 | durability?: number; 17 | price?: number; 18 | currency?: string; 19 | ingredients?: { [key: string]: number }; 20 | duration?: number; 21 | image?: string; 22 | grade?: number | number[]; 23 | }; 24 | -------------------------------------------------------------------------------- /web/src/components/utils/KeyPress.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { setShiftPressed } from '../../store/inventory'; 3 | import useKeyPress from '../../hooks/useKeyPress'; 4 | import { useAppDispatch } from '../../store'; 5 | 6 | const KeyPress: React.FC = () => { 7 | const dispatch = useAppDispatch(); 8 | const shiftPressed = useKeyPress('Shift'); 9 | 10 | useEffect(() => { 11 | dispatch(setShiftPressed(shiftPressed)); 12 | }, [shiftPressed, dispatch]); 13 | 14 | return <>; 15 | }; 16 | 17 | export default KeyPress; 18 | -------------------------------------------------------------------------------- /web/src/components/utils/transitions/Fade.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react'; 2 | import { CSSTransition } from 'react-transition-group'; 3 | 4 | interface Props { 5 | in?: boolean; 6 | children: React.ReactNode; 7 | } 8 | 9 | const Fade: React.FC = (props) => { 10 | const nodeRef = useRef(null); 11 | 12 | return ( 13 | 14 | {props.children} 15 | 16 | ); 17 | }; 18 | 19 | export default Fade; 20 | -------------------------------------------------------------------------------- /web/src/components/utils/icons/ClockIcon.tsx: -------------------------------------------------------------------------------- 1 | export const ClockIcon: React.FC = () => { 2 | return ( 3 | 14 | 15 | 16 | 17 | 18 | ); 19 | }; 20 | 21 | export default ClockIcon; 22 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Ox Inventory 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /web/src/thunks/craftItem.ts: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk } from '@reduxjs/toolkit'; 2 | import { fetchNui } from '../utils/fetchNui'; 3 | 4 | export const craftItem = createAsyncThunk( 5 | 'inventory/craftItem', 6 | async ( 7 | data: { fromSlot: number; fromType: string; toSlot: number; toType: string; count: number }, 8 | { rejectWithValue } 9 | ) => { 10 | try { 11 | const response = await fetchNui('craftItem', data); 12 | 13 | if (response === false) { 14 | return rejectWithValue(response); 15 | } 16 | } catch (error) { 17 | return rejectWithValue(false); 18 | } 19 | } 20 | ); 21 | -------------------------------------------------------------------------------- /web/src/components/utils/transitions/SlideUp.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react'; 2 | import { CSSTransition } from 'react-transition-group'; 3 | 4 | interface Props { 5 | in?: boolean; 6 | children: React.ReactElement>; 7 | } 8 | 9 | const SlideUp: React.FC = (props) => { 10 | const nodeRef = useRef(null); 11 | 12 | return ( 13 | 14 | {React.cloneElement(props.children, { ref: nodeRef })} 15 | 16 | ); 17 | }; 18 | 19 | export default SlideUp; 20 | -------------------------------------------------------------------------------- /web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | "allowUnreachableCode": false, 19 | }, 20 | "include": ["src"], 21 | "references": [{ "path": "./tsconfig.node.json" }] 22 | } 23 | -------------------------------------------------------------------------------- /web/src/thunks/buyItem.ts: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk } from '@reduxjs/toolkit'; 2 | import { fetchNui } from '../utils/fetchNui'; 3 | 4 | export const buyItem = createAsyncThunk( 5 | 'inventory/buyItem', 6 | async ( 7 | data: { 8 | fromSlot: number; 9 | fromType: string; 10 | toSlot: number; 11 | toType: string; 12 | count: number; 13 | }, 14 | { rejectWithValue } 15 | ) => { 16 | try { 17 | const response = await fetchNui('buyItem', data); 18 | 19 | if (response === false) { 20 | return rejectWithValue(response); 21 | } 22 | } catch (error) { 23 | return rejectWithValue(false); 24 | } 25 | } 26 | ); 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /data/crafting.lua: -------------------------------------------------------------------------------- 1 | return { 2 | { 3 | name = 'debug_crafting', 4 | items = { 5 | { 6 | name = 'lockpick', 7 | ingredients = { 8 | scrapmetal = 5, 9 | WEAPON_HAMMER = 0.05 10 | }, 11 | duration = 5000, 12 | count = 2, 13 | }, 14 | }, 15 | points = { 16 | vec3(-1147.083008, -2002.662109, 13.180260), 17 | vec3(-345.374969, -130.687088, 39.009613) 18 | }, 19 | zones = { 20 | { 21 | coords = vec3(-1146.2, -2002.05, 13.2), 22 | size = vec3(3.8, 1.05, 0.15), 23 | distance = 1.5, 24 | rotation = 315.0, 25 | }, 26 | { 27 | coords = vec3(-346.1, -130.45, 39.0), 28 | size = vec3(3.8, 1.05, 0.15), 29 | distance = 1.5, 30 | rotation = 70.0, 31 | }, 32 | }, 33 | blip = { id = 566, colour = 31, scale = 0.8 }, 34 | }, 35 | } 36 | -------------------------------------------------------------------------------- /web/src/thunks/validateItems.ts: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk } from '@reduxjs/toolkit'; 2 | import { setContainerWeight } from '../store/inventory'; 3 | import { fetchNui } from '../utils/fetchNui'; 4 | 5 | export const validateMove = createAsyncThunk( 6 | 'inventory/validateMove', 7 | async ( 8 | data: { 9 | fromSlot: number; 10 | fromType: string; 11 | toSlot: number; 12 | toType: string; 13 | count: number; 14 | }, 15 | { rejectWithValue, dispatch } 16 | ) => { 17 | try { 18 | const response = await fetchNui('swapItems', data); 19 | 20 | if (response === false) return rejectWithValue(response); 21 | 22 | if (typeof response === 'number') dispatch(setContainerWeight(response)); 23 | } catch (error) { 24 | return rejectWithValue(false); 25 | } 26 | } 27 | ); 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Framework** 14 | The framework your server uses for players (e.g. Ox, ESX, QB). 15 | 16 | **Resource version** 17 | The version number listed in fxmanifest.lua, or optionally a commit hash. 18 | 19 | **To Reproduce** 20 | Steps to reproduce the behavior: 21 | 1. Go to '...' 22 | 2. Click on '....' 23 | 3. Scroll down to '....' 24 | 4. See error 25 | 26 | **Expected behavior** 27 | A clear and concise description of what you expected to happen. 28 | 29 | **Screenshots** 30 | If applicable, add screenshots to help explain your problem. 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /web/LICENSE: -------------------------------------------------------------------------------- 1 | Ox Inventory 2 | Copyright © 2023 Overextended (https://github.com/overextended) 3 | Linden (https://github.com/thelindat) 4 | Luke (https://github.com/LukeWasTakenn) 5 | Dunak (https://github.com/dunak-debug) 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | -------------------------------------------------------------------------------- /web/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { Action, configureStore, ThunkAction } from '@reduxjs/toolkit'; 2 | import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; 3 | import inventoryReducer from './inventory'; 4 | import tooltipReducer from './tooltip'; 5 | import contextMenuReducer from './contextMenu'; 6 | 7 | export const store = configureStore({ 8 | reducer: { 9 | inventory: inventoryReducer, 10 | tooltip: tooltipReducer, 11 | contextMenu: contextMenuReducer, 12 | }, 13 | }); 14 | 15 | export type AppDispatch = typeof store.dispatch; 16 | export type RootState = ReturnType; 17 | export type AppThunk = ThunkAction>; 18 | 19 | export const useAppDispatch = () => useDispatch(); 20 | export const useAppSelector: TypedUseSelectorHook = useSelector; 21 | -------------------------------------------------------------------------------- /data/stashes.lua: -------------------------------------------------------------------------------- 1 | return { 2 | { 3 | coords = vec3(452.3, -991.4, 30.7), 4 | target = { 5 | loc = vec3(451.25, -994.28, 30.69), 6 | length = 1.2, 7 | width = 5.6, 8 | heading = 0, 9 | minZ = 29.49, 10 | maxZ = 32.09, 11 | label = 'Open personal locker' 12 | }, 13 | name = 'policelocker', 14 | label = 'Personal locker', 15 | owner = true, 16 | slots = 70, 17 | weight = 70000, 18 | groups = shared.police 19 | }, 20 | 21 | { 22 | coords = vec3(301.3, -600.23, 43.28), 23 | target = { 24 | loc = vec3(301.82, -600.99, 43.29), 25 | length = 0.6, 26 | width = 1.8, 27 | heading = 340, 28 | minZ = 43.34, 29 | maxZ = 44.74, 30 | label = 'Open personal locker' 31 | }, 32 | name = 'emslocker', 33 | label = 'Personal Locker', 34 | owner = true, 35 | slots = 70, 36 | weight = 70000, 37 | groups = {['ambulance'] = 0} 38 | }, 39 | } 40 | -------------------------------------------------------------------------------- /web/src/utils/debugData.ts: -------------------------------------------------------------------------------- 1 | import { isEnvBrowser } from './misc'; 2 | 3 | interface DebugEvent { 4 | action: string; 5 | data: T; 6 | } 7 | 8 | /** 9 | * Emulates dispatching an event using SendNUIMessage in the lua scripts. 10 | * This is used when developing in browser 11 | * 12 | * @param events - The event you want to cover 13 | * @param timer - How long until it should trigger (ms) 14 | */ 15 | export const debugData =

(events: DebugEvent

[], timer = 1000): void => { 16 | if (import.meta.env.DEV && isEnvBrowser()) { 17 | for (const event of events) { 18 | setTimeout(() => { 19 | window.dispatchEvent( 20 | new MessageEvent('message', { 21 | data: { 22 | action: event.action, 23 | data: event.data, 24 | }, 25 | }) 26 | ); 27 | }, timer); 28 | } 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /web/src/store/contextMenu.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from '@reduxjs/toolkit'; 2 | import { SlotWithItem } from '../typings'; 3 | 4 | interface ContextMenuState { 5 | coords: { 6 | x: number; 7 | y: number; 8 | } | null; 9 | item: SlotWithItem | null; 10 | } 11 | 12 | const initialState: ContextMenuState = { 13 | coords: null, 14 | item: null, 15 | }; 16 | 17 | export const contextMenuSlice = createSlice({ 18 | name: 'contextMenu', 19 | initialState, 20 | reducers: { 21 | openContextMenu(state, action: PayloadAction<{ item: SlotWithItem; coords: { x: number; y: number } }>) { 22 | state.coords = action.payload.coords; 23 | state.item = action.payload.item; 24 | }, 25 | closeContextMenu(state) { 26 | state.coords = null; 27 | }, 28 | }, 29 | }); 30 | 31 | export const { openContextMenu, closeContextMenu } = contextMenuSlice.actions; 32 | 33 | export default contextMenuSlice.reducer; 34 | -------------------------------------------------------------------------------- /web/src/hooks/useKeyPress.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from 'react'; 2 | 3 | export const useKeyPress = (targetKey: KeyboardEvent['key']) => { 4 | const [keyPressed, setKeyPressed] = useState(false); 5 | 6 | const keyToggler = useCallback( 7 | (toggle: boolean) => 8 | ({ key }: KeyboardEvent) => { 9 | if (key === targetKey) { 10 | setKeyPressed(toggle); 11 | } 12 | }, 13 | [targetKey] 14 | ); 15 | 16 | const downHandler = keyToggler(true); 17 | const upHandler = keyToggler(false); 18 | 19 | useEffect(() => { 20 | window.addEventListener('keydown', downHandler); 21 | window.addEventListener('keyup', upHandler); 22 | 23 | return () => { 24 | window.removeEventListener('keydown', downHandler); 25 | window.removeEventListener('keyup', upHandler); 26 | }; 27 | }, [downHandler, upHandler]); 28 | 29 | return keyPressed; 30 | }; 31 | 32 | export default useKeyPress; 33 | -------------------------------------------------------------------------------- /web/src/hooks/useQueue.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | export interface QueueMethods { 4 | add: (item: T) => void; 5 | remove: () => T | undefined; 6 | first: T; 7 | last: T; 8 | values: T[]; 9 | size: number; 10 | } 11 | 12 | const useQueue = (initialValue: T[] = []): QueueMethods => { 13 | const [state, set] = useState(initialValue); 14 | return { 15 | add: (value) => { 16 | set((queue) => [...queue, value]); 17 | }, 18 | remove: () => { 19 | let removed; 20 | set(([first, ...rest]) => { 21 | removed = first; 22 | return rest; 23 | }); 24 | return removed; 25 | }, 26 | get values() { 27 | return state; 28 | }, 29 | get first() { 30 | return state[0]; 31 | }, 32 | get last() { 33 | return state[state.length - 1]; 34 | }, 35 | get size() { 36 | return state.length; 37 | }, 38 | }; 39 | }; 40 | 41 | export default useQueue; 42 | -------------------------------------------------------------------------------- /web/src/store/tooltip.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from '@reduxjs/toolkit'; 2 | import { Inventory, SlotWithItem } from '../typings'; 3 | 4 | interface TooltipState { 5 | open: boolean; 6 | item: SlotWithItem | null; 7 | inventoryType: Inventory['type'] | null; 8 | } 9 | 10 | const initialState: TooltipState = { 11 | open: false, 12 | item: null, 13 | inventoryType: null, 14 | }; 15 | 16 | export const tooltipSlice = createSlice({ 17 | name: 'tooltip', 18 | initialState, 19 | reducers: { 20 | openTooltip(state, action: PayloadAction<{ item: SlotWithItem; inventoryType: Inventory['type'] }>) { 21 | state.open = true; 22 | state.item = action.payload.item; 23 | state.inventoryType = action.payload.inventoryType; 24 | }, 25 | closeTooltip(state) { 26 | state.open = false; 27 | }, 28 | }, 29 | }); 30 | 31 | export const { openTooltip, closeTooltip } = tooltipSlice.actions; 32 | 33 | export default tooltipSlice.reducer; 34 | -------------------------------------------------------------------------------- /web/images/readme.md: -------------------------------------------------------------------------------- 1 | ## The following images were provided by [yaroph](https://forum.cfx.re/u/yaroph/) for free-use by ox_inventory. 2 | - All files prefixed with _ammo-_, _at\__, and _WEAPON\__. 3 | - advancedkit 4 | - armour 5 | - bandage 6 | - black_money 7 | - burger 8 | - burger_chicken 9 | - card_id 10 | - carkey 11 | - cigarette 12 | - cigarettes_redwood 13 | - cocaine 14 | - donut 15 | - fries 16 | - garbage 17 | - key 18 | - lockpick 19 | - medikit 20 | - meth 21 | - money 22 | - mustard 23 | - oldkey 24 | - panties 25 | - paperbag 26 | - parachute 27 | - phone 28 | - pizza_ham 29 | - pizza_ham_box 30 | - pizza_ham_slice 31 | - radio 32 | - scrapmetal 33 | - sprunk 34 | - trash 35 | - trash_bread 36 | - trash_burger 37 | - trash_can 38 | - trash_chips 39 | - usb_black 40 | - water 41 | - weed 42 | - ziptie 43 | 44 | For more images, see [this thread](https://forum.cfx.re/t/inventory-icons-pack-for-rp-server-hq-draw-24k-cloth-l-1400-objects/5203350) and get a **10%** discount when using creator code `ox10`. 45 | -------------------------------------------------------------------------------- /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'cerulean' 2 | use_experimental_fxv2_oal 'yes' 3 | lua54 'yes' 4 | game 'gta5' 5 | name 'ox_inventory' 6 | author 'Overextended' 7 | version '2.44.1' 8 | repository 'https://github.com/overextended/ox_inventory' 9 | description 'Slot-based inventory with item metadata support' 10 | 11 | dependencies { 12 | '/server:6116', 13 | '/onesync', 14 | 'oxmysql', 15 | 'ox_lib', 16 | } 17 | 18 | shared_script '@ox_lib/init.lua' 19 | 20 | ox_libs { 21 | 'locale', 22 | 'table', 23 | 'math', 24 | } 25 | 26 | server_scripts { 27 | '@oxmysql/lib/MySQL.lua', 28 | 'init.lua' 29 | } 30 | 31 | client_script 'init.lua' 32 | 33 | ui_page 'web/build/index.html' 34 | 35 | files { 36 | 'client.lua', 37 | 'server.lua', 38 | 'locales/*.json', 39 | 'web/build/index.html', 40 | 'web/build/assets/*.js', 41 | 'web/build/assets/*.css', 42 | 'web/images/*.png', 43 | 'modules/**/shared.lua', 44 | 'modules/**/client.lua', 45 | 'modules/bridge/**/client.lua', 46 | 'data/*.lua', 47 | } 48 | -------------------------------------------------------------------------------- /modules/bridge/qbx/client.lua: -------------------------------------------------------------------------------- 1 | AddStateBagChangeHandler('isLoggedIn', ('player:%s'):format(cache.serverId), function(_, _, value) 2 | if not value then client.onLogout() end 3 | end) 4 | 5 | RegisterNetEvent('qbx_core:client:onGroupUpdate', function(groupName, groupGrade) 6 | local groups = PlayerData.groups 7 | if not groupGrade then 8 | groups[groupName] = nil 9 | else 10 | groups[groupName] = groupGrade 11 | end 12 | client.setPlayerData('groups', groups) 13 | end) 14 | 15 | RegisterNetEvent('qbx_core:client:setGroups', function(groups) 16 | client.setPlayerData('groups', groups) 17 | end) 18 | 19 | ---@diagnostic disable-next-line: duplicate-set-field 20 | function client.setPlayerStatus(values) 21 | local playerState = LocalPlayer.state 22 | for name, value in pairs(values) do 23 | -- compatibility for ESX style values 24 | if value > 100 or value < -100 then 25 | value = value * 0.0001 26 | end 27 | 28 | playerState:set(name, playerState[name] + value, true) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /web/src/hooks/useIntersection.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/mantinedev/mantine/blob/master/packages/%40mantine/hooks/src/use-intersection/use-intersection.ts 2 | 3 | import { useCallback, useRef, useState } from 'react'; 4 | 5 | export function useIntersection( 6 | options?: ConstructorParameters[1] 7 | ) { 8 | const [entry, setEntry] = useState(null); 9 | 10 | const observer = useRef(null); 11 | 12 | const ref = useCallback( 13 | (element: T | null) => { 14 | if (observer.current) { 15 | observer.current.disconnect(); 16 | observer.current = null; 17 | } 18 | 19 | if (element === null) { 20 | setEntry(null); 21 | return; 22 | } 23 | 24 | observer.current = new IntersectionObserver(([_entry]) => { 25 | setEntry(_entry); 26 | }, options); 27 | 28 | observer.current.observe(element); 29 | }, 30 | [options?.rootMargin, options?.root, options?.threshold] 31 | ); 32 | 33 | return { ref, entry }; 34 | } 35 | -------------------------------------------------------------------------------- /web/src/reducers/swapSlots.ts: -------------------------------------------------------------------------------- 1 | import { CaseReducer, PayloadAction } from '@reduxjs/toolkit'; 2 | import { getTargetInventory, itemDurability } from '../helpers'; 3 | import { Inventory, SlotWithItem, State } from '../typings'; 4 | 5 | export const swapSlotsReducer: CaseReducer< 6 | State, 7 | PayloadAction<{ 8 | fromSlot: SlotWithItem; 9 | fromType: Inventory['type']; 10 | toSlot: SlotWithItem; 11 | toType: Inventory['type']; 12 | }> 13 | > = (state, action) => { 14 | const { fromSlot, fromType, toSlot, toType } = action.payload; 15 | const { sourceInventory, targetInventory } = getTargetInventory(state, fromType, toType); 16 | const curTime = Math.floor(Date.now() / 1000); 17 | 18 | [sourceInventory.items[fromSlot.slot - 1], targetInventory.items[toSlot.slot - 1]] = [ 19 | { 20 | ...targetInventory.items[toSlot.slot - 1], 21 | slot: fromSlot.slot, 22 | durability: itemDurability(toSlot.metadata, curTime), 23 | }, 24 | { 25 | ...sourceInventory.items[fromSlot.slot - 1], 26 | slot: toSlot.slot, 27 | durability: itemDurability(fromSlot.metadata, curTime), 28 | }, 29 | ]; 30 | }; 31 | -------------------------------------------------------------------------------- /modules/bridge/ox/client.lua: -------------------------------------------------------------------------------- 1 | if not lib.checkDependency('ox_core', '0.21.3', true) then return end 2 | 3 | local Ox = require '@ox_core.lib.init' --[[@as OxClient]] 4 | local player = Ox.GetPlayer() 5 | 6 | RegisterNetEvent('ox:playerLogout', client.onLogout) 7 | 8 | RegisterNetEvent('ox:setGroup', function(name, grade) 9 | PlayerData.groups[name] = grade 10 | OnPlayerData('groups') 11 | end) 12 | 13 | ---@diagnostic disable-next-line: duplicate-set-field 14 | function client.setPlayerStatus(values) 15 | for name, value in pairs(values) do 16 | -- Thanks to having status values setup out of 1000000 (matching esx_status's standard) 17 | -- we need to awkwardly change the value 18 | if value > 100 or value < -100 then 19 | -- Hunger and thirst start at 0 and go up to 100 as you get hungry/thirsty (inverse of ESX) 20 | if (name == 'hunger' or name == 'thirst') then 21 | value = -value 22 | end 23 | 24 | value = value * 0.0001 25 | end 26 | 27 | ---@diagnostic disable-next-line: undefined-global 28 | player.addStatus(name, value) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /web/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import { Provider } from 'react-redux'; 4 | import { DndProvider } from 'react-dnd'; 5 | import { TouchBackend } from 'react-dnd-touch-backend'; 6 | import { store } from './store'; 7 | import App from './App'; 8 | import './index.scss'; 9 | import { ItemNotificationsProvider } from './components/utils/ItemNotifications'; 10 | import { isEnvBrowser } from './utils/misc'; 11 | 12 | const root = document.getElementById('root'); 13 | 14 | if (isEnvBrowser()) { 15 | // https://i.imgur.com/iPTAdYV.png - Night time img 16 | root!.style.backgroundImage = 'url("https://i.imgur.com/3pzRj9n.png")'; 17 | root!.style.backgroundSize = 'cover'; 18 | root!.style.backgroundRepeat = 'no-repeat'; 19 | root!.style.backgroundPosition = 'center'; 20 | } 21 | 22 | createRoot(root!).render( 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | ); 33 | -------------------------------------------------------------------------------- /modules/utils/server.lua: -------------------------------------------------------------------------------- 1 | if not lib then return end 2 | 3 | local Utils = {} 4 | 5 | local webHook = GetConvar('inventory:webhook', '') 6 | 7 | if webHook ~= '' then 8 | local validHosts = { 9 | ['i.imgur.com'] = true, 10 | } 11 | 12 | local validExtensions = { 13 | ['png'] = true, 14 | ['apng'] = true, 15 | ['webp'] = true, 16 | } 17 | 18 | local headers = { ['Content-Type'] = 'application/json' } 19 | 20 | function Utils.IsValidImageUrl(url) 21 | local host, extension = url:match('^https?://([^/]+).+%.([%l]+)') 22 | return host and extension and validHosts[host] and validExtensions[extension] 23 | end 24 | 25 | ---@param title string 26 | ---@param message string 27 | ---@param image string 28 | function Utils.DiscordEmbed(title, message, image, color) 29 | PerformHttpRequest(webHook, function() end, 'POST', json.encode({ 30 | username = 'ox_inventory', embeds = { 31 | { 32 | title = title, 33 | color = color, 34 | footer = { 35 | text = os.date('%c'), 36 | }, 37 | description = message, 38 | thumbnail = { 39 | url = image, 40 | width = 100, 41 | } 42 | } 43 | } 44 | }), headers) 45 | end 46 | end 47 | 48 | return Utils 49 | -------------------------------------------------------------------------------- /web/src/hooks/useExitListener.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | import { noop } from '../utils/misc'; 3 | import { fetchNui } from '../utils/fetchNui'; 4 | import { closeTooltip } from '../store/tooltip'; 5 | import { useAppDispatch } from '../store'; 6 | import { closeContextMenu } from '../store/contextMenu'; 7 | 8 | type FrameVisibleSetter = (bool: boolean) => void; 9 | 10 | const LISTENED_KEYS = ['Escape']; 11 | 12 | // Basic hook to listen for key presses in NUI in order to exit 13 | export const useExitListener = (visibleSetter: FrameVisibleSetter) => { 14 | const setterRef = useRef(noop); 15 | const dispatch = useAppDispatch(); 16 | 17 | useEffect(() => { 18 | setterRef.current = visibleSetter; 19 | }, [visibleSetter]); 20 | 21 | useEffect(() => { 22 | const keyHandler = (e: KeyboardEvent) => { 23 | if (LISTENED_KEYS.includes(e.code)) { 24 | setterRef.current(false); 25 | dispatch(closeTooltip()); 26 | dispatch(closeContextMenu()); 27 | fetchNui('exit'); 28 | } 29 | }; 30 | 31 | window.addEventListener('keyup', keyHandler); 32 | 33 | return () => window.removeEventListener('keyup', keyHandler); 34 | }, []); 35 | }; 36 | -------------------------------------------------------------------------------- /web/src/utils/fetchNui.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple wrapper around fetch API tailored for CEF/NUI use. This abstraction 3 | * can be extended to include AbortController if needed or if the response isn't 4 | * JSON. Tailor it to your needs. 5 | * 6 | * @param eventName - The endpoint eventname to target 7 | * @param data - Data you wish to send in the NUI Callback 8 | * 9 | * @return returnData - A promise for the data sent back by the NuiCallbacks CB argument 10 | */ 11 | 12 | import { isEnvBrowser } from './misc'; 13 | 14 | const resourceName = (window as any).GetParentResourceName ? (window as any).GetParentResourceName() : 'ox_inventory'; 15 | 16 | export async function fetchNui(eventName: string, data?: unknown): Promise { 17 | if (isEnvBrowser()) return undefined as any; // HACK FOR BORING ERRORS IN DEV 18 | 19 | try { 20 | const resp = await fetch(`https://${resourceName}/${eventName}`, { 21 | method: 'post', 22 | headers: { 23 | 'Content-Type': 'application/json; charset=UTF-8', 24 | }, 25 | body: JSON.stringify(data), 26 | }); 27 | 28 | const respFormatted = await resp.json(); 29 | 30 | return respFormatted; 31 | } catch (error) { 32 | throw Error(`Failed to fetch NUI callback ${eventName}! (${error})`); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Found a bug? 2 | - Check if the bug has already been reported under under [Issues](https://github.com/overextended/ox_inventory/issues). 3 | - If an **active** issue matches your own, provide additional information on the existing issue. 4 | - If there is no **open** issue related to the bug, create a new issue. Include a **descriptive title and clear description** with as much relevant information as possible, and include **code samples** or **reproduction steps**. 5 | - Use the relevant bug report template when creating an issue. 6 | 7 | ## Patched a bug? 8 | - Open a new pull request including **only** the related changes. 9 | - Clearly describe the problem being fixed, and the solution. If the patch resolves any issues, mention them in the description. 10 | 11 | ## Want to share an improvement or add a new feature? 12 | - Create an issue discussing the change and wait for feedback. 13 | - If you've already worked on the change you can submit a **draft** pull request for feedback and review. 14 | - Not all features and changes are desired! Changes may be messy, poorly-planned, incomplete, or simply incompatible with our design philosophy. 15 | 16 | ## Is your change cosmetic (e.g. formatting)? 17 | - We will not accept pull requests that do not make substantial changes to the stability or functionality of the resource. 18 | -------------------------------------------------------------------------------- /modules/bridge/nd/client.lua: -------------------------------------------------------------------------------- 1 | if not lib.checkDependency('ND_Core', '2.0.0', true) then return end 2 | 3 | NDCore = {} 4 | 5 | lib.load('@ND_Core.init') 6 | 7 | RegisterNetEvent("ND:characterUnloaded", client.onLogout) 8 | 9 | local function reorderGroups(groups) 10 | groups = groups or {} 11 | for group, info in pairs(groups) do 12 | groups[group] = info.rank 13 | end 14 | return groups 15 | end 16 | 17 | SetTimeout(500, function() 18 | local player = NDCore.getPlayer() 19 | if not player then return end 20 | client.setPlayerData("groups", reorderGroups(player.groups)) 21 | end) 22 | 23 | RegisterNetEvent("ND:characterLoaded", function(character) 24 | client.setPlayerData("groups", reorderGroups(character.groups)) 25 | end) 26 | 27 | RegisterNetEvent("ND:updateCharacter", function(character) 28 | client.setPlayerData("groups", reorderGroups(character.groups)) 29 | end) 30 | 31 | ---@diagnostic disable-next-line: duplicate-set-field 32 | function client.setPlayerStatus(values) 33 | if GetResourceState("ND_Status") ~= "started" then return end 34 | 35 | local status = exports["ND_Status"] 36 | 37 | for name, value in pairs(values) do 38 | 39 | if value > 100 or value < -100 then 40 | value = value * 0.0001 41 | end 42 | 43 | status:changeStatus(name, value) 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /web/src/reducers/stackSlots.ts: -------------------------------------------------------------------------------- 1 | import { CaseReducer, PayloadAction } from '@reduxjs/toolkit'; 2 | import { getTargetInventory } from '../helpers'; 3 | import { Inventory, InventoryType, SlotWithItem, State } from '../typings'; 4 | 5 | export const stackSlotsReducer: CaseReducer< 6 | State, 7 | PayloadAction<{ 8 | fromSlot: SlotWithItem; 9 | fromType: Inventory['type']; 10 | toSlot: SlotWithItem; 11 | toType: Inventory['type']; 12 | count: number; 13 | }> 14 | > = (state, action) => { 15 | const { fromSlot, fromType, toSlot, toType, count } = action.payload; 16 | 17 | const { sourceInventory, targetInventory } = getTargetInventory(state, fromType, toType); 18 | 19 | const pieceWeight = fromSlot.weight / fromSlot.count; 20 | 21 | targetInventory.items[toSlot.slot - 1] = { 22 | ...targetInventory.items[toSlot.slot - 1], 23 | count: toSlot.count + count, 24 | weight: pieceWeight * (toSlot.count + count), 25 | }; 26 | 27 | if (fromType === InventoryType.SHOP || fromType === InventoryType.CRAFTING) return; 28 | 29 | sourceInventory.items[fromSlot.slot - 1] = 30 | fromSlot.count - count > 0 31 | ? { 32 | ...sourceInventory.items[fromSlot.slot - 1], 33 | count: fromSlot.count - count, 34 | weight: pieceWeight * (fromSlot.count - count), 35 | } 36 | : { 37 | slot: fromSlot.slot, 38 | }; 39 | }; 40 | -------------------------------------------------------------------------------- /web/src/dnd/onCraft.ts: -------------------------------------------------------------------------------- 1 | import { store } from '../store'; 2 | import { DragSource, DropTarget } from '../typings'; 3 | import { isSlotWithItem } from '../helpers'; 4 | import { Items } from '../store/items'; 5 | import { craftItem } from '../thunks/craftItem'; 6 | 7 | export const onCraft = (source: DragSource, target: DropTarget) => { 8 | const { inventory: state } = store.getState(); 9 | 10 | const sourceInventory = state.rightInventory; 11 | const targetInventory = state.leftInventory; 12 | 13 | const sourceSlot = sourceInventory.items[source.item.slot - 1]; 14 | 15 | if (!isSlotWithItem(sourceSlot)) throw new Error(`Item ${sourceSlot.slot} name === undefined`); 16 | 17 | if (sourceSlot.count === 0) return; 18 | 19 | const sourceData = Items[sourceSlot.name]; 20 | 21 | if (sourceData === undefined) return console.error(`Item ${sourceSlot.name} data undefined!`); 22 | 23 | const targetSlot = targetInventory.items[target.item.slot - 1]; 24 | 25 | if (targetSlot === undefined) return console.error(`Target slot undefined`); 26 | 27 | const count = state.itemAmount === 0 ? 1 : state.itemAmount; 28 | 29 | const data = { 30 | fromSlot: sourceSlot, 31 | toSlot: targetSlot, 32 | fromType: sourceInventory.type, 33 | toType: targetInventory.type, 34 | count, 35 | }; 36 | 37 | store.dispatch( 38 | craftItem({ 39 | ...data, 40 | fromSlot: sourceSlot.slot, 41 | toSlot: targetSlot.slot, 42 | }) 43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /web/src/reducers/moveSlots.ts: -------------------------------------------------------------------------------- 1 | import { CaseReducer, PayloadAction } from '@reduxjs/toolkit'; 2 | import { getTargetInventory, itemDurability } from '../helpers'; 3 | import { Inventory, InventoryType, Slot, SlotWithItem, State } from '../typings'; 4 | 5 | export const moveSlotsReducer: CaseReducer< 6 | State, 7 | PayloadAction<{ 8 | fromSlot: SlotWithItem; 9 | fromType: Inventory['type']; 10 | toSlot: Slot; 11 | toType: Inventory['type']; 12 | count: number; 13 | }> 14 | > = (state, action) => { 15 | const { fromSlot, fromType, toSlot, toType, count } = action.payload; 16 | const { sourceInventory, targetInventory } = getTargetInventory(state, fromType, toType); 17 | const pieceWeight = fromSlot.weight / fromSlot.count; 18 | const curTime = Math.floor(Date.now() / 1000); 19 | const fromItem = sourceInventory.items[fromSlot.slot - 1]; 20 | 21 | targetInventory.items[toSlot.slot - 1] = { 22 | ...fromItem, 23 | count: count, 24 | weight: pieceWeight * count, 25 | slot: toSlot.slot, 26 | durability: itemDurability(fromItem.metadata, curTime), 27 | }; 28 | 29 | if (fromType === InventoryType.SHOP || fromType === InventoryType.CRAFTING) return; 30 | 31 | sourceInventory.items[fromSlot.slot - 1] = 32 | fromSlot.count - count > 0 33 | ? { 34 | ...sourceInventory.items[fromSlot.slot - 1], 35 | count: fromSlot.count - count, 36 | weight: pieceWeight * (fromSlot.count - count), 37 | } 38 | : { 39 | slot: fromSlot.slot, 40 | }; 41 | }; 42 | -------------------------------------------------------------------------------- /web/src/hooks/useNuiEvent.ts: -------------------------------------------------------------------------------- 1 | import { MutableRefObject, useEffect, useRef } from 'react'; 2 | import { noop } from '../utils/misc'; 3 | 4 | interface NuiMessageData { 5 | action: string; 6 | data: T; 7 | } 8 | 9 | type NuiHandlerSignature = (data: T) => void; 10 | 11 | /** 12 | * A hook that manage events listeners for receiving data from the client scripts 13 | * @param action The specific `action` that should be listened for. 14 | * @param handler The callback function that will handle data relayed by this hook 15 | * 16 | * @example 17 | * useNuiEvent<{visibility: true, wasVisible: 'something'}>('setVisible', (data) => { 18 | * // whatever logic you want 19 | * }) 20 | * 21 | **/ 22 | 23 | export const useNuiEvent = (action: string, handler: (data: T) => void) => { 24 | const savedHandler: MutableRefObject> = useRef(noop); 25 | 26 | // When handler value changes set mutable ref to handler val 27 | useEffect(() => { 28 | savedHandler.current = handler; 29 | }, [handler]); 30 | 31 | useEffect(() => { 32 | const eventListener = (event: MessageEvent>) => { 33 | const { action: eventAction, data } = event.data; 34 | 35 | if (savedHandler.current) { 36 | if (eventAction === action) { 37 | savedHandler.current(data); 38 | } 39 | } 40 | }; 41 | 42 | window.addEventListener('message', eventListener); 43 | // Remove Event Listener on component cleanup 44 | return () => window.removeEventListener('message', eventListener); 45 | }, [action]); 46 | }; 47 | 48 | export default useNuiEvent; 49 | -------------------------------------------------------------------------------- /modules/bridge/esx/client.lua: -------------------------------------------------------------------------------- 1 | local ESX = setmetatable({}, { 2 | __index = function(self, index) 3 | local obj = exports.es_extended:getSharedObject() 4 | self.SetPlayerData = obj.SetPlayerData 5 | self.PlayerLoaded = obj.PlayerLoaded 6 | return self[index] 7 | end 8 | }) 9 | 10 | ---@diagnostic disable-next-line: duplicate-set-field 11 | function client.setPlayerData(key, value) 12 | PlayerData[key] = value 13 | ESX.SetPlayerData(key, value) 14 | end 15 | 16 | ---@diagnostic disable-next-line: duplicate-set-field 17 | function client.setPlayerStatus(values) 18 | for name, value in pairs(values) do 19 | if value > 0 then TriggerEvent('esx_status:add', name, value) else TriggerEvent('esx_status:remove', name, -value) end 20 | end 21 | end 22 | 23 | RegisterNetEvent('esx:onPlayerLogout', client.onLogout) 24 | 25 | AddEventHandler('esx:setPlayerData', function(key, value) 26 | if not PlayerData.loaded or GetInvokingResource() ~= 'es_extended' then return end 27 | 28 | if key == 'job' then 29 | key = 'groups' 30 | value = { [value.name] = value.grade } 31 | end 32 | 33 | PlayerData[key] = value 34 | OnPlayerData(key, value) 35 | end) 36 | 37 | local Weapon = require 'modules.weapon.client' 38 | 39 | RegisterNetEvent('esx_policejob:handcuff', function() 40 | PlayerData.cuffed = not PlayerData.cuffed 41 | LocalPlayer.state:set('invBusy', PlayerData.cuffed, true) 42 | 43 | if not PlayerData.cuffed then return end 44 | 45 | Weapon.Disarm() 46 | end) 47 | 48 | RegisterNetEvent('esx_policejob:unrestrain', function() 49 | PlayerData.cuffed = false 50 | LocalPlayer.state:set('invBusy', PlayerData.cuffed, true) 51 | end) 52 | -------------------------------------------------------------------------------- /web/src/dnd/onBuy.ts: -------------------------------------------------------------------------------- 1 | import { isSlotWithItem } from '../helpers'; 2 | import { store } from '../store'; 3 | import { DragSource, DropTarget } from '../typings'; 4 | import { Items } from '../store/items'; 5 | import { buyItem } from '../thunks/buyItem'; 6 | 7 | export const onBuy = (source: DragSource, target: DropTarget) => { 8 | const { inventory: state } = store.getState(); 9 | 10 | const sourceInventory = state.rightInventory; 11 | const targetInventory = state.leftInventory; 12 | 13 | const sourceSlot = sourceInventory.items[source.item.slot - 1]; 14 | 15 | if (!isSlotWithItem(sourceSlot)) throw new Error(`Item ${sourceSlot.slot} name === undefined`); 16 | 17 | if (sourceSlot.count === 0) return; 18 | 19 | const sourceData = Items[sourceSlot.name]; 20 | 21 | if (sourceData === undefined) return console.error(`Item ${sourceSlot.name} data undefined!`); 22 | 23 | const targetSlot = targetInventory.items[target.item.slot - 1]; 24 | 25 | if (targetSlot === undefined) return console.error(`Target slot undefined`); 26 | 27 | const count = 28 | state.itemAmount !== 0 29 | ? sourceSlot.count 30 | ? state.itemAmount > sourceSlot.count 31 | ? sourceSlot.count 32 | : state.itemAmount 33 | : state.itemAmount 34 | : 1; 35 | 36 | const data = { 37 | fromSlot: sourceSlot, 38 | toSlot: targetSlot, 39 | fromType: sourceInventory.type, 40 | toType: targetInventory.type, 41 | count: count, 42 | }; 43 | 44 | store.dispatch( 45 | buyItem({ 46 | ...data, 47 | fromSlot: sourceSlot.slot, 48 | toSlot: targetSlot.slot, 49 | }) 50 | ); 51 | }; 52 | -------------------------------------------------------------------------------- /modules/bridge/server.lua: -------------------------------------------------------------------------------- 1 | ---@todo separate module into smaller submodules to handle each framework 2 | ---starting to get bulky 3 | 4 | function server.hasGroup(inv, group) 5 | if type(group) == 'table' then 6 | for name, rank in pairs(group) do 7 | local groupRank = inv.player.groups[name] 8 | if groupRank and groupRank >= (rank or 0) then 9 | return name, groupRank 10 | end 11 | end 12 | else 13 | local groupRank = inv.player.groups[group] 14 | if groupRank then 15 | return group, groupRank 16 | end 17 | end 18 | end 19 | 20 | ---@diagnostic disable-next-line: duplicate-set-field 21 | function server.setPlayerData(player) 22 | if not player.groups then 23 | warn(("server.setPlayerData did not receive any groups for '%s'"):format(player?.name or GetPlayerName(player))) 24 | end 25 | 26 | return { 27 | source = player.source, 28 | name = player.name, 29 | groups = player.groups or {}, 30 | sex = player.sex, 31 | dateofbirth = player.dateofbirth, 32 | } 33 | end 34 | 35 | ---@diagnostic disable-next-line: duplicate-set-field 36 | function server.buyLicense() 37 | warn('Licenses are not supported for the current framework.') 38 | end 39 | 40 | local Inventory = require 'modules.inventory.server' 41 | 42 | function server.playerDropped(source) 43 | local inv = Inventory(source) --[[@as OxInventory]] 44 | 45 | if inv?.player then 46 | inv:closeInventory() 47 | Inventory.Remove(inv) 48 | end 49 | end 50 | 51 | local success, result = pcall(lib.load, ('modules.bridge.%s.server'):format(shared.framework)) 52 | 53 | if not success then 54 | lib = nil 55 | error(result, 0) 56 | end 57 | 58 | if server.convertInventory then exports('ConvertItems', server.convertInventory) end 59 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ox_inventory", 3 | "version": "1.0.0", 4 | "homepage": "web/build", 5 | "private": true, 6 | "dependencies": { 7 | "@floating-ui/react": "^0.25.4", 8 | "@types/lodash": "^4.17.1", 9 | "@types/node": "^18.19.33", 10 | "@types/react": "^18.3.1", 11 | "@types/react-dom": "^18.3.0", 12 | "@types/react-transition-group": "^4.4.10", 13 | "@vitejs/plugin-react": "^3.1.0", 14 | "lodash": "^4.17.21", 15 | "react": "^18.3.1", 16 | "react-dnd": "^16.0.1", 17 | "react-dnd-touch-backend": "^16.0.1", 18 | "react-dom": "^18.3.1", 19 | "react-markdown": "^8.0.7", 20 | "react-redux": "^8.1.3", 21 | "react-transition-group": "^4.4.5", 22 | "redux": "^4.2.1", 23 | "sass": "^1.77.0" 24 | }, 25 | "scripts": { 26 | "start": "vite", 27 | "watch": "vite build --watch", 28 | "build": "tsc && vite build", 29 | "preview": "vite preview", 30 | "format": "prettier --write \"./src/**/*.{ts,tsx,css}\"" 31 | }, 32 | "eslintConfig": { 33 | "extends": [ 34 | "react-app" 35 | ] 36 | }, 37 | "browserslist": { 38 | "production": [ 39 | ">0.2%", 40 | "not dead", 41 | "not op_mini all" 42 | ], 43 | "development": [ 44 | "last 1 chrome version", 45 | "last 1 firefox version", 46 | "last 1 safari version" 47 | ] 48 | }, 49 | "devDependencies": { 50 | "@babel/core": "^7.24.5", 51 | "@redux-devtools/core": "^3.14.0", 52 | "@redux-devtools/instrument": "^2.2.0", 53 | "@reduxjs/toolkit": "^1.9.7", 54 | "@types/react-redux": "^7.1.33", 55 | "cross-env": "^7.0.3", 56 | "csstype": "^2.6.21", 57 | "prettier": "^2.8.8", 58 | "typescript": "^4.9.5", 59 | "vite": "^4.5.3" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /modules/pefcl/server.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Intended for use with https://github.com/project-error/pefcl 3 | config.useFrameworkIntegration.resource should be set as "ox_inventory" 4 | 5 | This isn't intended for use with frameworks with their own accounts, 6 | use the proper pefcl-framework resources and ensure item/account syncing 7 | works on your own. 8 | ]] 9 | 10 | local Inventory = require 'modules.inventory.server' 11 | 12 | ---@param source number 13 | ---@param amount number 14 | exports('addCash', function(source, amount) 15 | Inventory.AddItem(source, 'money', amount) 16 | end) 17 | 18 | ---@param source number 19 | ---@param amount number 20 | exports('removeCash', function(source, amount) 21 | Inventory.RemoveItem(source, 'money', amount) 22 | end) 23 | 24 | ---@param source number 25 | ---@return number 26 | exports('getCash', function(source) 27 | return Inventory.GetItemCount(source, 'money') 28 | end) 29 | 30 | ---@param source number 31 | ---@return table? 32 | exports('getCards', function(source) 33 | local items = Inventory(source)?.items 34 | 35 | if items then 36 | local retval, num = {}, 0 37 | 38 | for _, data in pairs(items) do 39 | if data.name == 'mastercard' then 40 | num += 1 41 | retval[num] = { 42 | id = data.metadata.id, 43 | holder = data.metadata.holder, 44 | number = data.metadata.number 45 | } 46 | end 47 | end 48 | 49 | return retval 50 | end 51 | end) 52 | 53 | ---@param source number 54 | ---@param card table 55 | exports('giveCard', function(source, card) 56 | Inventory.AddItem(source, 'mastercard', 1, { 57 | id = card.id, 58 | holder = card.holder, 59 | number = card.number, 60 | description = ('Card Number: %s'):format(card.number) 61 | }) 62 | end) 63 | 64 | ---no-op 65 | exports('getBank', function() end) 66 | 67 | -------------------------------------------------------------------------------- /modules/items/containers.lua: -------------------------------------------------------------------------------- 1 | local containers = {} 2 | 3 | ---@class ItemContainerProperties 4 | ---@field slots number 5 | ---@field maxWeight number 6 | ---@field whitelist? table | string[] 7 | ---@field blacklist? table | string[] 8 | 9 | local function arrayToSet(tbl) 10 | local size = #tbl 11 | local set = table.create(0, size) 12 | 13 | for i = 1, size do 14 | set[tbl[i]] = true 15 | end 16 | 17 | return set 18 | end 19 | 20 | ---Registers items with itemName as containers (i.e. backpacks, wallets). 21 | ---@param itemName string 22 | ---@param properties ItemContainerProperties 23 | ---@todo Rework containers for flexibility, improved data structure; then export this method. 24 | local function setContainerProperties(itemName, properties) 25 | local blacklist, whitelist = properties.blacklist, properties.whitelist 26 | 27 | if blacklist then 28 | local tableType = table.type(blacklist) 29 | 30 | if tableType == 'array' then 31 | blacklist = arrayToSet(blacklist) 32 | elseif tableType ~= 'hash' then 33 | TypeError('blacklist', 'table', type(blacklist)) 34 | end 35 | end 36 | 37 | if whitelist then 38 | local tableType = table.type(whitelist) 39 | 40 | if tableType == 'array' then 41 | whitelist = arrayToSet(whitelist) 42 | elseif tableType ~= 'hash' then 43 | TypeError('whitelist', 'table', type(whitelist)) 44 | end 45 | end 46 | 47 | containers[itemName] = { 48 | size = { properties.slots, properties.maxWeight }, 49 | blacklist = blacklist, 50 | whitelist = whitelist, 51 | } 52 | end 53 | 54 | setContainerProperties('paperbag', { 55 | slots = 5, 56 | maxWeight = 1000, 57 | blacklist = { 'testburger' } 58 | }) 59 | 60 | setContainerProperties('pizzabox', { 61 | slots = 5, 62 | maxWeight = 1000, 63 | whitelist = { 'pizza' } 64 | }) 65 | 66 | return containers 67 | -------------------------------------------------------------------------------- /modules/bridge/client.lua: -------------------------------------------------------------------------------- 1 | if not lib then return end 2 | 3 | ---@diagnostic disable-next-line: duplicate-set-field 4 | function client.setPlayerData(key, value) 5 | PlayerData[key] = value 6 | OnPlayerData(key, value) 7 | end 8 | 9 | function client.hasGroup(group) 10 | if not PlayerData.loaded then return end 11 | 12 | if type(group) == 'table' then 13 | for name, rank in pairs(group) do 14 | local groupRank = PlayerData.groups[name] 15 | if groupRank and groupRank >= (rank or 0) then 16 | return name, groupRank 17 | end 18 | end 19 | else 20 | local groupRank = PlayerData.groups[group] 21 | if groupRank then 22 | return group, groupRank 23 | end 24 | end 25 | end 26 | 27 | local Shops = require 'modules.shops.client' 28 | local Utils = require 'modules.utils.client' 29 | local Weapon = require 'modules.weapon.client' 30 | local Items = require 'modules.items.client' 31 | 32 | function client.onLogout() 33 | if not PlayerData.loaded then return end 34 | 35 | if client.parachute then 36 | Utils.DeleteEntity(client.parachute[1]) 37 | client.parachute = false 38 | end 39 | 40 | for _, point in pairs(client.drops) do 41 | if point.entity then 42 | Utils.DeleteEntity(point.entity) 43 | end 44 | 45 | point:remove() 46 | end 47 | 48 | for _, v in pairs(Items --[[@as table]]) do 49 | v.count = 0 50 | end 51 | 52 | PlayerData.loaded = false 53 | client.drops = nil 54 | 55 | client.closeInventory() 56 | Shops.wipeShops() 57 | 58 | if client.interval then 59 | ClearInterval(client.interval) 60 | ClearInterval(client.tick) 61 | end 62 | 63 | Weapon.Disarm() 64 | end 65 | 66 | local success, result = pcall(lib.load, ('modules.bridge.%s.client'):format(shared.framework)) 67 | 68 | if not success then 69 | lib.print.error(result) 70 | lib = nil 71 | return 72 | end 73 | -------------------------------------------------------------------------------- /web/src/reducers/setupInventory.ts: -------------------------------------------------------------------------------- 1 | import { CaseReducer, PayloadAction } from '@reduxjs/toolkit'; 2 | import { getItemData, itemDurability } from '../helpers'; 3 | import { Items } from '../store/items'; 4 | import { Inventory, State } from '../typings'; 5 | 6 | export const setupInventoryReducer: CaseReducer< 7 | State, 8 | PayloadAction<{ 9 | leftInventory?: Inventory; 10 | rightInventory?: Inventory; 11 | }> 12 | > = (state, action) => { 13 | const { leftInventory, rightInventory } = action.payload; 14 | const curTime = Math.floor(Date.now() / 1000); 15 | 16 | if (leftInventory) 17 | state.leftInventory = { 18 | ...leftInventory, 19 | items: Array.from(Array(leftInventory.slots), (_, index) => { 20 | const item = Object.values(leftInventory.items).find((item) => item?.slot === index + 1) || { 21 | slot: index + 1, 22 | }; 23 | 24 | if (!item.name) return item; 25 | 26 | if (typeof Items[item.name] === 'undefined') { 27 | getItemData(item.name); 28 | } 29 | 30 | item.durability = itemDurability(item.metadata, curTime); 31 | return item; 32 | }), 33 | }; 34 | 35 | if (rightInventory) 36 | state.rightInventory = { 37 | ...rightInventory, 38 | items: Array.from(Array(rightInventory.slots), (_, index) => { 39 | const item = Object.values(rightInventory.items).find((item) => item?.slot === index + 1) || { 40 | slot: index + 1, 41 | }; 42 | 43 | if (!item.name) return item; 44 | 45 | if (typeof Items[item.name] === 'undefined') { 46 | getItemData(item.name); 47 | } 48 | 49 | item.durability = itemDurability(item.metadata, curTime); 50 | return item; 51 | }), 52 | }; 53 | 54 | state.shiftPressed = false; 55 | state.isBusy = false; 56 | }; 57 | -------------------------------------------------------------------------------- /web/src/components/utils/Tooltip.tsx: -------------------------------------------------------------------------------- 1 | import { flip, FloatingPortal, offset, shift, useFloating, useTransitionStyles } from '@floating-ui/react'; 2 | import React, { useEffect } from 'react'; 3 | import { useAppSelector } from '../../store'; 4 | import SlotTooltip from '../inventory/SlotTooltip'; 5 | 6 | const Tooltip: React.FC = () => { 7 | const hoverData = useAppSelector((state) => state.tooltip); 8 | 9 | const { refs, context, floatingStyles } = useFloating({ 10 | middleware: [flip(), shift(), offset({ mainAxis: 10, crossAxis: 10 })], 11 | open: hoverData.open, 12 | placement: 'right-start', 13 | }); 14 | 15 | const { isMounted, styles } = useTransitionStyles(context, { 16 | duration: 200, 17 | }); 18 | 19 | const handleMouseMove = ({ clientX, clientY }: MouseEvent | React.MouseEvent) => { 20 | refs.setPositionReference({ 21 | getBoundingClientRect() { 22 | return { 23 | width: 0, 24 | height: 0, 25 | x: clientX, 26 | y: clientY, 27 | left: clientX, 28 | top: clientY, 29 | right: clientX, 30 | bottom: clientY, 31 | }; 32 | }, 33 | }); 34 | }; 35 | 36 | useEffect(() => { 37 | window.addEventListener('mousemove', handleMouseMove); 38 | 39 | return () => { 40 | window.removeEventListener('mousemove', handleMouseMove); 41 | }; 42 | }, []); 43 | 44 | return ( 45 | <> 46 | {isMounted && hoverData.item && hoverData.inventoryType && ( 47 | 48 | 54 | 55 | )} 56 | 57 | ); 58 | }; 59 | 60 | export default Tooltip; 61 | -------------------------------------------------------------------------------- /web/src/components/utils/WeightBar.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react'; 2 | 3 | const colorChannelMixer = (colorChannelA: number, colorChannelB: number, amountToMix: number) => { 4 | let channelA = colorChannelA * amountToMix; 5 | let channelB = colorChannelB * (1 - amountToMix); 6 | return channelA + channelB; 7 | }; 8 | 9 | const colorMixer = (rgbA: number[], rgbB: number[], amountToMix: number) => { 10 | let r = colorChannelMixer(rgbA[0], rgbB[0], amountToMix); 11 | let g = colorChannelMixer(rgbA[1], rgbB[1], amountToMix); 12 | let b = colorChannelMixer(rgbA[2], rgbB[2], amountToMix); 13 | return `rgb(${r}, ${g}, ${b})`; 14 | }; 15 | 16 | const COLORS = { 17 | // Colors used - https://materialui.co/flatuicolors 18 | primaryColor: [231, 76, 60], // Red (Pomegranate) 19 | secondColor: [39, 174, 96], // Green (Nephritis) 20 | accentColor: [211, 84, 0], // Orange (Oragne) 21 | }; 22 | 23 | const WeightBar: React.FC<{ percent: number; durability?: boolean }> = ({ percent, durability }) => { 24 | const color = useMemo( 25 | () => 26 | durability 27 | ? percent < 50 28 | ? colorMixer(COLORS.accentColor, COLORS.primaryColor, percent / 100) 29 | : colorMixer(COLORS.secondColor, COLORS.accentColor, percent / 100) 30 | : percent > 50 31 | ? colorMixer(COLORS.primaryColor, COLORS.accentColor, percent / 100) 32 | : colorMixer(COLORS.accentColor, COLORS.secondColor, percent / 50), 33 | [durability, percent] 34 | ); 35 | 36 | return ( 37 |

38 |
0 ? 'visible' : 'hidden', 41 | height: '100%', 42 | width: `${percent}%`, 43 | backgroundColor: color, 44 | transition: `background ${0.3}s ease, width ${0.3}s ease`, 45 | }} 46 | >
47 |
48 | ); 49 | }; 50 | export default WeightBar; 51 | -------------------------------------------------------------------------------- /modules/bridge/ox/server.lua: -------------------------------------------------------------------------------- 1 | if not lib.checkDependency('ox_core', '0.21.3', true) then return end 2 | 3 | local Ox = require '@ox_core.lib.init' --[[@as OxServer]] 4 | 5 | local Inventory = require 'modules.inventory.server' 6 | 7 | AddEventHandler('ox:playerLogout', server.playerDropped) 8 | 9 | AddEventHandler('ox:setGroup', function(source, name, grade) 10 | local inventory = Inventory(source) 11 | 12 | if not inventory then return end 13 | 14 | inventory.player.groups[name] = grade 15 | end) 16 | 17 | ---@diagnostic disable-next-line: duplicate-set-field 18 | function server.setPlayerData(player) 19 | player.groups = Ox.GetPlayer(player.source)?.getGroups() 20 | return player 21 | end 22 | 23 | ---@diagnostic disable-next-line: duplicate-set-field 24 | function server.hasLicense(inv, name) 25 | local player = Ox.GetPlayer(inv.id) 26 | 27 | if not player then return end 28 | 29 | return player.getLicense(name) 30 | end 31 | 32 | ---@diagnostic disable-next-line: duplicate-set-field 33 | function server.buyLicense(inv, license) 34 | local player = Ox.GetPlayer(inv.id) 35 | 36 | if not player then return end 37 | 38 | 39 | if player.getLicense(license.name) then 40 | return false, 'already_have' 41 | elseif Inventory.GetItemCount(inv, 'money') < license.price then 42 | return false, 'can_not_afford' 43 | end 44 | 45 | Inventory.RemoveItem(inv, 'money', license.price) 46 | player.addLicense(license.name) 47 | 48 | return true, 'have_purchased' 49 | end 50 | 51 | ---@diagnostic disable-next-line: duplicate-set-field 52 | function server.isPlayerBoss(playerId, group, grade) 53 | local groupData = GlobalState[('group.%s'):format(group)] 54 | 55 | return groupData and grade >= groupData.adminGrade 56 | end 57 | 58 | ---@param entityId number 59 | ---@return number | string 60 | ---@diagnostic disable-next-line: duplicate-set-field 61 | function server.getOwnedVehicleId(entityId) 62 | return Ox.GetVehicle(entityId)?.id 63 | end 64 | -------------------------------------------------------------------------------- /web/src/components/utils/DragPreview.tsx: -------------------------------------------------------------------------------- 1 | import React, { RefObject, useRef } from 'react'; 2 | import { DragLayerMonitor, useDragLayer, XYCoord } from 'react-dnd'; 3 | import { DragSource } from '../../typings'; 4 | 5 | interface DragLayerProps { 6 | data: DragSource; 7 | currentOffset: XYCoord | null; 8 | isDragging: boolean; 9 | } 10 | 11 | const subtract = (a: XYCoord, b: XYCoord): XYCoord => { 12 | return { 13 | x: a.x - b.x, 14 | y: a.y - b.y, 15 | }; 16 | }; 17 | 18 | const calculateParentOffset = (monitor: DragLayerMonitor): XYCoord => { 19 | const client = monitor.getInitialClientOffset(); 20 | const source = monitor.getInitialSourceClientOffset(); 21 | if (client === null || source === null || client.x === undefined || client.y === undefined) { 22 | return { x: 0, y: 0 }; 23 | } 24 | return subtract(client, source); 25 | }; 26 | 27 | export const calculatePointerPosition = (monitor: DragLayerMonitor, childRef: RefObject): XYCoord | null => { 28 | const offset = monitor.getClientOffset(); 29 | if (offset === null) { 30 | return null; 31 | } 32 | 33 | if (!childRef.current || !childRef.current.getBoundingClientRect) { 34 | return subtract(offset, calculateParentOffset(monitor)); 35 | } 36 | 37 | const bb = childRef.current.getBoundingClientRect(); 38 | const middle = { x: bb.width / 2, y: bb.height / 2 }; 39 | return subtract(offset, middle); 40 | }; 41 | 42 | const DragPreview: React.FC = () => { 43 | const element = useRef(null); 44 | 45 | const { data, isDragging, currentOffset } = useDragLayer((monitor) => ({ 46 | data: monitor.getItem(), 47 | currentOffset: calculatePointerPosition(monitor, element), 48 | isDragging: monitor.isDragging(), 49 | })); 50 | 51 | return ( 52 | <> 53 | {isDragging && currentOffset && data.item && ( 54 |
62 | )} 63 | 64 | ); 65 | }; 66 | 67 | export default DragPreview; 68 | -------------------------------------------------------------------------------- /web/src/components/inventory/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import useNuiEvent from '../../hooks/useNuiEvent'; 3 | import InventoryControl from './InventoryControl'; 4 | import InventoryHotbar from './InventoryHotbar'; 5 | import { useAppDispatch } from '../../store'; 6 | import { refreshSlots, setAdditionalMetadata, setupInventory } from '../../store/inventory'; 7 | import { useExitListener } from '../../hooks/useExitListener'; 8 | import type { Inventory as InventoryProps } from '../../typings'; 9 | import RightInventory from './RightInventory'; 10 | import LeftInventory from './LeftInventory'; 11 | import Tooltip from '../utils/Tooltip'; 12 | import { closeTooltip } from '../../store/tooltip'; 13 | import InventoryContext from './InventoryContext'; 14 | import { closeContextMenu } from '../../store/contextMenu'; 15 | import Fade from '../utils/transitions/Fade'; 16 | 17 | const Inventory: React.FC = () => { 18 | const [inventoryVisible, setInventoryVisible] = useState(false); 19 | const dispatch = useAppDispatch(); 20 | 21 | useNuiEvent('setInventoryVisible', setInventoryVisible); 22 | useNuiEvent('closeInventory', () => { 23 | setInventoryVisible(false); 24 | dispatch(closeContextMenu()); 25 | dispatch(closeTooltip()); 26 | }); 27 | useExitListener(setInventoryVisible); 28 | 29 | useNuiEvent<{ 30 | leftInventory?: InventoryProps; 31 | rightInventory?: InventoryProps; 32 | }>('setupInventory', (data) => { 33 | dispatch(setupInventory(data)); 34 | !inventoryVisible && setInventoryVisible(true); 35 | }); 36 | 37 | useNuiEvent('refreshSlots', (data) => dispatch(refreshSlots(data))); 38 | 39 | useNuiEvent('displayMetadata', (data: Array<{ metadata: string; value: string }>) => { 40 | dispatch(setAdditionalMetadata(data)); 41 | }); 42 | 43 | return ( 44 | <> 45 | 46 |
47 | 48 | 49 | 50 | 51 | 52 |
53 |
54 | 55 | 56 | ); 57 | }; 58 | 59 | export default Inventory; 60 | -------------------------------------------------------------------------------- /web/src/components/inventory/InventoryGrid.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useMemo, useRef, useState } from 'react'; 2 | import { Inventory } from '../../typings'; 3 | import WeightBar from '../utils/WeightBar'; 4 | import InventorySlot from './InventorySlot'; 5 | import { getTotalWeight } from '../../helpers'; 6 | import { useAppSelector } from '../../store'; 7 | import { useIntersection } from '../../hooks/useIntersection'; 8 | 9 | const PAGE_SIZE = 30; 10 | 11 | const InventoryGrid: React.FC<{ inventory: Inventory }> = ({ inventory }) => { 12 | const weight = useMemo( 13 | () => (inventory.maxWeight !== undefined ? Math.floor(getTotalWeight(inventory.items) * 1000) / 1000 : 0), 14 | [inventory.maxWeight, inventory.items] 15 | ); 16 | const [page, setPage] = useState(0); 17 | const containerRef = useRef(null); 18 | const { ref, entry } = useIntersection({ threshold: 0.5 }); 19 | const isBusy = useAppSelector((state) => state.inventory.isBusy); 20 | 21 | useEffect(() => { 22 | if (entry && entry.isIntersecting) { 23 | setPage((prev) => ++prev); 24 | } 25 | }, [entry]); 26 | return ( 27 | <> 28 |
29 |
30 |
31 |

{inventory.label}

32 | {inventory.maxWeight && ( 33 |

34 | {weight / 1000}/{inventory.maxWeight / 1000}kg 35 |

36 | )} 37 |
38 | 39 |
40 |
41 | <> 42 | {inventory.items.slice(0, (page + 1) * PAGE_SIZE).map((item, index) => ( 43 | 51 | ))} 52 | 53 |
54 |
55 | 56 | ); 57 | }; 58 | 59 | export default InventoryGrid; 60 | -------------------------------------------------------------------------------- /data/vehicles.lua: -------------------------------------------------------------------------------- 1 | return { 2 | -- 0 vehicle has no storage 3 | -- 1 vehicle has no trunk storage 4 | -- 2 vehicle has no glovebox storage 5 | -- 3 vehicle has trunk in the hood 6 | Storage = { 7 | [`jester`] = 3, 8 | [`adder`] = 3, 9 | [`osiris`] = 1, 10 | [`pfister811`] = 1, 11 | [`penetrator`] = 1, 12 | [`autarch`] = 1, 13 | [`bullet`] = 1, 14 | [`cheetah`] = 1, 15 | [`cyclone`] = 1, 16 | [`voltic`] = 1, 17 | [`reaper`] = 3, 18 | [`entityxf`] = 1, 19 | [`t20`] = 1, 20 | [`taipan`] = 1, 21 | [`tezeract`] = 1, 22 | [`torero`] = 3, 23 | [`turismor`] = 1, 24 | [`fmj`] = 1, 25 | [`infernus`] = 1, 26 | [`italigtb`] = 3, 27 | [`italigtb2`] = 3, 28 | [`nero2`] = 1, 29 | [`vacca`] = 3, 30 | [`vagner`] = 1, 31 | [`visione`] = 1, 32 | [`prototipo`] = 1, 33 | [`zentorno`] = 1, 34 | [`trophytruck`] = 0, 35 | [`trophytruck2`] = 0, 36 | }, 37 | 38 | -- slots, maxWeight; default weight is 8000 per slot 39 | glovebox = { 40 | [0] = {11, 88000}, -- Compact 41 | [1] = {11, 88000}, -- Sedan 42 | [2] = {11, 88000}, -- SUV 43 | [3] = {11, 88000}, -- Coupe 44 | [4] = {11, 88000}, -- Muscle 45 | [5] = {11, 88000}, -- Sports Classic 46 | [6] = {11, 88000}, -- Sports 47 | [7] = {11, 88000}, -- Super 48 | [8] = {5, 40000}, -- Motorcycle 49 | [9] = {11, 88000}, -- Offroad 50 | [10] = {11, 88000}, -- Industrial 51 | [11] = {11, 88000}, -- Utility 52 | [12] = {11, 88000}, -- Van 53 | [14] = {31, 248000}, -- Boat 54 | [15] = {31, 248000}, -- Helicopter 55 | [16] = {51, 408000}, -- Plane 56 | [17] = {11, 88000}, -- Service 57 | [18] = {11, 88000}, -- Emergency 58 | [19] = {11, 88000}, -- Military 59 | [20] = {11, 88000}, -- Commercial (trucks) 60 | models = { 61 | [`xa21`] = {11, 88000} 62 | } 63 | }, 64 | 65 | trunk = { 66 | [0] = {21, 168000}, -- Compact 67 | [1] = {41, 328000}, -- Sedan 68 | [2] = {51, 408000}, -- SUV 69 | [3] = {31, 248000}, -- Coupe 70 | [4] = {41, 328000}, -- Muscle 71 | [5] = {31, 248000}, -- Sports Classic 72 | [6] = {31, 248000}, -- Sports 73 | [7] = {21, 168000}, -- Super 74 | [8] = {5, 40000}, -- Motorcycle 75 | [9] = {51, 408000}, -- Offroad 76 | [10] = {51, 408000}, -- Industrial 77 | [11] = {41, 328000}, -- Utility 78 | [12] = {61, 488000}, -- Van 79 | -- [14] -- Boat 80 | -- [15] -- Helicopter 81 | -- [16] -- Plane 82 | [17] = {41, 328000}, -- Service 83 | [18] = {41, 328000}, -- Emergency 84 | [19] = {41, 328000}, -- Military 85 | [20] = {61, 488000}, -- Commercial 86 | models = { 87 | [`xa21`] = {11, 10000} 88 | }, 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | pull_request: 16 | # The branches below must be a subset of the branches above 17 | branches: [ main ] 18 | schedule: 19 | - cron: '38 8 * * 4' 20 | 21 | jobs: 22 | analyze: 23 | name: Analyze 24 | runs-on: ubuntu-latest 25 | permissions: 26 | actions: read 27 | contents: read 28 | security-events: write 29 | 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | language: [ 'javascript' ] 34 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 35 | # Learn more: 36 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 37 | 38 | steps: 39 | - name: Checkout repository 40 | uses: actions/checkout@v2 41 | 42 | # Initializes the CodeQL tools for scanning. 43 | - name: Initialize CodeQL 44 | uses: github/codeql-action/init@v1 45 | with: 46 | languages: ${{ matrix.language }} 47 | # If you wish to specify custom queries, you can do so here or in a config file. 48 | # By default, queries listed here will override any specified in a config file. 49 | # Prefix the list here with "+" to use these queries and those in the config file. 50 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 51 | 52 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 53 | # If this step fails, then you should remove it and run the build manually (see below) 54 | - name: Autobuild 55 | uses: github/codeql-action/autobuild@v1 56 | 57 | # ℹ️ Command-line programs to run using the OS shell. 58 | # 📚 https://git.io/JvXDl 59 | 60 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 61 | # and modify them (or add more) to build your code if your project 62 | # uses a compiled language 63 | 64 | #- run: | 65 | # make bootstrap 66 | # make release 67 | 68 | - name: Perform CodeQL Analysis 69 | uses: github/codeql-action/analyze@v1 70 | -------------------------------------------------------------------------------- /modules/crafting/client.lua: -------------------------------------------------------------------------------- 1 | if not lib then return end 2 | 3 | local CraftingBenches = {} 4 | local Items = require 'modules.items.client' 5 | local createBlip = require 'modules.utils.client'.CreateBlip 6 | local Utils = require 'modules.utils.client' 7 | local prompt = { 8 | options = { icon = 'fa-wrench' }, 9 | message = ('**%s** \n%s'):format(locale('open_crafting_bench'), locale('interact_prompt', GetControlInstructionalButton(0, 38, true):sub(3))) 10 | } 11 | 12 | ---@param id number 13 | ---@param data table 14 | local function createCraftingBench(id, data) 15 | CraftingBenches[id] = {} 16 | local recipes = data.items 17 | 18 | if recipes then 19 | data.slots = #recipes 20 | 21 | for i = 1, data.slots do 22 | local recipe = recipes[i] 23 | local item = Items[recipe.name] 24 | 25 | if item then 26 | recipe.weight = item.weight 27 | recipe.slot = i 28 | else 29 | warn(('failed to setup crafting recipe (bench: %s, slot: %s) - item "%s" does not exist'):format(id, i, recipe.name)) 30 | end 31 | end 32 | 33 | local blip = data.blip 34 | 35 | if blip then 36 | blip.name = blip.name or ('ox_crafting_%s'):format(data.label and id or 0) 37 | AddTextEntry(blip.name, data.label or locale('crafting_bench')) 38 | end 39 | 40 | if shared.target then 41 | data.points = nil 42 | if data.zones then 43 | for i = 1, #data.zones do 44 | local zone = data.zones[i] 45 | zone.name = ("craftingbench_%s:%s"):format(id, i) 46 | zone.id = id 47 | zone.index = i 48 | zone.options = { 49 | { 50 | label = zone.label or locale('open_crafting_bench'), 51 | canInteract = data.groups and function() 52 | return client.hasGroup(data.groups) 53 | end or nil, 54 | onSelect = function() 55 | client.openInventory('crafting', { id = id, index = i }) 56 | end, 57 | distance = zone.distance or 2.0, 58 | icon = zone.icon or 'fas fa-wrench', 59 | } 60 | } 61 | 62 | exports.ox_target:addBoxZone(zone) 63 | 64 | if blip then 65 | createBlip(blip, zone.coords) 66 | end 67 | end 68 | end 69 | elseif data.points then 70 | data.zones = nil 71 | 72 | for i = 1, #data.points do 73 | local coords = data.points[i] 74 | 75 | lib.points.new({ 76 | coords = coords, 77 | distance = 16, 78 | benchid = id, 79 | index = i, 80 | inv = 'crafting', 81 | prompt = prompt, 82 | marker = client.craftingmarker, 83 | nearby = Utils.nearbyMarker 84 | }) 85 | 86 | if blip then 87 | createBlip(blip, coords) 88 | end 89 | end 90 | end 91 | 92 | CraftingBenches[id] = data 93 | end 94 | end 95 | 96 | for id, data in pairs(lib.load('data.crafting') or {}) do createCraftingBench(data.name or id, data) end 97 | 98 | return CraftingBenches 99 | -------------------------------------------------------------------------------- /web/src/components/inventory/InventoryControl.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { useDrop } from 'react-dnd'; 3 | import { useAppDispatch, useAppSelector } from '../../store'; 4 | import { selectItemAmount, setItemAmount } from '../../store/inventory'; 5 | import { DragSource } from '../../typings'; 6 | import { onUse } from '../../dnd/onUse'; 7 | import { onGive } from '../../dnd/onGive'; 8 | import { fetchNui } from '../../utils/fetchNui'; 9 | import { Locale } from '../../store/locale'; 10 | import UsefulControls from './UsefulControls'; 11 | 12 | const InventoryControl: React.FC = () => { 13 | const itemAmount = useAppSelector(selectItemAmount); 14 | const dispatch = useAppDispatch(); 15 | 16 | const [infoVisible, setInfoVisible] = useState(false); 17 | 18 | const [, use] = useDrop(() => ({ 19 | accept: 'SLOT', 20 | drop: (source) => { 21 | source.inventory === 'player' && onUse(source.item); 22 | }, 23 | })); 24 | 25 | const [, give] = useDrop(() => ({ 26 | accept: 'SLOT', 27 | drop: (source) => { 28 | source.inventory === 'player' && onGive(source.item); 29 | }, 30 | })); 31 | 32 | const inputHandler = (event: React.ChangeEvent) => { 33 | event.target.valueAsNumber = 34 | isNaN(event.target.valueAsNumber) || event.target.valueAsNumber < 0 ? 0 : Math.floor(event.target.valueAsNumber); 35 | dispatch(setItemAmount(event.target.valueAsNumber)); 36 | }; 37 | 38 | return ( 39 | <> 40 | 41 |
42 |
43 | 50 | 53 | 56 | 59 |
60 |
61 | 62 | 67 | 68 | ); 69 | }; 70 | 71 | export default InventoryControl; 72 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | jobs: 9 | create-release: 10 | name: Build and Create Tagged release 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Install archive tools 15 | run: sudo apt install zip 16 | 17 | - name: Checkout source code 18 | uses: actions/checkout@v2 19 | with: 20 | fetch-depth: 0 21 | ref: ${{ github.event.repository.default_branch }} 22 | 23 | - name: Install pnpm 24 | uses: pnpm/action-setup@v4.0.0 25 | with: 26 | version: 9 27 | 28 | - name: Get variables 29 | id: get_vars 30 | run: | 31 | echo '::set-output name=SHORT_SHA::$(git rev-parse --short HEAD)' 32 | echo '::set-output name=DATE::$(date +'%D')' 33 | 34 | - name: Setup node 35 | uses: actions/setup-node@v2 36 | with: 37 | node-version: 18.x 38 | cache: "pnpm" 39 | cache-dependency-path: "web/pnpm-lock.yaml" 40 | 41 | - name: Install dependencies 42 | run: pnpm i --frozen-lockfile 43 | working-directory: web 44 | 45 | - name: Run build 46 | run: pnpm build 47 | working-directory: web 48 | env: 49 | CI: false 50 | 51 | - name: Bump manifest version 52 | run: node .github/actions/bump-manifest-version.js 53 | env: 54 | TGT_RELEASE_VERSION: ${{ github.ref_name }} 55 | 56 | - name: Push manifest change 57 | uses: EndBug/add-and-commit@v8 58 | with: 59 | add: fxmanifest.lua 60 | push: true 61 | author_name: Manifest Bumper 62 | author_email: 41898282+github-actions[bot]@users.noreply.github.com 63 | message: "chore: bump manifest version to ${{ github.ref_name }}" 64 | 65 | - name: Update tag ref 66 | uses: EndBug/latest-tag@latest 67 | with: 68 | tag-name: ${{ github.ref_name }} 69 | 70 | - name: Bundle files 71 | run: | 72 | mkdir -p ./temp/ox_inventory 73 | mkdir -p ./temp/ox_inventory/web 74 | cp ./{server.lua,README.md,LICENSE,fxmanifest.lua,client.lua,init.lua} ./temp/ox_inventory 75 | cp -r ./{setup,modules,data,locales} ./temp/ox_inventory 76 | cp -r ./web/build ./temp/ox_inventory/web/build 77 | cp ./web/LICENSE ./temp/ox_inventory/web/build 78 | cp -r ./web/images ./temp/ox_inventory/web/images 79 | cd ./temp && zip -r ../ox_inventory.zip ./ox_inventory 80 | 81 | - name: Create Release 82 | uses: "marvinpinto/action-automatic-releases@v1.2.1" 83 | id: auto_release 84 | with: 85 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 86 | title: "${{ env.RELEASE_VERSION }}" 87 | prerelease: false 88 | files: ox_inventory.zip 89 | 90 | env: 91 | CI: false 92 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 93 | -------------------------------------------------------------------------------- /web/src/dnd/onDrop.ts: -------------------------------------------------------------------------------- 1 | import { canStack, findAvailableSlot, getTargetInventory, isSlotWithItem } from '../helpers'; 2 | import { validateMove } from '../thunks/validateItems'; 3 | import { store } from '../store'; 4 | import { DragSource, DropTarget, InventoryType, SlotWithItem } from '../typings'; 5 | import { moveSlots, stackSlots, swapSlots } from '../store/inventory'; 6 | import { Items } from '../store/items'; 7 | 8 | export const onDrop = (source: DragSource, target?: DropTarget) => { 9 | const { inventory: state } = store.getState(); 10 | 11 | const { sourceInventory, targetInventory } = getTargetInventory(state, source.inventory, target?.inventory); 12 | 13 | const sourceSlot = sourceInventory.items[source.item.slot - 1] as SlotWithItem; 14 | 15 | const sourceData = Items[sourceSlot.name]; 16 | 17 | if (sourceData === undefined) return console.error(`${sourceSlot.name} item data undefined!`); 18 | 19 | // If dragging from container slot 20 | if (sourceSlot.metadata?.container !== undefined) { 21 | // Prevent storing container in container 22 | if (targetInventory.type === InventoryType.CONTAINER) 23 | return console.log(`Cannot store container ${sourceSlot.name} inside another container`); 24 | 25 | // Prevent dragging of container slot when opened 26 | if (state.rightInventory.id === sourceSlot.metadata.container) 27 | return console.log(`Cannot move container ${sourceSlot.name} when opened`); 28 | } 29 | 30 | const targetSlot = target 31 | ? targetInventory.items[target.item.slot - 1] 32 | : findAvailableSlot(sourceSlot, sourceData, targetInventory.items); 33 | 34 | if (targetSlot === undefined) return console.error('Target slot undefined!'); 35 | 36 | // If dropping on container slot when opened 37 | if (targetSlot.metadata?.container !== undefined && state.rightInventory.id === targetSlot.metadata.container) 38 | return console.log(`Cannot swap item ${sourceSlot.name} with container ${targetSlot.name} when opened`); 39 | 40 | const count = 41 | state.shiftPressed && sourceSlot.count > 1 && sourceInventory.type !== 'shop' 42 | ? Math.floor(sourceSlot.count / 2) 43 | : state.itemAmount === 0 || state.itemAmount > sourceSlot.count 44 | ? sourceSlot.count 45 | : state.itemAmount; 46 | 47 | const data = { 48 | fromSlot: sourceSlot, 49 | toSlot: targetSlot, 50 | fromType: sourceInventory.type, 51 | toType: targetInventory.type, 52 | count: count, 53 | }; 54 | 55 | store.dispatch( 56 | validateMove({ 57 | ...data, 58 | fromSlot: sourceSlot.slot, 59 | toSlot: targetSlot.slot, 60 | }) 61 | ); 62 | 63 | isSlotWithItem(targetSlot, true) 64 | ? sourceData.stack && canStack(sourceSlot, targetSlot) 65 | ? store.dispatch( 66 | stackSlots({ 67 | ...data, 68 | toSlot: targetSlot, 69 | }) 70 | ) 71 | : store.dispatch( 72 | swapSlots({ 73 | ...data, 74 | toSlot: targetSlot, 75 | }) 76 | ) 77 | : store.dispatch(moveSlots(data)); 78 | }; 79 | -------------------------------------------------------------------------------- /web/src/components/inventory/InventoryHotbar.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { getItemUrl, isSlotWithItem } from '../../helpers'; 3 | import useNuiEvent from '../../hooks/useNuiEvent'; 4 | import { Items } from '../../store/items'; 5 | import WeightBar from '../utils/WeightBar'; 6 | import { useAppSelector } from '../../store'; 7 | import { selectLeftInventory } from '../../store/inventory'; 8 | import { SlotWithItem } from '../../typings'; 9 | import SlideUp from '../utils/transitions/SlideUp'; 10 | 11 | const InventoryHotbar: React.FC = () => { 12 | const [hotbarVisible, setHotbarVisible] = useState(false); 13 | const items = useAppSelector(selectLeftInventory).items.slice(0, 5); 14 | 15 | //stupid fix for timeout 16 | const [handle, setHandle] = useState(); 17 | useNuiEvent('toggleHotbar', () => { 18 | if (hotbarVisible) { 19 | setHotbarVisible(false); 20 | } else { 21 | if (handle) clearTimeout(handle); 22 | setHotbarVisible(true); 23 | setHandle(setTimeout(() => setHotbarVisible(false), 3000)); 24 | } 25 | }); 26 | 27 | return ( 28 | 29 |
30 | {items.map((item) => ( 31 |
38 | {isSlotWithItem(item) && ( 39 |
40 |
41 |
{item.slot}
42 |
43 |

44 | {item.weight > 0 45 | ? item.weight >= 1000 46 | ? `${(item.weight / 1000).toLocaleString('en-us', { 47 | minimumFractionDigits: 2, 48 | })}kg ` 49 | : `${item.weight.toLocaleString('en-us', { 50 | minimumFractionDigits: 0, 51 | })}g ` 52 | : ''} 53 |

54 |

{item.count ? item.count.toLocaleString('en-us') + `x` : ''}

55 |
56 |
57 |
58 | {item?.durability !== undefined && } 59 |
60 |
61 | {item.metadata?.label ? item.metadata.label : Items[item.name]?.label || item.name} 62 |
63 |
64 |
65 |
66 | )} 67 |
68 | ))} 69 |
70 |
71 | ); 72 | }; 73 | 74 | export default InventoryHotbar; 75 | -------------------------------------------------------------------------------- /locales/zh-cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui_use": "使用", 3 | "ui_give": "给予", 4 | "ui_close": "关闭", 5 | "ui_drop": "丢掉", 6 | "ui_removeattachments": "删除附件", 7 | "ui_copy": "复制序列号", 8 | "ui_durability": "耐久性", 9 | "ui_ammo": "弹药", 10 | "ui_serial": "编号", 11 | "ui_components": "组件", 12 | "ui_tint": "色调", 13 | "ui_usefulcontrols": "实用控件", 14 | "ui_rmb": "打开物品菜单", 15 | "ui_ctrl_lmb": "快速将一堆物品移动到另一个物品栏中", 16 | "ui_shift_drag": "将项目数量分成两份", 17 | "ui_ctrl_shift_lmb": "快速将一半物品移动到另一个物品栏中", 18 | "ui_alt_lmb": "快速使用物品", 19 | "ui_ctrl_c": "当悬停在武器上时,复制其序列号", 20 | "ui_remove_ammo": "移除弹药", 21 | "ammo_type": "弹药类型", 22 | "$": "$", 23 | "male": "男性", 24 | "female": "女性", 25 | "used": "使用过", 26 | "ui_removed": "已移除", 27 | "ui_added": "添加", 28 | "ui_holstered": "已收回", 29 | "ui_equipped": "已装备", 30 | "using": "使用 %s", 31 | "inventory_setup": "物品栏已就绪", 32 | "inventory_player_access": "您现在无法打开物品栏", 33 | "inventory_right_access": "您无法打开此物品栏", 34 | "inventory_lost_access": "无法再访问此物品栏", 35 | "wrong_ammo": "您不能用 %s 弹药加载 %s", 36 | "license": "%s 许可证", 37 | "already_have": "您已经拥有 %s 许可证", 38 | "have_purchased": "您已购买 %s 许可证", 39 | "can_not_afford": "您买不起 %s 许可证", 40 | "purchase_license": "购买 %s 许可证", 41 | "interact_prompt": "[%s] 进行交互", 42 | "weapon_unregistered": "%s 未向任何人注册", 43 | "weapon_registered": "%s (%s) 注册到 %s", 44 | "weapon_broken": "这武器坏了", 45 | "vehicle_locked": "车辆已锁定", 46 | "nobody_nearby": "附近没有人", 47 | "give_amount": "您必须输入金额", 48 | "buy_amount": "您必须输入购买金额", 49 | "component_has": "这把武器已经有了 %s", 50 | "component_invalid": "此武器不兼容 %s", 51 | "component_slot_occupied": "该武器的 %s 插槽已被占用", 52 | "cannot_perform": "您无法执行此操作", 53 | "cannot_carry": "您不能带那么多", 54 | "cannot_carry_other": "目标物品栏不能容纳这些物品", 55 | "cannot_carry_limit": "您不能携带超过 %s %s", 56 | "cannot_carry_limit_other": "目标不能携带超过 %s %s", 57 | "items_confiscated": "您的物品已被没收", 58 | "items_returned": "您的物品已被退回", 59 | "item_unauthorised": "您无权购买此商品", 60 | "item_unlicensed": "您无权购买此商品", 61 | "item_not_enough": "您没有足够的 %s", 62 | "cannot_afford": "您买不起 (需要 %s)", 63 | "stash_lowgrade": "您无权携带此物品", 64 | "cannot_use": "无法使用 %s", 65 | "shop_nostock": "商品缺货", 66 | "identification": "性别: %s \n出生日期: %s", 67 | "search_dumpster": "搜索垃圾箱", 68 | "open_label": "打开 %s", 69 | "purchased_for": "已购买 %s %s %s%s", 70 | "unable_stack_items": "您无法堆叠这些物品", 71 | "police_evidence": "警方物证仓库", 72 | "open_police_evidence": "打开警方物证仓库", 73 | "open_stash": "打开藏匿处", 74 | "locker_number": "储物柜号码", 75 | "locker_no_value": "必须包含打开储物柜的号码", 76 | "locker_must_number": "储物柜号码必须是数字", 77 | "weapon_hand_required": "您手上必须有武器", 78 | "weapon_hand_wrong": "拿错了武器", 79 | "open_player_inventory": "打开玩家物品栏~", 80 | "open_secondary_inventory": "打开二级物品栏~", 81 | "disable_hotbar": "显示物品栏快捷栏~", 82 | "reload_weapon": "重装武器~", 83 | "use_hotbar": "使用快捷栏 %s~", 84 | "no_durability": "%s 耐久已耗尽", 85 | "cannot_give": "无法将 %s %s 交给目标", 86 | "evidence_cannot_take": "您无权撤回证据", 87 | "dumpster": "垃圾箱", 88 | "crafting_item": "制作 %s", 89 | "crafting_bench": "工作台", 90 | "open_crafting_bench": "打开工作台", 91 | "not_enough_durability": "%s 没有足够的耐久度", 92 | "storage": "储物柜" 93 | } -------------------------------------------------------------------------------- /locales/zh-tw.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui_use": "使用", 3 | "ui_give": "給予", 4 | "ui_close": "關閉", 5 | "ui_drop": "丟下", 6 | "ui_removeattachments": "移除配件", 7 | "ui_copy": "複製序列號", 8 | "ui_durability": "耐久度", 9 | "ui_ammo": "彈藥", 10 | "ui_serial": "序列號", 11 | "ui_components": "附加組件", 12 | "ui_tint": "塗裝", 13 | "ui_usefulcontrols": "有效的操作", 14 | "ui_rmb": "打開快捷選單", 15 | "ui_ctrl_lmb": "快速移交物品", 16 | "ui_shift_drag": "將物品數量分成一半", 17 | "ui_ctrl_shift_lmb": "快捷移交一半的物品", 18 | "ui_alt_lmb": "物品快捷使用", 19 | "ui_ctrl_c": "將鼠標懸停在武器上時,複製其序列號", 20 | "ui_remove_ammo": "移除彈藥", 21 | "ammo_type": "彈藥類型", 22 | "$": "$", 23 | "male": "男", 24 | "female": "女", 25 | "used": "使用", 26 | "ui_removed": "移除了", 27 | "ui_added": "增加了", 28 | "ui_holstered": "收起武器", 29 | "ui_equipped": "裝備武器", 30 | "using": "使用 %s 中", 31 | "inventory_setup": "庫存已可以開啟", 32 | "inventory_player_access": "你現在無法開啟背包", 33 | "inventory_right_access": "你無法開啟這個物品庫", 34 | "inventory_lost_access": "無權力開啟這個物品庫", 35 | "wrong_ammo": "在 %s 上裝填 %s 時裝填失敗,彈藥類型錯誤", 36 | "license": "%s 許可證", 37 | "already_have": "你已擁有 %s 許可證", 38 | "have_purchased": "你購買了 %s 許可證", 39 | "can_not_afford": "你沒有足夠的金錢購買 %s 許可證", 40 | "purchase_license": "購買 %s 許可證", 41 | "interact_prompt": "[%s] 進行互動", 42 | "weapon_unregistered": "%s 沒有任何人註冊", 43 | "weapon_registered": "%s (%s) 註冊於 %s", 44 | "weapon_broken": "這把武器壞掉了", 45 | "vehicle_locked": "此載具是鎖上的", 46 | "nobody_nearby": "沒有任何人在附近", 47 | "give_amount": "你必須輸入要給予的數量", 48 | "buy_amount": "你必須輸入要購買的數量", 49 | "component_has": "這個武器已經有 %s", 50 | "component_invalid": "這個武器與 %s 不相容", 51 | "component_slot_occupied": "這個武器的 %s 位置已有其他同類型組件", 52 | "cannot_perform": "無法執行這個動作", 53 | "cannot_carry": "背包重量已達限制", 54 | "cannot_carry_other": "目標攜帶重量已達限制", 55 | "cannot_carry_limit": "你無法攜帶超過 %s %s", 56 | "cannot_carry_limit_other": "目標無法攜帶超過 %s %s", 57 | "items_confiscated": "物品已被沒收", 58 | "items_returned": "物品已被歸還", 59 | "item_unauthorised": "無權購買此商品", 60 | "item_unlicensed": "無購買此物品的許可", 61 | "item_not_enough": "你沒有足夠的 %s", 62 | "cannot_afford": "無法得到該物品 (缺少 %s)", 63 | "stash_lowgrade": "無權獲取此物品", 64 | "cannot_use": " %s 無法使用", 65 | "shop_nostock": "此商品缺貨中", 66 | "identification": "性別: %s \n生日: %s", 67 | "search_dumpster": "搜索垃圾桶", 68 | "open_label": "打開 %s", 69 | "purchased_for": "購買了 %s 個 %s 花了 %s%s", 70 | "unable_stack_items": "你無法堆疊此物品", 71 | "police_evidence": "警用證物櫃", 72 | "open_police_evidence": "打開警用證物櫃", 73 | "open_stash": "打開藏匿箱", 74 | "locker_number": "證物箱編號", 75 | "locker_no_value": "必須輸入編號來打開證物箱", 76 | "locker_must_number": "證物箱編號必須為數字", 77 | "weapon_hand_required": "你必須手持武器", 78 | "weapon_hand_wrong": "手持錯誤的武器", 79 | "open_player_inventory": "打開背包~", 80 | "open_secondary_inventory": "打開手套箱~", 81 | "disable_hotbar": "顯示物品快捷欄~", 82 | "reload_weapon": "武器裝填~", 83 | "use_hotbar": "使用快捷欄物品 %s~", 84 | "no_durability": "物品耐久度已耗盡", 85 | "cannot_give": "無法將 %s %s 給予目標 ", 86 | "evidence_cannot_take": "階級不夠,無法拿取證物", 87 | "dumpster": "垃圾桶", 88 | "crafting_item": "製作 %s", 89 | "crafting_bench": "工作台", 90 | "open_crafting_bench": "打開工作台", 91 | "not_enough_durability": "%s 沒有足夠的耐久度", 92 | "storage": "儲櫃" 93 | } 94 | -------------------------------------------------------------------------------- /locales/ko.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui_use": "사용", 3 | "ui_give": "건네기", 4 | "ui_close": "닫기", 5 | "ui_drop": "떨구기", 6 | "ui_removeattachments": "부착물 제거", 7 | "ui_durability": "내구도", 8 | "ui_ammo": "탄약", 9 | "ui_serial": "시리얼 넘버", 10 | "ui_components": "컴포넌트", 11 | "ui_tint": "색조", 12 | "ui_usefulcontrols": "도움말", 13 | "ui_rmb": "아이템 컨메뉴 열기", 14 | "ui_ctrl_lmb": "다른 인벤토리로 빠르게 옮기기", 15 | "ui_shift_drag": "아이템 절반으로 나누기", 16 | "ui_ctrl_shift_lmb": "절반 아이템 빠르게 옮기기", 17 | "ui_alt_lmb": "퀵사용", 18 | "$": "$", 19 | "male": "남성", 20 | "female": "여성", 21 | "used": "사용된", 22 | "ui_removed": "사라짐", 23 | "ui_added": "추가됨", 24 | "ui_holstered": "Holstered", 25 | "ui_equipped": "장착됨", 26 | "using": "사용 %s", 27 | "inventory_setup": "인벤토리 사용이 준비되었습니다!", 28 | "inventory_player_access": "당신은 지금 인벤토리를 열 수 없어요.", 29 | "inventory_right_access": "당신은 이 인벤토리를 열 수 없어요.", 30 | "inventory_lost_access": "더 이상 이 인벤토리에 액세스할 수 없습니다.", 31 | "wrong_ammo": "%s 탄약을 %s 탄약으로 로드할 수 없습니다.", 32 | "weapon_license": "무기 라이센스", 33 | "already_have": "당신은 이미 무기 라이센스를 가지고 있습니다", 34 | "have_purchased": "무기 라이센스를 구매했습니다", 35 | "can_not_afford": "당신은 무기 라이센스를 감당할 수 없습니다", 36 | "purchase_license": "라이선스 구매", 37 | "interact_prompt": "Interact with [%s]", 38 | "weapon_unregistered": "%s은(는) 누구에게도 등록되지 않았습니다.", 39 | "weapon_registered": "%s(%s)이(가) %s에 등록되었습니다.", 40 | "weapon_broken": "이 무기는 고장났다", 41 | "vehicle_locked": "차량이 잠겨 있습니다", 42 | "nobody_nearby": "근처에 아무도 없다", 43 | "give_amount": "제공할 금액을 입력해야 합니다.", 44 | "buy_amount": "구매하려면 금액을 입력해야 합니다.", 45 | "component_has": "이 무기에는 이미 %s이(가) 있습니다.", 46 | "component_invalid": "이 무기는 %s과(와) 호환되지 않습니다.", 47 | "cannot_perform": "이 작업을 수행할 수 없습니다.", 48 | "cannot_carry": "당신은 그렇게 많이 가지고 갈 수 없습니다", 49 | "cannot_carry_other": "대상 인벤토리는 그렇게 많이 보유할 수 없습니다.", 50 | "cannot_carry_limit": "%s %s을(를) 초과할 수 없습니다.", 51 | "cannot_carry_limit_other": "대상은 %s %s을(를) 초과할 수 없습니다.", 52 | "items_confiscated": "귀하의 물품이 압수되었습니다.", 53 | "items_returned": "귀하의 항목이 반환되었습니다", 54 | "item_unauthorised": "이 항목을 구매할 권한이 없습니다.", 55 | "item_unlicensed": "이 항목을 구매할 수 있는 권한이 없습니다.", 56 | "item_not_enough": "%s이(가) 충분하지 않습니다.", 57 | "cannot_afford": "당신은 그것을 감당할 수 없습니다(%s 누락)", 58 | "stash_lowgrade": "이 항목을 가져갈 권한이 없습니다.", 59 | "cannot_use": "%s을(를) 사용할 수 없습니다.", 60 | "shop_nostock": "품목이 품절되었습니다", 61 | "identification": "성별:%s 생년월일: %s", 62 | "search_dumpster": "쓰레기 수거통 검색", 63 | "open_label": "열기 %s", 64 | "purchased_for": "%s %s을(를) %s%s에 대해 구매했습니다.", 65 | "unable_stack_items": "이 항목을 쌓을 수 없습니다", 66 | "police_evidence": "경찰 증거물", 67 | "open_police_evidence": "경찰 증거물 열기", 68 | "open_stash": "보관함 열기", 69 | "locker_number": "로커 번호", 70 | "locker_no_value": "사물함을 열려면 값이 있어야 합니다.", 71 | "locker_must_number": "사물함은 숫자여야 합니다.", 72 | "weapon_hand_required": "당신은 손에 무기가 있어야합니다", 73 | "weapon_hand_wrong": "손에 잘못된 무기", 74 | "open_player_inventory": "플레이어 인벤토리를 엽니다~", 75 | "open_secondary_inventory": "2차 인벤토리 열기~", 76 | "disable_hotbar": "인벤토리 핫바 표시~", 77 | "reload_weapon": "무기 재장전~", 78 | "use_hotbar": "핫바 항목 %s 사용~", 79 | "no_durability": "%s 내구도가 고갈되었습니다", 80 | "cannot_give": "대상에 %s %s을(를) 제공할 수 없습니다.", 81 | "evidence_cannot_take": "증거로 삼을 만큼 등급이 높지 않음", 82 | "dumpster": "쓰레기 수거통" 83 | } 84 | -------------------------------------------------------------------------------- /web/src/components/inventory/UsefulControls.tsx: -------------------------------------------------------------------------------- 1 | import { Locale } from '../../store/locale'; 2 | import React from 'react'; 3 | import { 4 | FloatingFocusManager, 5 | FloatingOverlay, 6 | FloatingPortal, 7 | useDismiss, 8 | useFloating, 9 | useInteractions, 10 | useTransitionStyles, 11 | } from '@floating-ui/react'; 12 | 13 | interface Props { 14 | infoVisible: boolean; 15 | setInfoVisible: React.Dispatch>; 16 | } 17 | 18 | const UsefulControls: React.FC = ({ infoVisible, setInfoVisible }) => { 19 | const { refs, context } = useFloating({ 20 | open: infoVisible, 21 | onOpenChange: setInfoVisible, 22 | }); 23 | 24 | const dismiss = useDismiss(context, { 25 | outsidePressEvent: 'mousedown', 26 | }); 27 | 28 | const { isMounted, styles } = useTransitionStyles(context); 29 | 30 | const { getFloatingProps } = useInteractions([dismiss]); 31 | 32 | return ( 33 | <> 34 | {isMounted && ( 35 | 36 | 37 | 38 |
39 |
40 |

{Locale.ui_usefulcontrols || 'Useful controls'}

41 |
setInfoVisible(false)}> 42 | 43 | 44 | 45 |
46 |
47 |
48 |

49 | RMB 50 |
51 | {Locale.ui_rmb} 52 |

53 |

54 | ALT + LMB 55 |
56 | {Locale.ui_alt_lmb} 57 |

58 |

59 | CTRL + LMB 60 |
61 | {Locale.ui_ctrl_lmb} 62 |

63 |

64 | SHIFT + Drag 65 |
66 | {Locale.ui_shift_drag} 67 |

68 |

69 | CTRL + SHIFT + LMB 70 |
71 | {Locale.ui_ctrl_shift_lmb} 72 |

73 |
🐂
74 |
75 |
76 |
77 |
78 |
79 | )} 80 | 81 | ); 82 | }; 83 | 84 | export default UsefulControls; 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ox_inventory 2 | 3 | A complete inventory system for FiveM, implementing items, weapons, shops, and more without any strict framework dependency. 4 | 5 | ![](https://img.shields.io/github/downloads/overextended/ox_inventory/total?logo=github) 6 | ![](https://img.shields.io/github/downloads/overextended/ox_inventory/latest/total?logo=github) 7 | ![](https://img.shields.io/github/contributors/overextended/ox_inventory?logo=github) 8 | ![](https://img.shields.io/github/v/release/overextended/ox_inventory?logo=github) 9 | 10 | ## 📚 Documentation 11 | 12 | https://overextended.dev/ox_inventory 13 | 14 | ## 💾 Download 15 | 16 | https://github.com/overextended/ox_inventory/releases/latest/download/ox_inventory.zip 17 | 18 | ## Supported frameworks 19 | 20 | We do not guarantee compatibility or support for third-party resources. 21 | 22 | - [ox_core](https://github.com/overextended/ox_core) 23 | - [esx](https://github.com/esx-framework/esx_core) 24 | - [qbox](https://github.com/Qbox-project/qbx_core) 25 | - [nd_core](https://github.com/ND-Framework/ND_Core) 26 | 27 | ## ✨ Features 28 | 29 | - Server-side security ensures interactions with items, shops, and stashes are all validated. 30 | - Logging for important events, such as purchases, item movement, and item creation or removal. 31 | - Supports player-owned vehicles, licenses, and group systems implemented by frameworks. 32 | - Fully synchronised, allowing multiple players to [access the same inventory](https://user-images.githubusercontent.com/65407488/230926091-c0033732-d293-48c9-9d62-6f6ae0a8a488.mp4). 33 | 34 | ### Items 35 | 36 | - Inventory items are stored per-slot, with customisable metadata to support item uniqueness. 37 | - Overrides default weapon-system with weapons as items. 38 | - Weapon attachments and ammo system, including special ammo types. 39 | - Durability, allowing items to be depleted or removed overtime. 40 | - Internal item system provides secure and easy handling for item use effects. 41 | - Compatibility with 3rd party framework item registration. 42 | 43 | ### Shops 44 | 45 | - Restricted access based on groups and licenses. 46 | - Support different currency for items (black money, poker chips, etc). 47 | 48 | ### Stashes 49 | 50 | - Personal stashes, linking a stash with a specific identifier or creating per-player instances. 51 | - Restricted access based on groups. 52 | - Registration of new stashes from any resource. 53 | - Containers allow access to stashes when using an item, like a paperbag or backpack. 54 | - Access gloveboxes and trunks for any vehicle. 55 | - Random item generation inside dumpsters and unowned vehicles. 56 | 57 | ## Copyright 58 | 59 | Copyright © 2024 Overextended 60 | 61 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 62 | 63 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 64 | 65 | You should have received a copy of the GNU General Public License along with this program. If not, see . 66 | -------------------------------------------------------------------------------- /web/src/components/utils/ItemNotifications.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { createPortal } from 'react-dom'; 3 | import { TransitionGroup } from 'react-transition-group'; 4 | import useNuiEvent from '../../hooks/useNuiEvent'; 5 | import useQueue from '../../hooks/useQueue'; 6 | import { Locale } from '../../store/locale'; 7 | import { getItemUrl } from '../../helpers'; 8 | import { SlotWithItem } from '../../typings'; 9 | import { Items } from '../../store/items'; 10 | import Fade from './transitions/Fade'; 11 | 12 | interface ItemNotificationProps { 13 | item: SlotWithItem; 14 | text: string; 15 | } 16 | 17 | export const ItemNotificationsContext = React.createContext<{ 18 | add: (item: ItemNotificationProps) => void; 19 | } | null>(null); 20 | 21 | export const useItemNotifications = () => { 22 | const itemNotificationsContext = useContext(ItemNotificationsContext); 23 | if (!itemNotificationsContext) throw new Error(`ItemNotificationsContext undefined`); 24 | return itemNotificationsContext; 25 | }; 26 | 27 | const ItemNotification = React.forwardRef( 28 | (props: { item: ItemNotificationProps; style?: React.CSSProperties }, ref: React.ForwardedRef) => { 29 | const slotItem = props.item.item; 30 | 31 | return ( 32 |
40 |
41 |
42 |

{props.item.text}

43 |
44 |
45 |
{slotItem.metadata?.label || Items[slotItem.name]?.label}
46 |
47 |
48 |
49 | ); 50 | } 51 | ); 52 | 53 | export const ItemNotificationsProvider = ({ children }: { children: React.ReactNode }) => { 54 | const queue = useQueue<{ 55 | id: number; 56 | item: ItemNotificationProps; 57 | ref: React.RefObject; 58 | }>(); 59 | 60 | const add = (item: ItemNotificationProps) => { 61 | const ref = React.createRef(); 62 | const notification = { id: Date.now(), item, ref: ref }; 63 | 64 | queue.add(notification); 65 | 66 | const timeout = setTimeout(() => { 67 | queue.remove(); 68 | clearTimeout(timeout); 69 | }, 2500); 70 | }; 71 | 72 | useNuiEvent<[item: SlotWithItem, text: string, count?: number]>('itemNotify', ([item, text, count]) => { 73 | add({ item: item, text: count ? `${Locale[text]} ${count}x` : `${Locale[text]}` }); 74 | }); 75 | 76 | return ( 77 | 78 | {children} 79 | {createPortal( 80 | 81 | {queue.values.map((notification, index) => ( 82 | 83 | 84 | 85 | ))} 86 | , 87 | document.body 88 | )} 89 | 90 | ); 91 | }; 92 | -------------------------------------------------------------------------------- /web/src/reducers/refreshSlots.ts: -------------------------------------------------------------------------------- 1 | import { CaseReducer, PayloadAction } from '@reduxjs/toolkit'; 2 | import { itemDurability } from '../helpers'; 3 | import { inventorySlice } from '../store/inventory'; 4 | import { Items } from '../store/items'; 5 | import { InventoryType, Slot, State } from '../typings'; 6 | 7 | export type ItemsPayload = { item: Slot; inventory?: InventoryType }; 8 | 9 | interface Payload { 10 | items?: ItemsPayload | ItemsPayload[]; 11 | itemCount?: Record; 12 | weightData?: { inventoryId: string; maxWeight: number }; 13 | slotsData?: { inventoryId: string; slots: number }; 14 | } 15 | 16 | export const refreshSlotsReducer: CaseReducer> = (state, action) => { 17 | if (action.payload.items) { 18 | if (!Array.isArray(action.payload.items)) action.payload.items = [action.payload.items]; 19 | const curTime = Math.floor(Date.now() / 1000); 20 | 21 | Object.values(action.payload.items) 22 | .filter((data) => !!data) 23 | .forEach((data) => { 24 | const targetInventory = data.inventory 25 | ? data.inventory !== InventoryType.PLAYER 26 | ? state.rightInventory 27 | : state.leftInventory 28 | : state.leftInventory; 29 | 30 | data.item.durability = itemDurability(data.item.metadata, curTime); 31 | targetInventory.items[data.item.slot - 1] = data.item; 32 | }); 33 | 34 | // Janky workaround to force a state rerender for crafting inventory to 35 | // run canCraftItem checks 36 | if (state.rightInventory.type === InventoryType.CRAFTING) { 37 | state.rightInventory = { ...state.rightInventory }; 38 | } 39 | } 40 | 41 | if (action.payload.itemCount) { 42 | const items = Object.entries(action.payload.itemCount); 43 | 44 | for (let i = 0; i < items.length; i++) { 45 | const item = items[i][0]; 46 | const count = items[i][1]; 47 | 48 | if (Items[item]!) { 49 | Items[item]!.count += count; 50 | } else console.log(`Item data for ${item} is undefined`); 51 | } 52 | } 53 | 54 | // Refresh maxWeight when SetMaxWeight is ran while an inventory is open 55 | if (action.payload.weightData) { 56 | const inventoryId = action.payload.weightData.inventoryId; 57 | const inventoryMaxWeight = action.payload.weightData.maxWeight; 58 | const inv = 59 | inventoryId === state.leftInventory.id 60 | ? 'leftInventory' 61 | : inventoryId === state.rightInventory.id 62 | ? 'rightInventory' 63 | : null; 64 | 65 | if (!inv) return; 66 | 67 | state[inv].maxWeight = inventoryMaxWeight; 68 | } 69 | 70 | if (action.payload.slotsData) { 71 | const { inventoryId } = action.payload.slotsData; 72 | const { slots } = action.payload.slotsData; 73 | 74 | const inv = 75 | inventoryId === state.leftInventory.id 76 | ? 'leftInventory' 77 | : inventoryId === state.rightInventory.id 78 | ? 'rightInventory' 79 | : null; 80 | 81 | if (!inv) return; 82 | 83 | state[inv].slots = slots; 84 | inventorySlice.caseReducers.setupInventory(state, { 85 | type: 'setupInventory', 86 | payload: { 87 | leftInventory: inv === 'leftInventory' ? state[inv] : undefined, 88 | rightInventory: inv === 'rightInventory' ? state[inv] : undefined, 89 | }, 90 | }); 91 | } 92 | }; 93 | -------------------------------------------------------------------------------- /modules/items/shared.lua: -------------------------------------------------------------------------------- 1 | local function useExport(resource, export) 2 | return function(...) 3 | return exports[resource][export](nil, ...) 4 | end 5 | end 6 | 7 | local ItemList = {} 8 | local isServer = IsDuplicityVersion() 9 | 10 | local function setImagePath(path) 11 | if path then 12 | return path:match('^[%w]+://') and path or ('%s/%s'):format(client.imagepath, path) 13 | end 14 | end 15 | 16 | ---@param data OxItem 17 | local function newItem(data) 18 | data.weight = data.weight or 0 19 | 20 | if data.close == nil then 21 | data.close = true 22 | end 23 | 24 | if data.stack == nil then 25 | data.stack = true 26 | end 27 | 28 | local clientData, serverData = data.client, data.server 29 | ---@cast clientData -nil 30 | ---@cast serverData -nil 31 | 32 | if not data.consume and (clientData and (clientData.status or clientData.usetime or clientData.export) or serverData?.export) then 33 | data.consume = 1 34 | end 35 | 36 | if isServer then 37 | ---@cast data OxServerItem 38 | serverData = data.server 39 | data.client = nil 40 | 41 | if not data.durability then 42 | if data.degrade or (data.consume and data.consume ~= 0 and data.consume < 1) then 43 | data.durability = true 44 | end 45 | end 46 | 47 | if not serverData then goto continue end 48 | 49 | if serverData.export then 50 | data.cb = useExport(string.strsplit('.', serverData.export)) 51 | end 52 | else 53 | ---@cast data OxClientItem 54 | clientData = data.client 55 | data.server = nil 56 | data.count = 0 57 | 58 | if not clientData then goto continue end 59 | 60 | if clientData.export then 61 | data.export = useExport(string.strsplit('.', clientData.export)) 62 | end 63 | 64 | clientData.image = setImagePath(clientData.image) 65 | 66 | if clientData.propTwo then 67 | clientData.prop = clientData.prop and { clientData.prop, clientData.propTwo } or clientData.propTwo 68 | clientData.propTwo = nil 69 | end 70 | end 71 | 72 | ::continue:: 73 | ItemList[data.name] = data 74 | end 75 | 76 | for type, data in pairs(lib.load('data.weapons') or {}) do 77 | for k, v in pairs(data) do 78 | v.name = k 79 | v.close = type == 'Ammo' and true or false 80 | v.weight = v.weight or 0 81 | 82 | if type == 'Weapons' then 83 | ---@cast v OxWeapon 84 | v.model = v.model or k -- actually weapon type or such? model for compatibility 85 | v.hash = joaat(v.model) 86 | v.stack = v.throwable and true or false 87 | v.durability = v.durability or 0.05 88 | v.weapon = true 89 | else 90 | v.stack = true 91 | end 92 | 93 | v[type == 'Ammo' and 'ammo' or type == 'Components' and 'component' or type == 'Tints' and 'tint' or 'weapon'] = true 94 | 95 | if isServer then v.client = nil else 96 | v.count = 0 97 | v.server = nil 98 | local clientData = v.client 99 | 100 | if clientData?.image then 101 | clientData.image = setImagePath(clientData.image) 102 | end 103 | end 104 | 105 | ItemList[k] = v 106 | end 107 | end 108 | 109 | for k, v in pairs(lib.load('data.items') or {}) do 110 | v.name = k 111 | local success, response = pcall(newItem, v) 112 | 113 | if not success then 114 | warn(('An error occurred while creating item "%s" callback!\n^1SCRIPT ERROR: %s^0'):format(k, response)) 115 | end 116 | end 117 | 118 | ItemList.cash = ItemList.money 119 | 120 | return ItemList 121 | -------------------------------------------------------------------------------- /web/src/store/inventory.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, current, isFulfilled, isPending, isRejected, PayloadAction } from '@reduxjs/toolkit'; 2 | import type { RootState } from '.'; 3 | import { 4 | moveSlotsReducer, 5 | refreshSlotsReducer, 6 | setupInventoryReducer, 7 | stackSlotsReducer, 8 | swapSlotsReducer, 9 | } from '../reducers'; 10 | import { State } from '../typings'; 11 | 12 | const initialState: State = { 13 | leftInventory: { 14 | id: '', 15 | type: '', 16 | slots: 0, 17 | maxWeight: 0, 18 | items: [], 19 | }, 20 | rightInventory: { 21 | id: '', 22 | type: '', 23 | slots: 0, 24 | maxWeight: 0, 25 | items: [], 26 | }, 27 | additionalMetadata: new Array(), 28 | itemAmount: 0, 29 | shiftPressed: false, 30 | isBusy: false, 31 | }; 32 | 33 | export const inventorySlice = createSlice({ 34 | name: 'inventory', 35 | initialState, 36 | reducers: { 37 | stackSlots: stackSlotsReducer, 38 | swapSlots: swapSlotsReducer, 39 | setupInventory: setupInventoryReducer, 40 | moveSlots: moveSlotsReducer, 41 | refreshSlots: refreshSlotsReducer, 42 | setAdditionalMetadata: (state, action: PayloadAction>) => { 43 | const metadata = []; 44 | 45 | for (let i = 0; i < action.payload.length; i++) { 46 | const entry = action.payload[i]; 47 | if (!state.additionalMetadata.find((el) => el.value === entry.value)) metadata.push(entry); 48 | } 49 | 50 | state.additionalMetadata = [...state.additionalMetadata, ...metadata]; 51 | }, 52 | setItemAmount: (state, action: PayloadAction) => { 53 | state.itemAmount = action.payload; 54 | }, 55 | setShiftPressed: (state, action: PayloadAction) => { 56 | state.shiftPressed = action.payload; 57 | }, 58 | setContainerWeight: (state, action: PayloadAction) => { 59 | const container = state.leftInventory.items.find((item) => item.metadata?.container === state.rightInventory.id); 60 | 61 | if (!container) return; 62 | 63 | container.weight = action.payload; 64 | }, 65 | }, 66 | extraReducers: (builder) => { 67 | builder.addMatcher(isPending, (state) => { 68 | state.isBusy = true; 69 | 70 | state.history = { 71 | leftInventory: current(state.leftInventory), 72 | rightInventory: current(state.rightInventory), 73 | }; 74 | }); 75 | builder.addMatcher(isFulfilled, (state) => { 76 | state.isBusy = false; 77 | }); 78 | builder.addMatcher(isRejected, (state) => { 79 | if (state.history && state.history.leftInventory && state.history.rightInventory) { 80 | state.leftInventory = state.history.leftInventory; 81 | state.rightInventory = state.history.rightInventory; 82 | } 83 | state.isBusy = false; 84 | }); 85 | }, 86 | }); 87 | 88 | export const { 89 | setAdditionalMetadata, 90 | setItemAmount, 91 | setShiftPressed, 92 | setupInventory, 93 | swapSlots, 94 | moveSlots, 95 | stackSlots, 96 | refreshSlots, 97 | setContainerWeight, 98 | } = inventorySlice.actions; 99 | export const selectLeftInventory = (state: RootState) => state.inventory.leftInventory; 100 | export const selectRightInventory = (state: RootState) => state.inventory.rightInventory; 101 | export const selectItemAmount = (state: RootState) => state.inventory.itemAmount; 102 | export const selectIsBusy = (state: RootState) => state.inventory.isBusy; 103 | 104 | export default inventorySlice.reducer; 105 | -------------------------------------------------------------------------------- /locales/ar.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui_use": "إستعمال", 3 | "ui_give": "إعطاء", 4 | "ui_close": "إغلاق", 5 | "ui_drop": "رمي", 6 | "ui_removeattachments": "الغاء الملحق", 7 | "ui_copy": "نسخ الكود", 8 | "ui_durability": "المتانة", 9 | "ui_ammo": "الرصاص", 10 | "ui_serial": "الكود", 11 | "ui_components": "الملحقات", 12 | "ui_tint": "الصبعة", 13 | "ui_usefulcontrols": "عناصر تحكم مفيدة", 14 | "ui_rmb": "فتح قائمة التحكات", 15 | "ui_ctrl_lmb": "نقل سريع لـ العناصر في مخزون آخر", 16 | "ui_shift_drag": "قسم كمية إلى نصف", 17 | "ui_ctrl_shift_lmb": "نقل سريع نصف كومة من العناصر في مخزون آخر", 18 | "ui_alt_lmb": "استخدام سريع عنصر", 19 | "ui_ctrl_c": "عند تحوم فوق سلاح فإن النسخ هو لـ الرقم التسلسلي", 20 | "$": "$", 21 | "male": "رجل", 22 | "female": "مرأة", 23 | "used": "استعملت", 24 | "ui_removed": "-", 25 | "ui_added": "+", 26 | "ui_holstered": "حازم", 27 | "ui_equipped": "مسلح", 28 | "using": "%s استعمال", 29 | "inventory_setup": "الحقيبة جاهز للاستخدام", 30 | "inventory_player_access": "لا يمكنك فتح المخزون الخاص بك الآن", 31 | "inventory_right_access": "لا يمكنك فتح هذا المخزون", 32 | "inventory_lost_access": "لم تعد قادرة على الوصول إلى هذه المخزون", 33 | "wrong_ammo": "%s غير ممكن مع %s", 34 | "weapon_license": "رخصة اسلحة", 35 | "already_have": "لديك بالفعل ترخيص الأسلحة", 36 | "have_purchased": "لقد اشتريت ترخيص أسلحة", 37 | "can_not_afford": "لا يمكنك تحمل ترخيص الأسلحة", 38 | "purchase_license": "ﺔﺼﺧﺭ ﺀﺍﺮﺷ", 39 | "interact_prompt": "Interact with [%s]", 40 | "weapon_unregistered": "%s غير مسجل لأي شخص", 41 | "weapon_registered": "%s (%s) مسجل في %s", 42 | "weapon_broken": "هذا السلاح مكسور", 43 | "vehicle_locked": "تم حبس السيارة", 44 | "nobody_nearby": "لا يوجد أحد في مكان قريب", 45 | "give_amount": "يجب إدخال مبلغ لإعطاء", 46 | "buy_amount": "يجب إدخال مبلغ لشراء", 47 | "component_has": "%s هذا السلاح لديه بالفعل", 48 | "component_invalid": "%s هذا السلاح غير متوافق مع", 49 | "cannot_perform": "لا يمكنك تنفيذ هذا الإجراء", 50 | "cannot_carry": "لا يمكنك حمل ذلك كثيرا", 51 | "cannot_carry_other": "المخزون المستهدف لا يمكن أن يحمل الكثير", 52 | "cannot_carry_limit": "لا يمكنك حمل أكثر من %s %s", 53 | "cannot_carry_limit_other": "الهدف لا يمكن أن تحمل أكثر من %s %s", 54 | "items_confiscated": "تم صوادر البنود الخاصة بك", 55 | "items_returned": "تم إرجاع البنود الخاصة بك", 56 | "item_unauthorised": "أنت غير مصرح لك بشراء هذا البند", 57 | "item_unlicensed": "أنت غير مرخص لشراء هذا البند", 58 | "item_not_enough": "%s ليس لديك ما يكفي", 59 | "cannot_afford": "(%s) لا يمكنك تحمل ذلك", 60 | "stash_lowgrade": "أنت غير مخول لأخذ هذا البند", 61 | "cannot_use": "%s غير قادر على الاستخدام", 62 | "shop_nostock": "البضاعة غير متوفرة", 63 | "identification": "%s الجنس \n %s تاريخ الميلاد", 64 | "search_dumpster": "بحث القمامة", 65 | "open_label": "%s فتح", 66 | "purchased_for": "%s %s = %s%s", 67 | "unable_stack_items": "أنت غير قادر على تكديس هذه العناصر", 68 | "police_evidence": "مخزن الأدلة", 69 | "open_police_evidence": "فتح مخزن ادلة الشرطة", 70 | "open_stash": "فتح حقيبة", 71 | "locker_number": "رقم الخزانة", 72 | "locker_no_value": "يجب أن تحتوي على قيمة لفتح الخزانة", 73 | "locker_must_number": "يجب أن يكون الخزانة عددا", 74 | "weapon_hand_required": "يجب أن يكون لديك سلاح في متناول اليد", 75 | "weapon_hand_wrong": "سلاح خاطئ في متناول اليد", 76 | "open_player_inventory": "فتح مخزون اللاعب~", 77 | "open_secondary_inventory": "فتح الحقيبة الثانوية~", 78 | "disable_hotbar": "عرض المخزون HOTBAR.~", 79 | "reload_weapon": "تحديث سلاح~", 80 | "use_hotbar": "استخدام عنصر هوتبار بند %s~", 81 | "no_durability": "%s مكسور", 82 | "cannot_give": "غير قادر على الاعطاء", 83 | "evidence_cannot_take": "ليس درجة عالية بما فيه الكفاية لاتخاذ من الأدلة", 84 | "dumpster": "القمامة" 85 | } 86 | -------------------------------------------------------------------------------- /modules/hooks/server.lua: -------------------------------------------------------------------------------- 1 | if not lib then return end 2 | 3 | local eventHooks = {} 4 | local microtime = os.microtime 5 | 6 | local function itemFilter(filter, item, secondItem) 7 | local itemName = type(item) == 'table' and item.name or item 8 | 9 | if not itemName or not filter[itemName] then 10 | if type(secondItem) ~= 'table' or not filter[secondItem.name] then 11 | return false 12 | end 13 | end 14 | 15 | return true 16 | end 17 | 18 | local function inventoryFilter(filter, inventory, secondInventory) 19 | for i = 1, #filter do 20 | local pattern = filter[i] 21 | 22 | if inventory:match(pattern) or (secondInventory and secondInventory:match(pattern)) then 23 | return true 24 | end 25 | end 26 | 27 | return false 28 | end 29 | 30 | local function typeFilter(filter, type) 31 | return filter[type] or false 32 | end 33 | 34 | local function TriggerEventHooks(event, payload) 35 | local hooks = eventHooks[event] 36 | 37 | if hooks then 38 | local fromInventory = payload.fromInventory and tostring(payload.fromInventory) or payload.inventoryId and tostring(payload.inventoryId) or payload.shopType and tostring(payload.shopType) 39 | local toInventory = payload.toInventory and tostring(payload.toInventory) 40 | 41 | for i = 1, #hooks do 42 | local hook = hooks[i] 43 | 44 | if hook.itemFilter and not itemFilter(hook.itemFilter, payload.fromSlot or payload.item or payload.itemName or payload.recipe, payload.toSlot) then 45 | goto skipLoop 46 | end 47 | 48 | if hook.inventoryFilter and not inventoryFilter(hook.inventoryFilter, fromInventory, toInventory) then 49 | goto skipLoop 50 | end 51 | 52 | if hook.typeFilter and not typeFilter(hook.typeFilter, payload.inventoryType or payload.shopType or payload.fromType) then 53 | goto skipLoop 54 | end 55 | 56 | if hook.print then 57 | shared.info(('Triggering event hook "%s:%s:%s".'):format(hook.resource, event, i)) 58 | end 59 | 60 | local start = microtime() 61 | local _, response = pcall(hooks[i], payload) 62 | local executionTime = microtime() - start 63 | 64 | if executionTime >= 100000 then 65 | warn(('Execution of event hook "%s:%s:%s" took %.2fms.'):format(hook.resource, event, i, executionTime / 1e3)) 66 | end 67 | 68 | if event == 'createItem' then 69 | if type(response) == 'table' then 70 | payload.metadata = response 71 | end 72 | elseif response == false then 73 | return false 74 | end 75 | 76 | ::skipLoop:: 77 | end 78 | end 79 | 80 | if event == 'createItem' then 81 | return payload.metadata 82 | end 83 | 84 | return true 85 | end 86 | 87 | local hookId = 0 88 | 89 | exports('registerHook', function(event, cb, options) 90 | if not eventHooks[event] then 91 | eventHooks[event] = {} 92 | end 93 | 94 | local mt = getmetatable(cb) 95 | mt.__index = nil 96 | mt.__newindex = nil 97 | cb.resource = GetInvokingResource() 98 | hookId += 1 99 | cb.hookId = hookId 100 | 101 | if options then 102 | for k, v in pairs(options) do 103 | cb[k] = v 104 | end 105 | end 106 | 107 | eventHooks[event][#eventHooks[event] + 1] = cb 108 | return hookId 109 | end) 110 | 111 | local function removeResourceHooks(resource, id) 112 | for _, hooks in pairs(eventHooks) do 113 | for i = #hooks, 1, -1 do 114 | local hook = hooks[i] 115 | 116 | if hook.resource == resource and (not id or hook.hookId == id) then 117 | table.remove(hooks, i) 118 | end 119 | end 120 | end 121 | end 122 | 123 | AddEventHandler('onResourceStop', removeResourceHooks) 124 | 125 | exports('removeHooks', function(id) 126 | removeResourceHooks(GetInvokingResource() or cache.resource, id) 127 | end) 128 | 129 | return TriggerEventHooks 130 | -------------------------------------------------------------------------------- /web/src/App.tsx: -------------------------------------------------------------------------------- 1 | import InventoryComponent from './components/inventory'; 2 | import useNuiEvent from './hooks/useNuiEvent'; 3 | import { Items } from './store/items'; 4 | import { Locale } from './store/locale'; 5 | import { setImagePath } from './store/imagepath'; 6 | import { setupInventory } from './store/inventory'; 7 | import { Inventory } from './typings'; 8 | import { useAppDispatch } from './store'; 9 | import { debugData } from './utils/debugData'; 10 | import DragPreview from './components/utils/DragPreview'; 11 | import { fetchNui } from './utils/fetchNui'; 12 | import { useDragDropManager } from 'react-dnd'; 13 | import KeyPress from './components/utils/KeyPress'; 14 | 15 | debugData([ 16 | { 17 | action: 'setupInventory', 18 | data: { 19 | leftInventory: { 20 | id: 'test', 21 | type: 'player', 22 | slots: 50, 23 | label: 'Bob Smith', 24 | weight: 3000, 25 | maxWeight: 5000, 26 | items: [ 27 | { 28 | slot: 1, 29 | name: 'iron', 30 | weight: 3000, 31 | metadata: { 32 | description: `name: Svetozar Miletic \n Gender: Male`, 33 | ammo: 3, 34 | mustard: '60%', 35 | ketchup: '30%', 36 | mayo: '10%', 37 | }, 38 | count: 5, 39 | }, 40 | { slot: 2, name: 'powersaw', weight: 0, count: 1, metadata: { durability: 75 } }, 41 | { slot: 3, name: 'copper', weight: 100, count: 12, metadata: { type: 'Special' } }, 42 | { 43 | slot: 4, 44 | name: 'water', 45 | weight: 100, 46 | count: 1, 47 | metadata: { description: 'Generic item description' }, 48 | }, 49 | { slot: 5, name: 'water', weight: 100, count: 1 }, 50 | { 51 | slot: 6, 52 | name: 'backwoods', 53 | weight: 100, 54 | count: 1, 55 | metadata: { 56 | label: 'Russian Cream', 57 | imageurl: 'https://i.imgur.com/2xHhTTz.png', 58 | }, 59 | }, 60 | ], 61 | }, 62 | rightInventory: { 63 | id: 'shop', 64 | type: 'crafting', 65 | slots: 5000, 66 | label: 'Bob Smith', 67 | weight: 3000, 68 | maxWeight: 5000, 69 | items: [ 70 | { 71 | slot: 1, 72 | name: 'lockpick', 73 | weight: 500, 74 | price: 300, 75 | ingredients: { 76 | iron: 5, 77 | copper: 12, 78 | powersaw: 0.1, 79 | }, 80 | metadata: { 81 | description: 'Simple lockpick that breaks easily and can pick basic door locks', 82 | }, 83 | }, 84 | ], 85 | }, 86 | }, 87 | }, 88 | ]); 89 | 90 | const App: React.FC = () => { 91 | const dispatch = useAppDispatch(); 92 | const manager = useDragDropManager(); 93 | 94 | useNuiEvent<{ 95 | locale: { [key: string]: string }; 96 | items: typeof Items; 97 | leftInventory: Inventory; 98 | imagepath: string; 99 | }>('init', ({ locale, items, leftInventory, imagepath }) => { 100 | for (const name in locale) Locale[name] = locale[name]; 101 | for (const name in items) Items[name] = items[name]; 102 | 103 | setImagePath(imagepath); 104 | dispatch(setupInventory({ leftInventory })); 105 | }); 106 | 107 | fetchNui('uiLoaded', {}); 108 | 109 | useNuiEvent('closeInventory', () => { 110 | manager.dispatch({ type: 'dnd-core/END_DRAG' }); 111 | }); 112 | 113 | return ( 114 |
115 | 116 | 117 | 118 |
119 | ); 120 | }; 121 | 122 | addEventListener("dragstart", function(event) { 123 | event.preventDefault() 124 | }) 125 | 126 | export default App; 127 | -------------------------------------------------------------------------------- /locales/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui_use": "Använd", 3 | "ui_give": "Ge", 4 | "ui_close": "Stäng", 5 | "ui_drop": "Kasta", 6 | "ui_removeattachments": "Ta av attachment", 7 | "ui_copy": "Kopiera serienummer", 8 | "ui_durability": "Hållbarhet", 9 | "ui_ammo": "Ammunition", 10 | "ui_serial": "Serienummer", 11 | "ui_components": "Komponenter", 12 | "ui_tint": "Färgton", 13 | "ui_usefulcontrols": "Användbara kontroller", 14 | "ui_rmb": "Open item context menu", 15 | "ui_ctrl_lmb": "Flytta snabbt en hög med föremål till ett annat inventory", 16 | "ui_shift_drag": "Dela kvantiteten på hälften", 17 | "ui_ctrl_shift_lmb": "Flytta snabbt en hög med föremål till ett annat inventory", 18 | "ui_alt_lmb": "Snabbanvänd ett föremål", 19 | "ui_ctrl_c": "När du håller över vapnet kopieras serienumret", 20 | "$": "SEK", 21 | "male": "Man", 22 | "female": "Kvinna", 23 | "used": "Använd", 24 | "ui_removed": "Borttagen", 25 | "ui_added": "Tillagd", 26 | "ui_holstered": "Hölstrad", 27 | "ui_equipped": "Utrustad", 28 | "using": "Använder %s", 29 | "inventory_setup": "Ditt inventory är redo att användas", 30 | "inventory_player_access": "Du kan inte öppna ditt inventory just nu", 31 | "inventory_right_access": "Kan inte öppna ditt inventory", 32 | "inventory_lost_access": "Du har inte tillgång till detta inventory", 33 | "wrong_ammo": "Du kan inte ladda %s med %s ammo", 34 | "weapon_license": "Vapenlicens", 35 | "has_weapon_license": "Du har redan vapenlicens", 36 | "bought_weapon_license": "Du har köpt vapenlicens", 37 | "poor_weapon_license": "Du har inte råd med vapenlicens", 38 | "purchase_license": "[~g~E~s~] Köp licens", 39 | "weapon_unregistered": "%s är inte registrerad till någon", 40 | "weapon_registered": "%s (%s) är registrerad till %s", 41 | "weapon_broken": "Vapnet är trasigt", 42 | "vehicle_locked": "Fordonet är låst", 43 | "nobody_nearby": "Det är ingen i närheten", 44 | "give_amount": "Du behöver ange en summa att ge", 45 | "buy_amount": "Du behöver ange en summa för att köpa", 46 | "component_has": "Det här vapnet har redan en %s", 47 | "component_invalid": "Detta vapen är inkompatibelt med %s", 48 | "cannot_perform": "Du kan inte utföra den här åtgärden", 49 | "cannot_carry": "Du kan inte bära så mycket", 50 | "cannot_carry_other": "Andra inventoryt kan inte hålla så mycket", 51 | "cannot_carry_limit": "Du kan inte bära mer än %s %s", 52 | "cannot_carry_limit_other": "Målet kan inte bära mer än %s %s", 53 | "items_confiscated": "Dina saker har blivit konfiskerade", 54 | "items_returned": "Du har fått tillbaka dina föremål", 55 | "item_unauthorised": "Du har inte behörighet att köpa det", 56 | "item_unlicensed": "Du har inte tillgång till att köpa saken", 57 | "item_not_enough": "Du har inte tillräckligt %s", 58 | "cannot_afford": "Du har inte råd (saknas %s)", 59 | "stash_lowgrade": "Du har inte behörighet att ta detta föremål", 60 | "cannot_use": "Kan inte använda %s", 61 | "shop_nostock": "Varan är slut i lager", 62 | "identification": "Kön: %s \nFödelsedatum: %s", 63 | "search_dumpster": "Sök igenom soptunnan", 64 | "open_shop": "Öppna %s", 65 | "purchased_for": "Köpt %s %s för %s%s", 66 | "unable_stack_items": "Du kan inte stapla dessa items", 67 | "police_evidence": "Bevismaterial", 68 | "open_police_evidence": "Öppna bevismaterial", 69 | "open_stash": "Öppna förråd", 70 | "locker_number": "Skåpnummer", 71 | "locker_no_value": "Måste innehålla ett värde för att öppna skåpet", 72 | "locker_must_number": "Skåpet måste ha ett nummer", 73 | "weapon_hand_required": "Du måste ha ett vapen i handen", 74 | "weapon_hand_wrong": "Fel vapen i handen", 75 | "open_player_inventory": "Öppna personens inventory~", 76 | "open_secondary_inventory": "Öppna andrahands inventory~", 77 | "disable_hotbar": "Visa inventory hotbar~", 78 | "reload_weapon": "Ladda om vapnet~", 79 | "use_hotbar": "Använd hotbar item %s~", 80 | "no_durability": "%s Hållbarheten är slut", 81 | "cannot_give": "Kan inte ge %s %s till målet", 82 | "evidence_cannot_take": "Du har inte rätt att dra tillbaka bevis", 83 | "dumpster": "Soptunna" 84 | } 85 | -------------------------------------------------------------------------------- /locales/hr.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui_use": "Iskoristi", 3 | "ui_give": "Daj", 4 | "ui_close": "Zatvori", 5 | "ui_drop": "Baci", 6 | "ui_removeattachments": "Skini Dodatke", 7 | "ui_copy": "Kopiraj serijski broj", 8 | "ui_durability": "Izdržljivost", 9 | "ui_ammo": "Metci", 10 | "ui_serial": "Serijski Broj", 11 | "ui_components": "Komponente", 12 | "ui_tint": "Tinta", 13 | "ui_usefulcontrols": "Korisne kontrole", 14 | "ui_rmb": "Predmet Konteks Menu", 15 | "ui_ctrl_lmb": "Brzo premjestite hrpu predmeta u drugi inventar", 16 | "ui_shift_drag": "Razdvoji predmete na pola", 17 | "ui_ctrl_shift_lmb": "Brzo premjestite pola hrpe predmeta u drugi inventar", 18 | "ui_alt_lmb": "Brzo iskoristi predmet", 19 | "ui_ctrl_c": "Kada si mišem iznad oružija, kopira serijski broj", 20 | "$": "$", 21 | "male": "Muško", 22 | "female": "Žensko", 23 | "used": "Iskorišteno", 24 | "ui_removed": "Uklonjeno", 25 | "ui_added": "Dodano", 26 | "ui_holstered": "U futroli", 27 | "ui_equipped": "U rukama", 28 | "using": "Koristiš %s", 29 | "inventory_setup": "Inventar je spreman za korištenje", 30 | "inventory_player_access": "Trenutno ne možeš otvoriti ovaj inventar", 31 | "inventory_right_access": "Trenutno ne možeš otvoriti ovaj inventar", 32 | "inventory_lost_access": "Više ne možeš pristupiti ovom inventaru", 33 | "wrong_ammo": "Ne možete napuniti %s streljivom %s", 34 | "weapon_license": "Dozvola za oružije", 35 | "already_have": "Već imaš dozvolu za oružije", 36 | "have_purchased": "Kupio si dozvola za oružije", 37 | "can_not_afford": "Ne možeš si priuštiti dozvolu za oružije", 38 | "purchase_license": "Kupi Licenu", 39 | "interact_prompt": "Interact with [%s]", 40 | "weapon_unregistered": "%s nije registriran na nikoga", 41 | "weapon_registered": "%s (%s) je registriran na %s", 42 | "weapon_broken": "Oružije je potrgano", 43 | "vehicle_locked": "Vozilo zaključano", 44 | "nobody_nearby": "Nema nikoga u blizini", 45 | "give_amount": "Morate unijeti iznos koji želite dati", 46 | "buy_amount": "Morate unijeti iznos za kupnju", 47 | "component_has": "Ovo oružje već ima %s", 48 | "component_invalid": "Ovo oružje nije kompatibilno sa %s", 49 | "cannot_perform": "Ne možeš izvesti tu radnju", 50 | "cannot_carry": "Ne možeš nositi toliko", 51 | "cannot_carry_other": "Ciljani inventar ne može zadržati toliko", 52 | "cannot_carry_limit": "Ne možete nositi više od %s %s", 53 | "cannot_carry_limit_other": "Cilj ne može nositi više od %s %s", 54 | "items_confiscated": "Vaši predmeti su zaplijenjeni", 55 | "items_returned": "Vaši artikli su vraćeni", 56 | "item_unauthorised": "Niste ovlašteni za kupnju ovog artikla", 57 | "item_unlicensed": "Nemate licencu za kupnju ovog artikla", 58 | "item_not_enough": "Nemate dovoljno %s", 59 | "cannot_afford": "Ne možete si to priuštiti (nedostaje %s)", 60 | "stash_lowgrade": "Niste ovlašteni preuzeti ovaj predmet", 61 | "cannot_use": "Nije moguće koristiti %s", 62 | "shop_nostock": "Artikl nije na zalihama", 63 | "identification": "Spol: %s \nDatum rođenja: %s", 64 | "search_dumpster": "Pretražite kontejner za smeće", 65 | "open_label": "Otvori %s", 66 | "purchased_for": "Kupljeno %s %s za %s%s", 67 | "unable_stack_items": "Ne možeš ostaviti stvari ovdje", 68 | "police_evidence": "Policijski dokazi", 69 | "open_police_evidence": "Otvori Policijske dokaze", 70 | "open_stash": "Otvori Stash", 71 | "locker_number": "Ormarić", 72 | "locker_no_value": "Mora sadržavati vrijednost za otvaranje ormarića", 73 | "locker_must_number": "Ormar mora biti broj", 74 | "weapon_hand_required": "Morate imati oružje u ruci", 75 | "weapon_hand_wrong": "Pogrešno oružje u ruci", 76 | "open_player_inventory": "Otvori inventar igrača~", 77 | "open_secondary_inventory": "Otvori sekundarni inventar~", 78 | "disable_hotbar": "Prikaz inventara hotbar~", 79 | "reload_weapon": "Ponovno napuni oružje~", 80 | "use_hotbar": "Koristite hotbar stavku %s~", 81 | "no_durability": "Izdržljivost je jako slaba", 82 | "cannot_give": "Nije moguće dati %s %s cilju", 83 | "evidence_cannot_take": "Nisi autoriziran za ovo", 84 | "dumpster": "Kontejner" 85 | } 86 | -------------------------------------------------------------------------------- /locales/sl.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui_use": "Uporaba", 3 | "ui_give": "Daj", 4 | "ui_close": "Zapri", 5 | "ui_drop": "Spusti", 6 | "ui_removeattachments": "Odstrani dodatke", 7 | "ui_copy": "Kopiraj serialno številko", 8 | "ui_durability": "Trajnost", 9 | "ui_ammo": "Strelivo", 10 | "ui_serial": "Serijska številka", 11 | "ui_components": "Komponente", 12 | "ui_tint": "Odtenek", 13 | "ui_usefulcontrols": "Koristne kontrole", 14 | "ui_rmb": "Odpri kontekstni meni elementa", 15 | "ui_ctrl_lmb": "Hitro premaknite kup predmetov v drug inventar", 16 | "ui_shift_drag": "Razdeli količino artikla na polovico", 17 | "ui_ctrl_shift_lmb": "Hitro premaknite polovico sveženj predmetov v drug inventar", 18 | "ui_alt_lmb": "Hitra uporaba predmeta", 19 | "ui_ctrl_c": "Ko drzite miško naz orozjem, kopira serialno številko.", 20 | "$": "€", 21 | "moški": "Moški", 22 | "female": "Ženska", 23 | "used": "Rabljen", 24 | "ui_removed": "Odstranjen", 25 | "ui_added": "Dodan", 26 | "ui_holstered": "V kuburi", 27 | "ui_equipped": "Opremljen", 28 | "using": "Uporaba %s", 29 | "inventory_setup": "Inventory je pripravljen za uporabo", 30 | "inventory_player_access": "Trenutno ne morete odpreti svojega inventarja", 31 | "inventory_right_access": "Tega inventarja ne morete odpreti", 32 | "inventory_lost_access": "Ne morem več dostopati do tega inventarja", 33 | "wrong_ammo": "Ne morete naložiti %s s strelivom %s", 34 | "weapon_license": "Licenca za orožje", 35 | "already_have": "Licenco za orožje že imate", 36 | "have_purchased": "Kupili ste licenco za orožje", 37 | "can_not_afford": "Licenca za orožje si ne morete privoščiti", 38 | "purchase_license": "Nakup licence", 39 | "interact_prompt": "Interact with [%s]", 40 | "weapon_unregistered": "%s ni prijavljen nikomur", 41 | "weapon_registered": "%s (%s) je registriran na %s", 42 | "weapon_broken": "To orožje je pokvarjeno", 43 | "vehicle_locked": "Vozilo je zaklenjeno", 44 | "nobody_nearby": "V bližini ni nikogar", 45 | "give_amount": "Vnesti morate znesek, ki ga želite dati", 46 | "buy_amount": "Za nakup morate vnesti znesek", 47 | "component_has": "To orožje že ima %s", 48 | "component_invalid": "To orožje ni združljivo z %s", 49 | "cannot_perform": "Tega dejanja ne morete izvesti", 50 | "cannot_carry": "Ne moreš nositi toliko", 51 | "cannot_carry_other": "Ciljni inventar ne more zadržati toliko", 52 | "cannot_carry_limit": "Ne morete nositi več kot %s %s", 53 | "cannot_carry_limit_other": "Cilj ne more prenesti več kot %s %s", 54 | "items_confiscated": "Vaši predmeti so bili zaplenjeni", 55 | "items_returned": "Vaši predmeti so bili vrnjeni", 56 | "item_unauthorised": "Niste pooblaščeni za nakup tega izdelka", 57 | "item_unlicensed": "Nimate licence za nakup tega izdelka", 58 | "item_not_enough": "Nimate dovolj %s", 59 | "cannot_afford": "Tega si ne morete privoščiti (manjka %s)", 60 | "stash_lowgrade": "Niste pooblaščeni za prevzem tega predmeta", 61 | "cannot_use": "Ne morem uporabiti %s", 62 | "shop_nostock": "Izdelka ni na zalogi", 63 | "identification": "Spol: %s \nDatum rojstva: %s", 64 | "search_dumpster": "Išči smetišče", 65 | "open_label": "Odpri %s", 66 | "purchased_for": "Kupljeno %s %s za %s%s", 67 | "unable_stack_items": "Teh elementov ne morete zložiti", 68 | "police_evidence": "Policijski dokazi", 69 | "open_police_evidence": "Odprti policijski dokazi", 70 | "open_stash": "Odpri shrambo", 71 | "locker_number": "Številka omarice", 72 | "locker_no_value": "Za odpiranje omarice mora vsebovati vrednost", 73 | "locker_must_number": "Omarica mora biti številka", 74 | "weapon_hand_required": "V roki morate imeti orožje", 75 | "weapon_hand_wrong": "Napačno orožje v roki", 76 | "open_player_inventory": "Odpri inventar igralca~", 77 | "open_secondary_inventory": "Odpri sekundarni inventar~", 78 | "disable_hotbar": "Prikaži vročo vrstico inventarja~", 79 | "reload_weapon": "Znova naloži orožje~", 80 | "use_hotbar": "Uporabi element vroče vrstice %s~", 81 | "no_durability": "Trajnost artikla je izčrpana", 82 | "cannot_give": "Cilju ni mogoče dati %s %s", 83 | "evidence_cannot_take": "Nisi dovolj veliki čin, da bi uzemal dokaze.", 84 | "dumpster": "Smetnjak" 85 | } 86 | -------------------------------------------------------------------------------- /locales/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui_use": "Использовать", 3 | "ui_give": "Передать", 4 | "ui_close": "Закрыть", 5 | "ui_drop": "Выкинуть", 6 | "ui_removeattachments": "Снять приспособления", 7 | "ui_copy": "Copy serial number", 8 | "ui_durability": "Прочность", 9 | "ui_ammo": "Патроны", 10 | "ui_serial": "Серийный номер", 11 | "ui_components": "Компоненты", 12 | "ui_tint": "Оттенок", 13 | "ui_usefulcontrols": "Полезные элементы управления", 14 | "ui_rmb": "Открыть контекстное меню предмета", 15 | "ui_ctrl_lmb": "Быстро переместить стопку предметов в другой инвентарь", 16 | "ui_shift_drag": "Разделить количество предметов пополам", 17 | "ui_ctrl_shift_lmb": "Быстро переместить половину стопки предметов в другой инвентарь", 18 | "ui_alt_lmb": "Быстрое использование предмета", 19 | "ui_ctrl_c": "When hovering over a weapon, copies it's serial number", 20 | "$": "$", 21 | "male": "Мужчина", 22 | "female": "Женщина", 23 | "used": "Б/У", 24 | "ui_removed": "Удалено", 25 | "ui_added": "Добавлено", 26 | "ui_holstered": "Спрятано", 27 | "ui_equipped": "Снаряжено", 28 | "using": "Используется: %s", 29 | "inventory_setup": "Инвентарь готов к использованию", 30 | "inventory_player_access": "Вы не можете открыть свой инвентарь прямо сейчас", 31 | "inventory_right_access": "Вы не можете открыть этот инвентарь", 32 | "inventory_lost_access": "Больше нет доступа к этому инвентарю", 33 | "wrong_ammo": "Вы не можете зарядить %s патронами %s", 34 | "weapon_license": "Лицензия на оружие", 35 | "already_have": "У вас уже есть лицензия на оружие", 36 | "have_purchased": "Вы купили лицензию на оружие", 37 | "can_not_afford": "Недостаточно средств для покупки лицензии на оружие", 38 | "purchase_license": "Купить лицензию", 39 | "interact_prompt": "Interact with [%s]", 40 | "weapon_unregistered": "%s ни на кого не зарегистрирован", 41 | "weapon_registered": "%s (%s) зарегистрирован на %s", 42 | "weapon_broken": "Это оружие сломано", 43 | "vehicle_locked": "Транспорт заблокирован", 44 | "nobody_nearby": "Поблизости никого нет", 45 | "give_amount": "Вы должны ввести количество", 46 | "buy_amount": "Вы должны ввести количество", 47 | "component_has": "На это оружие уже установлен %s", 48 | "component_invalid": "Это оружие несовместимо с %s", 49 | "cannot_perform": "Вы не можете совершить это действие", 50 | "cannot_carry": "Вы не можете столько унести", 51 | "cannot_carry_other": "Целевой инвентарь не может столько вместить", 52 | "cannot_carry_limit": "Вы не можете унести больше, чем %s %s", 53 | "cannot_carry_limit_other": "Цель не может унести больше, чем %s %s", 54 | "items_confiscated": "Ваши предметы были конфискованы", 55 | "items_returned": "Ваши предметы были возвращены", 56 | "item_unauthorised": "У вас нет прав на приобретение этого предмета", 57 | "item_unlicensed": "У вас нет лицензии на этот товар", 58 | "item_not_enough": "У вас недостаточно %s", 59 | "cannot_afford": "Недостаточно средств (не хватает %s)", 60 | "stash_lowgrade": "У вас нет прав взять этот предмет", 61 | "cannot_use": "Невозможно использовать %s", 62 | "shop_nostock": "Товара нет в наличии", 63 | "identification": "Пол: %s \nДата рождения: %s", 64 | "search_dumpster": "Обыскать мусорку", 65 | "open_label": "Открыть %s", 66 | "purchased_for": "Приобретено: %s %s за %s%s", 67 | "unable_stack_items": "Вы не можете складывать эти предметы", 68 | "police_evidence": "Улика", 69 | "open_police_evidence": "Открыть улики", 70 | "open_stash": "Открыть тайник", 71 | "locker_number": "Номер шкафчика", 72 | "locker_no_value": "Должен содержать значение, чтобы открыть шкафчик", 73 | "locker_must_number": "Введите номер шкафчика (число)", 74 | "weapon_hand_required": "В вашей руке должно быть оружие", 75 | "weapon_hand_wrong": "Неверное оружие в руке", 76 | "open_player_inventory": "Открыть инвентарь игрока~", 77 | "open_secondary_inventory": "Открыть вторичный инвентарь~", 78 | "disable_hotbar": "Отобразить горячую панель~", 79 | "reload_weapon": "Перезарядить оружие~", 80 | "use_hotbar": "Использовать предмет горячей панели %s~", 81 | "no_durability": "Исчерпана прочность предмета", 82 | "cannot_give": "Невозможно передать %s %s цели", 83 | "evidence_cannot_take": "Недостаточно высокое звание, чтобы забирать улики", 84 | "dumpster": "Мусорный контейнер" 85 | } 86 | -------------------------------------------------------------------------------- /locales/lt.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui_use": "Panaudoti", 3 | "ui_give": "Duoti", 4 | "ui_close": "Uždaryti", 5 | "ui_drop": "Išmesti", 6 | "ui_removeattachments": "Nuimti priedus", 7 | "ui_copy": "Kopijuoti serijos numerį", 8 | "ui_durability": "Patvarumas", 9 | "ui_ammo": "Kulkos", 10 | "ui_serial": "Serijos numeris", 11 | "ui_components": "Komponentai", 12 | "ui_tint": "Spalva", 13 | "ui_usefulcontrols": "Naudingi valdikliai", 14 | "ui_rmb": "Atidaryti daikto informacijos meniu", 15 | "ui_ctrl_lmb": "Greitai perkelti daiktus į kitą inventorių", 16 | "ui_shift_drag": "Paimti pusę daiktų", 17 | "ui_ctrl_shift_lmb": "Greitai perkelti pusę daiktų į kitą inventorių", 18 | "ui_alt_lmb": "Greituoju būdu panaudoti daiktą", 19 | "ui_ctrl_c": "Kol pelytė yra virš ginklo, nukopijuoja jo serijos numerį", 20 | "ui_remove_ammo": "Išimti kulkas", 21 | "ammo_type": "Kulkų tipas", 22 | "$": "$", 23 | "male": "Vyras", 24 | "female": "Moteris", 25 | "used": "Panaudota", 26 | "ui_removed": "Panaikinta", 27 | "ui_added": "Gauta", 28 | "ui_holstered": "Pasidėta", 29 | "ui_equipped": "Paimta", 30 | "using": "Naudojama %s", 31 | "inventory_setup": "Inventorius paruoštas naudoti", 32 | "inventory_player_access": "Šiuo metu negalite atidaryti savo inventoriaus", 33 | "inventory_right_access": "Šiuo metu negalite atidaryti inventoriaus", 34 | "inventory_lost_access": "Nebeturite prieigos atidaryti šį inventorių", 35 | "wrong_ammo": "Negalite užtaisyti %s su %s kulkomis", 36 | "license": "%s Licencija", 37 | "already_have": "Jau turite %s", 38 | "have_purchased": "Nusipirkote %s", 39 | "can_not_afford": "Nepakanka lėšų nusipirkti %s", 40 | "purchase_license": "Nusipirkti %s licenziją", 41 | "interact_prompt": "Sąveikauti su [%s]", 42 | "weapon_unregistered": "%s nėra niekam registruotas", 43 | "weapon_registered": "%s (%s) yra registruotas asmeniui %s", 44 | "weapon_broken": "Šis ginklas yra sulūžęs", 45 | "vehicle_locked": "Tr. priemonė užrakinta", 46 | "nobody_nearby": "Netoliese nieko nėra", 47 | "give_amount": "Reikia įvesti kiekį kurį norite duoti", 48 | "buy_amount": "Reikia įvesti kiekį kurį norite pirkti", 49 | "component_has": "Šis ginklas jau turi %s", 50 | "component_invalid": "Šiam ginklui netinka %s", 51 | "cannot_perform": "Negalite atlikti šio veiksmo", 52 | "cannot_carry": "Negalite tiek panešti...", 53 | "cannot_carry_other": "Kitam inventoriui tai yra perdidelis svoris", 54 | "cannot_carry_limit": "Negalite panešti daugiau nei %s %s", 55 | "cannot_carry_limit_other": "Kitas inventorius negali panešti daugiau nei %s %s", 56 | "items_confiscated": "Daiktai buvo konfiskuoti", 57 | "items_returned": "Daiktai buvo grąžinti", 58 | "item_unauthorised": "Negalite nusipirkti šio daikto", 59 | "item_unlicensed": "Neturite licenzijos šiam daiktui nusipirkti", 60 | "item_not_enough": "Neturite pakankamai daiktų %s", 61 | "cannot_afford": "Nepakanka lėšų šiam daiktui nusipirkti (trūksta %s)", 62 | "stash_lowgrade": "Negalite paimti šio daikto", 63 | "cannot_use": "Negalite panaudoti %s", 64 | "shop_nostock": "Šio daikto nebėra sandelyje", 65 | "identification": "Lytis: %s \nGimimo data: %s", 66 | "search_dumpster": "Apieškoti konteinerį", 67 | "open_label": "Atidaryti %s", 68 | "purchased_for": "Nusipirkote %s %s už %s%s", 69 | "unable_stack_items": "Negalite sudėti šių daiktų į krūvą", 70 | "police_evidence": "Policijos įkalčiai", 71 | "open_police_evidence": "Atidaryti policijos įkalčių sandėlį", 72 | "open_stash": "Atidaryti slėptuvę", 73 | "locker_number": "Spintelės numeris", 74 | "locker_no_value": "Turi būti skaitmuo norint atidaryti spintelę", 75 | "locker_must_number": "Spintelė turi būti skaičius", 76 | "weapon_hand_required": "Privalote rankoje turėti ginklą", 77 | "weapon_hand_wrong": "Neteisingas ginklas rankoje", 78 | "open_player_inventory": "Atidaryti žaidėjo inventorių~", 79 | "open_secondary_inventory": "Atidaryti antrinį inventorių~", 80 | "disable_hotbar": "Parodyti inventoriaus greito panaudojimo juostą~", 81 | "reload_weapon": "Įdėti naują apkabą~", 82 | "use_hotbar": "Naudoti greitojo panaudojimo juostos daiktą %s~", 83 | "no_durability": "%s nebepatvarus", 84 | "cannot_give": "Nepavyko paduoti %s %s", 85 | "evidence_cannot_take": "Negalite paimti įkalčių", 86 | "dumpster": "Konteineris", 87 | "crafting_item": "Gaminti %s", 88 | "crafting_bench": "Gamybos stalas", 89 | "open_crafting_bench": "Atidaryti Gamybos stalą", 90 | "not_enough_durability": "%s nėra pakankamai patvarus" 91 | } 92 | -------------------------------------------------------------------------------- /locales/pt-br.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui_use": "Usar", 3 | "ui_give": "Dar", 4 | "ui_close": "Fechar", 5 | "ui_drop": "Soltar", 6 | "ui_removeattachments": "Remover acessórios", 7 | "ui_copy": "Copiar número de série", 8 | "ui_durability": "Durabilidade", 9 | "ui_ammo": "Munição", 10 | "ui_serial": "Número de Série", 11 | "ui_components": "Componentes", 12 | "ui_tint": "Pintura", 13 | "ui_usefulcontrols": "Controles úteis", 14 | "ui_rmb": "Abrir o menu de contexto do item", 15 | "ui_ctrl_lmb": "Mova rapidamente uma pilha de itens para outro inventário", 16 | "ui_shift_drag": "Divida a quantidade do item pela metade", 17 | "ui_ctrl_shift_lmb": "Mova rapidamente meia pilha de itens para outro inventário", 18 | "ui_alt_lmb": "Uso rápido de um item", 19 | "ui_ctrl_c": "When hovering over a weapon, copies it's serial number", 20 | "$": "R$", 21 | "male": "Homem", 22 | "female": "Mulher", 23 | "used": "Usou", 24 | "ui_removed": "Removido", 25 | "ui_added": "Adicionado", 26 | "ui_holstered": "Guardado", 27 | "ui_equipped": "Equipado", 28 | "using": "Usando %s", 29 | "inventory_setup": "O inventário está pronto para usar", 30 | "inventory_player_access": "Você não pode abrir seu inventário agora", 31 | "inventory_right_access": "Você não pode abrir este inventário", 32 | "inventory_lost_access": "Não é mais possível acessar este inventário", 33 | "wrong_ammo": "Você não pode carregar a %s com %s balas", 34 | "weapon_license": "Licença de Porte de Armas", 35 | "already_have": "Você já tem uma Licença de porte de armas", 36 | "have_purchased": "Você comprou uma Licença de Porte de Armas", 37 | "can_not_afford": "Você não pode pagar por uma Licença de Porte de Armas", 38 | "purchase_license": "Comprar licença", 39 | "interact_prompt": "Interact with [%s]", 40 | "weapon_unregistered": "%s não está registrado para ninguém", 41 | "weapon_registered": "%s (%s) está registrada para %s", 42 | "weapon_broken": "Esta arma esta quebrada", 43 | "vehicle_locked": "Veículo está trancado", 44 | "nobody_nearby": "Não há ninguém por perto", 45 | "give_amount": "Você deve inserir um valor para dar", 46 | "buy_amount": "Você deve inserir um valor para comprar", 47 | "component_has": "Esta arma já tem um %s", 48 | "component_invalid": "Esta arma é incompatível com %s", 49 | "cannot_perform": "Você não pode realizar esta ação", 50 | "cannot_carry": "Você não pode carregar muito", 51 | "cannot_carry_other": "O inventário de destino não pode conter tanto", 52 | "cannot_carry_limit": "You cannot carry more than %s %s", 53 | "cannot_carry_limit_other": "Target cannot carry more than %s %s", 54 | "items_confiscated": "Seus itens foram confiscados", 55 | "items_returned": "Seus itens foram devolvidos", 56 | "item_unauthorised": "Você não está autorizado a comprar este item", 57 | "item_unlicensed": "Você não está licenciado para comprar este item", 58 | "item_not_enough": "Você não tem o suficiente de %s", 59 | "cannot_afford": "Você não pode pagar por isso (faltam %s)", 60 | "stash_lowgrade": "Você não está autorizado a pegar este item", 61 | "cannot_use": "Incapaz de usar %s", 62 | "shop_nostock": "Item fora de estoque", 63 | "identification": "Sexo: %s \nData de Nascimento: %s", 64 | "search_dumpster": "Procurar no Container", 65 | "open_label": "Abrir %s", 66 | "purchased_for": "Comprou %s %s por %s%s", 67 | "unable_stack_items": "Você não consegue empilhar esses itens", 68 | "police_evidence": "Provas Policiais", 69 | "open_police_evidence": "Open Police Evidence", 70 | "open_stash": "Open Stash", 71 | "locker_number": "Número do armário", 72 | "locker_no_value": "Deve conter um valor para abrir o armário", 73 | "locker_must_number": "O armário deve ser um número", 74 | "weapon_hand_required": "Você deve ter uma arma em mãos", 75 | "weapon_hand_wrong": "Arma errada em mãos", 76 | "open_player_inventory": "Abrir o inventário do jogador~", 77 | "open_secondary_inventory": "Abrir inventário secundário~", 78 | "disable_hotbar": "Exibir hotbar de inventário~", 79 | "reload_weapon": "Recarregar arma~", 80 | "use_hotbar": "Usar item da hotbar %s~", 81 | "no_durability": "A durabilidade do item está esgotada", 82 | "cannot_give": "Incapaz de dar %s %s ao alvo", 83 | "evidence_cannot_take": "Grau não alto o suficiente para tirar da evidência", 84 | "dumpster": "Lixeira" 85 | } 86 | -------------------------------------------------------------------------------- /locales/sr.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "ui_use": "Koristi", 4 | "ui_give": "Daj", 5 | "ui_close": "Zatvori", 6 | "ui_drop": "Baci", 7 | "ui_removeattachments": "Skini dodatke", 8 | "ui_copy": "Kopiraj serijski broj", 9 | "ui_durability": "Izdrzljivost", 10 | "ui_ammo": "Municija", 11 | "ui_serial": "Serijski broj", 12 | "ui_components": "Dodaci", 13 | "ui_tint": "Tinta", 14 | "ui_usefulcontrols": "Korisne kontrole", 15 | "ui_rmb": "Otvori kontekst meni", 16 | "ui_ctrl_lmb": "Brzo prebaci gomilu predmeta u drugi inventar", 17 | "ui_shift_drag": "Podeli broj predmeta na pola", 18 | "ui_ctrl_shift_lmb": "Brzo prebaci pola gomile predmeta u drugi inventar", 19 | "ui_alt_lmb": "Brzo iskoristi predmet", 20 | "ui_ctrl_c": "Dok prelazis misem preko oruzja, kopira njegov serijski broj", 21 | "ui_remove_ammo": "Izvadi municiju", 22 | "ammo_type": "Tip municije", 23 | "$": "$", 24 | "male": "Musko", 25 | "female": "Zensko", 26 | "used": "Iskorisceno", 27 | "ui_removed": "Uklonjeno", 28 | "ui_added": "Dodato", 29 | "ui_holstered": "Vraceno u futrolu", 30 | "ui_equipped": "Opremljeno", 31 | "using": "Koriscenje %s", 32 | "inventory_setup": "Inventar je spreman za koriscenje", 33 | "inventory_player_access": "Trenutno ne mozete otvoriti inventar", 34 | "inventory_right_access": "Ne mozete otvoriti taj inventar", 35 | "inventory_lost_access": "Nemate pristup vise tom inventaru", 36 | "wrong_ammo": "Ne mozes napuniti %s sa %s municijom", 37 | "license": "%s Dozvola", 38 | "already_have": "Vec imate %s", 39 | "have_purchased": "Vi ste kupili %s", 40 | "can_not_afford": "Nemate dovoljno novca da bi kupili %s", 41 | "purchase_license": "Kupi %s dozvolu", 42 | "interact_prompt": "Interakcija sa [%s]", 43 | "weapon_unregistered": "%s nije registrovano", 44 | "weapon_registered": "%s (%s) je registrovano na %s", 45 | "weapon_broken": "Oruzje je pokvareno", 46 | "vehicle_locked": "Vozilo je zakljucano", 47 | "nobody_nearby": "Nema nikoga u blizini", 48 | "give_amount": "Morate uneti kolicinu da bi dali", 49 | "buy_amount": "Morate uneti kolicinu da bi kupili", 50 | "component_has": "To oruzje vec ima %s", 51 | "component_invalid": "Oruzje nije kompatibilno sa %s", 52 | "component_slot_occupied": "%s slot je zauzet", 53 | "cannot_perform": "Ne mozete to uraditi", 54 | "cannot_carry": "Ne mozete toliko poneti", 55 | "cannot_carry_other": "Osoba ne moze toliko poneti", 56 | "cannot_carry_limit": "Ne mozete poneti vise od %s %s", 57 | "cannot_carry_limit_other": "Osoba ne moze poneti vise od %s %s", 58 | "items_confiscated": "Vasi predmeti su oduzeti", 59 | "items_returned": "Vasi predmeti su vraceni", 60 | "item_unauthorised": "Niste autorizovani da kupite taj predmet", 61 | "item_unlicensed": "Nemate dozvolu da kupite taj predmet", 62 | "item_not_enough": "Nemate dovoljno %s", 63 | "cannot_afford": "Nemate dovoljno novca (nedostaje %s)", 64 | "stash_lowgrade": "Niste autorizovani da koristite taj predmet", 65 | "cannot_use": "Nemoguce iskoristiti %s", 66 | "shop_nostock": "Nestalo je predmeta", 67 | "identification": "Pol: %s \nDatum rodjenja: %s", 68 | "search_dumpster": "Pretrazi Kontejner", 69 | "open_label": "Otvori %s", 70 | "purchased_for": "Kupljeno %s %s za %s%s", 71 | "unable_stack_items": "Ne mozete stakovati ove predmete", 72 | "police_evidence": "Policijski Dokazi", 73 | "open_police_evidence": "Otvori policijske dokaze", 74 | "open_stash": "Otvori Sef", 75 | "locker_number": "Broj Ormarica", 76 | "locker_no_value": "Morate uneti broj ormarica", 77 | "locker_must_number": "Ormaric mora biti broj", 78 | "weapon_hand_required": "Morate imati oruzje u ruci", 79 | "weapon_hand_wrong": "Imate pogresno oruzje u ruci", 80 | "open_player_inventory": "Otvori inventar igraca~", 81 | "open_secondary_inventory": "Otvori drugi inventar~", 82 | "disable_hotbar": "Prikazi hotbar~", 83 | "reload_weapon": "Napuni oruzje~", 84 | "use_hotbar": "Iskoristi predmet u hotbaru %s~", 85 | "no_durability": "%s izdrzljivost je na nuli", 86 | "cannot_give": "Nemoguce dati %s %s osobi", 87 | "evidence_cannot_take": "Niste autorizovani da uzmete dokaz", 88 | "dumpster": "Kontejner", 89 | "crafting_item": "Kraftovanje %s", 90 | "crafting_bench": "Sto za Kraftovanje", 91 | "open_crafting_bench": "Otvori sto za kraftovanje", 92 | "not_enough_durability": "%s nema dovoljno izdrzljivosti" 93 | } 94 | --------------------------------------------------------------------------------