├── README.md ├── client.lua ├── function └── utils.lua ├── fxmanifest.lua ├── positionTable.json ├── server.lua └── shared ├── config.lua └── language.lua /README.md: -------------------------------------------------------------------------------- 1 | # PX WEAPON CRAFTING 2 | Simple weapon crafting system with XP system and job control

3 |

px-development

4 | 5 | 6 | # **Frameworks** 7 | - ESX

8 | # **Features** 9 | - EnableDebug: A boolean flag indicating whether debugging is enabled for the crafting system
10 | - XpSystem: A boolean flag indicating activate the experience system for crafting weapons
11 | - ExperiancePerCraft: The amount of experience gained per craft in the crafting system
12 | - Weapon: A set of definitions for different weapons in the game, each with details such as weapon code, name, job requirements, required experience, an allowed job list, and items required for crafting. For example, the sniper rifle requires the police job, 10 experience points, and 2 iron and 5 copper for crafting
13 | - PositionCrafting: A list of locations in the game where you can craft, defined by coordinates and direction
14 | 15 | **🧷 Discord** 16 | [Pixel Scripts](https://discord.gg/KeZSH27fGe) 17 |
18 | **💻 Preview** 19 | [Video](https://www.youtube.com/watch?v=qLC1rGdf22g) 20 |
21 | **📖 Documentation** 22 | [View](https://app.gitbook.com/o/maLkzzf71CV0QY3XJcJC/s/ryJXOX2xIE06Mha4iYtD/~/changes/18/) 23 | -------------------------------------------------------------------------------- /client.lua: -------------------------------------------------------------------------------- 1 | RegisterNetEvent('esx:playerLoaded') 2 | AddEventHandler('esx:playerLoaded', function(xPlayer) 3 | ESX.PlayerData = xPlayer 4 | end) 5 | 6 | RegisterNetEvent('esx:setJob') 7 | AddEventHandler('esx:setJob', function(job) 8 | ESX.PlayerData.job = job 9 | end) 10 | 11 | AddEventHandler('esx:onPlayerSpawn', function() 12 | SpawnObject() 13 | end) 14 | 15 | local cam 16 | local inCam 17 | local objectPosition = {} 18 | 19 | exports.ox_target:addModel(Crafting.PropBench, { 20 | { 21 | name = 'open_crafting', 22 | event = '', 23 | icon = 'fa-solid fa-screwdriver-wrench', 24 | label = lang.target_label, 25 | onSelect = function(entity, distance, coords, name) 26 | local coords = GetEntityCoords(entity.entity) 27 | CamON(entity.entity, coords, GetEntityHeading(entity.entity)) 28 | end 29 | } 30 | }) 31 | 32 | local view 33 | 34 | RegisterNetEvent('px_crafting:showCrafting') 35 | AddEventHandler('px_crafting:showCrafting', function() 36 | ShowAllEntity() 37 | end) 38 | 39 | function ShowAllEntity() 40 | local data = lib.callback.await('px_crafting:getTablePosition', false) 41 | if type(data) == "table" then 42 | local options = {} 43 | debug(data) 44 | for _, v in pairs(data) do 45 | options[#options + 1] = { 46 | label = v.name, args = {coords = v.coords, name = v.name}, close = true 47 | } 48 | end 49 | lib.registerMenu({ 50 | id = 'some_menu_id', 51 | title = 'All Bench', 52 | position = 'top-right', 53 | options = options, 54 | onClose = function() 55 | view = false 56 | end, 57 | }, function(selected, scrollIndex, args) 58 | local coords = args.coords 59 | local name = args.name 60 | InfoEntity(coords, name) 61 | end) 62 | lib.showMenu('some_menu_id') 63 | end 64 | end 65 | 66 | function InfoEntity(coords, name) 67 | lib.registerMenu({ 68 | id = 'info_entity', 69 | title = 'Menu Actions', 70 | position = 'top-right', 71 | options = { 72 | {label = 'View', args = {coords = coords}, close = false}, 73 | {label = 'Delete', args = {coords = coords, name = name}, close = true} 74 | }, 75 | onClose = function() 76 | ShowAllEntity() 77 | view = false 78 | end, 79 | }, function(selected, scrollIndex, args) 80 | local coords = args.coords 81 | local name = args.name 82 | if selected == 1 then 83 | debug('View') 84 | view = false 85 | Wait(50) 86 | view = true 87 | CreateMarker(coords) 88 | elseif selected == 2 then 89 | view = false 90 | TriggerServerEvent('px_weapon_crafting:DeleteEntity', coords, name) 91 | for _, v in pairs(objectPosition) do 92 | debug(v) 93 | DeleteEntity(v) 94 | end 95 | Wait(100) 96 | SpawnObject() 97 | end 98 | end) 99 | lib.showMenu('info_entity') 100 | end 101 | 102 | function CreateMarker(coords) 103 | while view do 104 | Wait(0) 105 | DrawMarker(2, coords.x, coords.y, coords.z + 2, 0.0, 0.0, 0.0, 0.0, 180.0, 0.0, 1.0, 1.0, 1.0, 255, 128, 0, 50, true, true, 2, nil, nil, false) 106 | end 107 | end 108 | 109 | AddEventHandler('onResourceStart', function(resourceName) 110 | if GetCurrentResourceName() ~= resourceName then 111 | return 112 | end 113 | SpawnObject() 114 | end) 115 | 116 | function SpawnObject() 117 | local data = lib.callback.await('px_crafting:getTablePosition', false) 118 | if data ~= nil then 119 | for _, v in ipairs(data) do 120 | local heading = 0.0 + v.heading 121 | local createCrafting = CreateObject(Crafting.PropBench, v.coords.x, v.coords.y, v.coords.z, false, true, 122 | false) 123 | 124 | if createCrafting then 125 | SetEntityHeading(createCrafting, heading) 126 | SetEntityCollision(createCrafting, true, true) 127 | PlaceObjectOnGroundProperly(createCrafting) 128 | table.insert(objectPosition, createCrafting) 129 | else 130 | debug('Error during the creation of the crafting object.') 131 | end 132 | end 133 | end 134 | end 135 | 136 | AddEventHandler("onResourceStop", function(re) 137 | if re == GetCurrentResourceName() then 138 | for _, v in pairs(objectPosition) do 139 | debug(v) 140 | DeleteEntity(v) 141 | end 142 | end 143 | end) 144 | 145 | function OpenCrafting(coords, heading) 146 | local options = {} 147 | local PlayerJob = GetJobPlayer() 148 | local value = false 149 | 150 | debug(PlayerJob) 151 | 152 | for k, v in pairs(Crafting.Weapon) do 153 | debug(v) 154 | 155 | if v.requiredJob then 156 | for _, data in pairs(v.allowlistJob) do 157 | debug('For Job ' .. data) 158 | 159 | if data == PlayerJob then 160 | debug('Check') 161 | Wait(50) 162 | local option = { 163 | label = v.itemName, 164 | args = { value = k, code = v.itemCode }, 165 | close = true, 166 | icon = "fa-solid fa-gun", 167 | iconColor = '#0061A2' 168 | } 169 | 170 | table.insert(options, option) 171 | value = true 172 | end 173 | end 174 | else 175 | debug(v) 176 | Wait(50) 177 | local option = { 178 | label = v.itemName, 179 | args = { value = k, code = v.itemCode }, 180 | close = true, 181 | icon = "fa-solid fa-gun", 182 | iconColor = '#5C7CFA' 183 | } 184 | 185 | table.insert(options, option) 186 | value = true 187 | end 188 | end 189 | 190 | Wait(50) 191 | 192 | if value then 193 | lib.registerMenu({ 194 | id = 'ApriCrafting', 195 | title = lang.menu_title, 196 | position = 'top-left', 197 | options = options, 198 | onClose = function() 199 | CamOFF() 200 | end, 201 | }, function(selected, scrollIndex, args) 202 | local value = args.value 203 | local code = args.code 204 | local hash = GetHashKey(value) 205 | 206 | if selected then 207 | CreaArma(hash, coords, heading, code, value) 208 | end 209 | end) 210 | lib.showMenu('ApriCrafting') 211 | elseif not value then 212 | debug('You cannot craft weapons') 213 | lib.notify({ 214 | title = lang.notify_cannot_craft, 215 | description = '', 216 | type = 'error', 217 | position = 'top-center' 218 | }) 219 | Wait(500) 220 | CamOFF() 221 | end 222 | end 223 | 224 | local function GetIntFromBlob(b, s, o) 225 | local r = 0 226 | for i = 1, s, 1 do 227 | r = r | (string.byte(b, o + i) << (i - 1) * 8) 228 | end 229 | return r 230 | end 231 | 232 | function GetWeaponStats(weaponHash, none) 233 | local blob = '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' 234 | local retval = Citizen.InvokeNative(0xD92C739EE34C9EBA, weaponHash, blob, Citizen.ReturnResultAnyway()) 235 | local hudDamage = GetIntFromBlob(blob, 8, 0) 236 | local hudSpeed = GetIntFromBlob(blob, 8, 8) 237 | local hudCapacity = GetIntFromBlob(blob, 8, 16) 238 | local hudAccuracy = GetIntFromBlob(blob, 8, 24) 239 | local hudRange = GetIntFromBlob(blob, 8, 32) 240 | return retval, hudDamage, hudSpeed, hudCapacity, hudAccuracy, hudRange 241 | end 242 | 243 | function CreaArma(hash, coords, d, code, value) 244 | local modelHash = hash 245 | inCam = true 246 | DeleteObject(obj) 247 | local s = d - 180 248 | local heading = s 249 | obj = CreateObject(modelHash, coords.x, coords.y, coords.z + 1.1, true, false, true) 250 | SetEntityHeading(obj, heading) 251 | OpenMenuWeapon(code, value) 252 | Citizen.CreateThread(function() 253 | while inCam do 254 | Wait(0) 255 | DisableControlAction(0, 34, true) -- A 256 | DisableControlAction(0, 30, true) -- D 257 | InfoCrafting() 258 | if IsControlPressed(0, 9) then -- Right 259 | heading = heading + 5 260 | SetEntityHeading(obj, heading) 261 | elseif IsControlPressed(0, 63) then -- Left 262 | heading = heading - 5 263 | SetEntityHeading(obj, heading) 264 | end 265 | end 266 | end) 267 | end 268 | 269 | function OpenMenuWeapon(hash, value) 270 | local options = {} 271 | debug("Code " .. hash) 272 | for k, v in pairs(Crafting.Weapon) do 273 | -- debug(v) 274 | if v.itemCode == hash then 275 | if v.weapon then 276 | options = { 277 | { label = lang.menu2_options_1, close = true, icon = "fa-solid fa-hammer" }, 278 | { label = lang.menu2_options_2, close = true, icon = "fa-solid fa-circle-info" } 279 | } 280 | else 281 | options = { 282 | { label = lang.menu2_options_1, close = true, icon = "fa-solid fa-hammer" } 283 | } 284 | end 285 | end 286 | end 287 | Wait(100) 288 | lib.registerMenu({ 289 | id = 'OpenMenuInfo', 290 | title = lang.menu2_title, 291 | position = 'top-left', 292 | options = options, 293 | onClose = function() 294 | DeleteObject(obj) 295 | lib.showMenu('ApriCrafting') 296 | end, 297 | }, function(selected, scrollIndex, args) 298 | if selected == 1 then 299 | OpenMenuCraft(hash, value) 300 | elseif selected == 2 then 301 | OpenMenuInfo(hash, value) 302 | end 303 | end) 304 | lib.showMenu('OpenMenuInfo') 305 | end 306 | 307 | function OpenMenuCraft(hash, value) 308 | local options = {} 309 | local craftingInfo = {} 310 | for k, v in pairs(Crafting.Weapon) do 311 | if k == value then 312 | itemName = v.itemCode 313 | xp = v.requiredXp 314 | time = v.requiredTime 315 | if Crafting.XpSystem then 316 | options[#options + 1] = { 317 | label = lang.menu3_options_3 .. " " .. xp, 318 | close = false, 319 | icon = "fa-solid fa-arrow-up-wide-short" 320 | } 321 | for j, l in pairs(v.ItemRequired) do 322 | debug("Item Name: " .. l.itemName .. " Item Quantity: " .. l.quantity) 323 | craftingInfo[#craftingInfo + 1] = { 324 | itemName = l.itemName, 325 | quantity = l.quantity, 326 | } 327 | options[#options + 1] = { 328 | label = lang.menu3_options_1 .. l.label .. " x " .. l.quantity, 329 | close = false, 330 | icon = "fa-solid fa-clipboard" 331 | } 332 | end 333 | else 334 | for j, l in pairs(v.ItemRequired) do 335 | debug("Item Name: " .. l.itemName .. " Item Quantity: " .. l.quantity) 336 | craftingInfo[#craftingInfo + 1] = { 337 | itemName = l.itemName, 338 | quantity = l.quantity, 339 | } 340 | options[#options + 1] = { 341 | label = lang.menu3_options_1 .. l.label .. " x " .. l.quantity, 342 | close = false, 343 | icon = "fa-solid fa-clipboard" 344 | } 345 | end 346 | end 347 | end 348 | end 349 | options[#options + 1] = { 350 | label = lang.menu3_options_2, 351 | close = true, 352 | args = { craftingInfo = craftingInfo }, 353 | icon = "fa-solid fa-hammer" 354 | } 355 | Wait(50) 356 | lib.registerMenu({ 357 | id = 'OpenMenuRealCraft', 358 | title = lang.menu3_title, 359 | position = 'top-left', 360 | options = options, 361 | onClose = function() 362 | OpenMenuWeapon(hash, value) 363 | end, 364 | }, function(selected, scrollIndex, args) 365 | local totalOptions = #options 366 | if selected == totalOptions then 367 | local data = lib.callback.await('px_crafting:getItemCount', false, args.craftingInfo) 368 | local item = args.craftingInfo 369 | debug('Start check item and xp...') 370 | debug(data) 371 | if data.value then 372 | if Crafting.XpSystem then 373 | if data.xp >= xp then 374 | debug('Start Crafting') 375 | if lib.progressBar({ 376 | duration = time, 377 | label = 'Creating', 378 | useWhileDead = false, 379 | canCancel = true, 380 | disable = { 381 | car = true, 382 | }, 383 | anim = { 384 | dict = 'mini@repair', 385 | clip = 'fixing_a_ped' 386 | }, 387 | }) then 388 | CamOFF() 389 | TriggerServerEvent('px_crafting:removeItem', item, itemName) 390 | end 391 | else 392 | debug('You don\'t have enough experience points') 393 | lib.notify({ 394 | title = lang.notify_enough_xp, 395 | description = '', 396 | type = 'error', 397 | position = 'top-center' 398 | }) 399 | CamOFF() 400 | end 401 | else 402 | debug('Start Crafting') 403 | if lib.progressBar({ 404 | duration = time, 405 | label = 'Creating', 406 | useWhileDead = false, 407 | canCancel = true, 408 | disable = { 409 | car = true, 410 | }, 411 | anim = { 412 | dict = 'mini@repair', 413 | clip = 'fixing_a_ped' 414 | }, 415 | }) then 416 | CamOFF() 417 | TriggerServerEvent('px_crafting:removeItem', item, itemName) 418 | end 419 | end 420 | else 421 | CamOFF() 422 | end 423 | end 424 | end) 425 | lib.showMenu('OpenMenuRealCraft') 426 | end 427 | 428 | function OpenMenuInfo(hash, value) 429 | local job = GetJobPlayer() 430 | debug("Player Job " .. job) 431 | local options = {} 432 | local _, hudDamage, hudSpeed, hudCapacity, hudAccuracy, hudRange = GetWeaponStats(GetHashKey(hash)) 433 | debug(_, hudDamage, hudSpeed, hudCapacity, hudAccuracy, hudRange) 434 | options = { 435 | { label = lang.menu4_options_1 .. ' (' .. hudDamage .. '%)', progress = hudDamage, colorScheme = '#0061A2', close = false }, 436 | { label = lang.menu4_options_2 .. ' (' .. hudSpeed .. '%)', progress = hudSpeed, colorScheme = '#0061A2', close = false }, 437 | { label = lang.menu4_options_3 .. ' (' .. hudCapacity .. '%)', progress = hudCapacity, colorScheme = '#0061A2', close = false }, 438 | { label = lang.menu4_options_4 .. ' (' .. hudAccuracy .. '%)', progress = hudAccuracy, colorScheme = '#0061A2', close = false }, 439 | { label = lang.menu4_options_5 .. ' (' .. hudRange .. '%)', progress = hudRange, colorScheme = '#0061A2', close = false } 440 | } 441 | Wait(100) 442 | lib.registerMenu({ 443 | id = 'OpenMenuInfo', 444 | title = lang.menu4_title, 445 | position = 'top-left', 446 | options = options, 447 | onClose = function() 448 | OpenMenuWeapon(hash, value) 449 | end, 450 | }, function(selected, scrollIndex, args) 451 | end) 452 | lib.showMenu('OpenMenuInfo') 453 | end 454 | 455 | function CamON(obj, coordsArma, heading) 456 | local coords = GetOffsetFromEntityInWorldCoords(obj, 0, -0.75, 0) 457 | RenderScriptCams(false, false, 0, 1, 0) 458 | DestroyCam(cam, false) 459 | FreezeEntityPosition(cache.ped, true) 460 | 461 | if not DoesCamExist(cam) then 462 | cam = CreateCam("DEFAULT_SCRIPTED_CAMERA", true) 463 | SetCamActive(cam, true) 464 | RenderScriptCams(true, true, 250, 1, 0) 465 | SetCamCoord(cam, coords.x, coords.y, coords.z + 1.2) 466 | SetCamRot(cam, 0.0, 0.0, GetEntityHeading(obj)) 467 | OpenCrafting(coordsArma, heading) 468 | else 469 | CamOFF() 470 | Wait(500) 471 | CamON() 472 | end 473 | end 474 | 475 | function InfoCrafting() 476 | Scale = RequestScaleformMovie("INSTRUCTIONAL_BUTTONS"); 477 | while not HasScaleformMovieLoaded(Scale) do 478 | Citizen.Wait(0) 479 | end 480 | 481 | BeginScaleformMovieMethod(Scale, "CLEAR_ALL"); 482 | EndScaleformMovieMethod(); 483 | 484 | --Destra 485 | BeginScaleformMovieMethod(Scale, "SET_DATA_SLOT"); 486 | ScaleformMovieMethodAddParamInt(0); 487 | PushScaleformMovieMethodParameterString("~INPUT_MOVE_RIGHT_ONLY~"); 488 | PushScaleformMovieMethodParameterString("Rotate right"); 489 | EndScaleformMovieMethod(); 490 | 491 | --Sinistra 492 | BeginScaleformMovieMethod(Scale, "SET_DATA_SLOT"); 493 | ScaleformMovieMethodAddParamInt(1); 494 | PushScaleformMovieMethodParameterString("~INPUT_MOVE_LEFT_ONLY~"); 495 | PushScaleformMovieMethodParameterString("Rotate Left"); 496 | EndScaleformMovieMethod(); 497 | 498 | BeginScaleformMovieMethod(Scale, "SET_DATA_SLOT"); 499 | ScaleformMovieMethodAddParamInt(2); 500 | PushScaleformMovieMethodParameterString("~INPUT_CELLPHONE_CANCEL~"); 501 | PushScaleformMovieMethodParameterString("Exit"); 502 | EndScaleformMovieMethod(); 503 | 504 | 505 | BeginScaleformMovieMethod(Scale, "DRAW_INSTRUCTIONAL_BUTTONS"); 506 | ScaleformMovieMethodAddParamInt(0); 507 | EndScaleformMovieMethod(); 508 | 509 | DrawScaleformMovieFullscreen(Scale, 255, 255, 255, 255, 0); 510 | end 511 | 512 | function InfoPlaceCrafting() 513 | Scale = RequestScaleformMovie("INSTRUCTIONAL_BUTTONS"); 514 | while not HasScaleformMovieLoaded(Scale) do 515 | Citizen.Wait(0) 516 | end 517 | 518 | BeginScaleformMovieMethod(Scale, "CLEAR_ALL"); 519 | EndScaleformMovieMethod(); 520 | 521 | --Destra 522 | BeginScaleformMovieMethod(Scale, "SET_DATA_SLOT"); 523 | ScaleformMovieMethodAddParamInt(0); 524 | PushScaleformMovieMethodParameterString("~INPUT_PICKUP~"); 525 | PushScaleformMovieMethodParameterString("Place Prop"); 526 | EndScaleformMovieMethod(); 527 | 528 | --Rotate Left 529 | BeginScaleformMovieMethod(Scale, "SET_DATA_SLOT"); 530 | ScaleformMovieMethodAddParamInt(1); 531 | PushScaleformMovieMethodParameterString("~INPUT_WEAPON_WHEEL_PREV~"); 532 | PushScaleformMovieMethodParameterString("Rotate Right"); 533 | EndScaleformMovieMethod(); 534 | 535 | --Rotate Right 536 | BeginScaleformMovieMethod(Scale, "SET_DATA_SLOT"); 537 | ScaleformMovieMethodAddParamInt(2); 538 | PushScaleformMovieMethodParameterString("~INPUT_WEAPON_WHEEL_NEXT~"); 539 | PushScaleformMovieMethodParameterString("Rotate Left"); 540 | EndScaleformMovieMethod(); 541 | 542 | 543 | BeginScaleformMovieMethod(Scale, "DRAW_INSTRUCTIONAL_BUTTONS"); 544 | ScaleformMovieMethodAddParamInt(0); 545 | EndScaleformMovieMethod(); 546 | 547 | DrawScaleformMovieFullscreen(Scale, 255, 255, 255, 255, 0); 548 | end 549 | 550 | function CamOFF() 551 | -- lib.hideMenu('ApriCrafting') 552 | FreezeEntityPosition(PlayerPedId(), false) 553 | DeleteObject(obj) 554 | RenderScriptCams(false, true, 250, 1, 0) 555 | DestroyCam(cam, false) 556 | -- SetLocalPlayerAsGhost(false) 557 | inCam = false 558 | end 559 | 560 | local confirmed 561 | local heading 562 | 563 | function RotationToDirection(rotation) 564 | local adjustedRotation = 565 | { 566 | x = (math.pi / 180) * rotation.x, 567 | y = (math.pi / 180) * rotation.y, 568 | z = (math.pi / 180) * rotation.z 569 | } 570 | local direction = 571 | { 572 | x = -math.sin(adjustedRotation.z) * math.abs(math.cos(adjustedRotation.x)), 573 | y = math.cos(adjustedRotation.z) * math.abs(math.cos(adjustedRotation.x)), 574 | z = math.sin(adjustedRotation.x) 575 | } 576 | return direction 577 | end 578 | 579 | function DrawPropAxes(prop) 580 | local propForward, propRight, propUp, propCoords = GetEntityMatrix(prop) 581 | 582 | local propXAxisEnd = propCoords + propRight * 1.0 583 | local propYAxisEnd = propCoords + propForward * 1.0 584 | local propZAxisEnd = propCoords + propUp * 1.0 585 | 586 | DrawLine(propCoords.x, propCoords.y, propCoords.z + 0.1, propXAxisEnd.x, propXAxisEnd.y, propXAxisEnd.z, 255, 0, 0, 587 | 255) 588 | DrawLine(propCoords.x, propCoords.y, propCoords.z + 0.1, propYAxisEnd.x, propYAxisEnd.y, propYAxisEnd.z, 0, 255, 0, 589 | 255) 590 | DrawLine(propCoords.x, propCoords.y, propCoords.z + 0.1, propZAxisEnd.x, propZAxisEnd.y, propZAxisEnd.z, 0, 0, 255, 591 | 255) 592 | end 593 | 594 | function RayCastGamePlayCamera(distance) 595 | local cameraRotation = GetGameplayCamRot() 596 | local cameraCoord = GetGameplayCamCoord() 597 | local direction = RotationToDirection(cameraRotation) 598 | local destination = 599 | { 600 | x = cameraCoord.x + direction.x * distance, 601 | y = cameraCoord.y + direction.y * distance, 602 | z = cameraCoord.z + direction.z * distance 603 | } 604 | local a, b, c, d, e = GetShapeTestResult(StartShapeTestRay(cameraCoord.x, cameraCoord.y, cameraCoord.z, destination 605 | .x, destination.y, destination.z, -1, PlayerPedId(), 0)) 606 | return b, c, e 607 | end 608 | 609 | RegisterNetEvent('px_crafting:placeProp') 610 | AddEventHandler('px_crafting:placeProp', function(prop) 611 | debug('Hola') 612 | prop = joaat(prop) 613 | heading = 0.0 614 | confirmed = false 615 | 616 | RequestModel(prop) 617 | while not HasModelLoaded(prop) do 618 | Wait(0) 619 | end 620 | 621 | local hit, coords 622 | 623 | while not hit do 624 | hit, coords = RayCastGamePlayCamera(10.0) 625 | Wait(0) 626 | end 627 | 628 | local propObject = CreateObject(prop, coords.x, coords.y, coords.z, true, false, true) 629 | 630 | CreateThread(function() 631 | while not confirmed do 632 | InfoPlaceCrafting() 633 | hit, coords, entity = RayCastGamePlayCamera(10.0) 634 | DisableControlAction(0, 24, true) 635 | DisableControlAction(0, 25, true) 636 | SetEntityCoordsNoOffset(propObject, coords.x, coords.y, coords.z, false, false, false, true) 637 | FreezeEntityPosition(propObject, true) 638 | SetEntityCollision(propObject, false, false) 639 | SetEntityAlpha(propObject, 100, false) 640 | debug(heading) 641 | DrawPropAxes(propObject) 642 | Wait(0) 643 | 644 | if IsControlPressed(0, 15) then -- Left 645 | heading = heading + 5.0 646 | elseif IsControlPressed(0, 14) then -- Right 647 | heading = heading - 5.0 648 | end 649 | 650 | if IsControlJustPressed(0, 177) then 651 | DeleteObject(propObject) 652 | confirmed = true 653 | end 654 | 655 | if heading > 360.0 then 656 | heading = 0.0 657 | elseif heading < 0.0 then 658 | heading = 360.0 659 | end 660 | 661 | SetEntityHeading(propObject, heading) 662 | 663 | if IsControlJustPressed(0, 38) then -- "E" 664 | local input = lib.inputDialog('Table Name', { '' }) 665 | 666 | if not input then 667 | DeleteObject(propObject) 668 | return 669 | else 670 | confirmed = true 671 | SetEntityAlpha(propObject, 255, false) 672 | SetEntityCollision(propObject, true, true) 673 | TriggerServerEvent('px_crafting:SaveTable', input[1], coords.x, coords.y, coords.z, heading) 674 | table.insert(objectPosition, propObject) 675 | end 676 | end 677 | end 678 | end) 679 | end) 680 | -------------------------------------------------------------------------------- /function/utils.lua: -------------------------------------------------------------------------------- 1 | function debug(...) 2 | if Crafting.EnableDebug then 3 | local args = { ... } 4 | 5 | for i = 1, #args do 6 | local arg = args[i] 7 | args[i] = type(arg) == 'table' and json.encode(arg, { sort_keys = true, indent = true }) or tostring(arg) 8 | end 9 | 10 | print('^1[DEBUG] ^7', table.concat(args, '\t')) 11 | end 12 | end 13 | 14 | function GetJobPlayer() 15 | return ESX.PlayerData.job.name 16 | end 17 | 18 | function GetPlayerXp() 19 | local src = source 20 | local xPlayer = ESX.GetPlayerFromId(src) 21 | local player_xp = MySQL.scalar.await('SELECT `crafting_level` FROM `users` WHERE `identifier` = ?', { 22 | xPlayer.identifier 23 | }) 24 | return player_xp 25 | end -------------------------------------------------------------------------------- /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'adamant' 2 | game 'gta5' 3 | lua54 'yes' 4 | 5 | author 'Haze' 6 | version '1.0.0' 7 | 8 | shared_scripts{ 9 | '@es_extended/imports.lua', 10 | '@ox_lib/init.lua', 11 | 'shared/*.lua', 12 | 'function/utils.lua' 13 | } 14 | 15 | client_scripts{ 16 | 'client.lua' 17 | } 18 | 19 | server_scripts{ 20 | '@oxmysql/lib/MySQL.lua', 21 | 'server.lua' 22 | } -------------------------------------------------------------------------------- /positionTable.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pixel-Scripts/px_weapon-crafting/262101c2ee52661fad43783c98d557d26524eaea/positionTable.json -------------------------------------------------------------------------------- /server.lua: -------------------------------------------------------------------------------- 1 | lib.callback.register('px_crafting:getItemCount', function(source, item) 2 | local src = source 3 | local xPlayer = ESX.GetPlayerFromId(src) 4 | local hasEnoughItems = true 5 | for k, v in pairs(item) do 6 | local c = exports.ox_inventory:GetItem(src, v.itemName, nil, false) 7 | debug(c.label .. " " .. c.count) 8 | 9 | if c.count < v.quantity then 10 | hasEnoughItems = false 11 | TriggerClientEvent('ox_lib:notify', source, { 12 | type = 'error', 13 | title = 'You do not have: ' .. c.label.." Required "..v.quantity, 14 | position = 'top', 15 | description = '', 16 | 5000 17 | }) 18 | end 19 | end 20 | 21 | if hasEnoughItems then 22 | local info = {} 23 | info.value = true 24 | info.xp = GetPlayerXp() 25 | debug(info) 26 | return info 27 | else 28 | local info = {} 29 | info.value = false 30 | return info 31 | end 32 | end) 33 | 34 | RegisterNetEvent('px_crafting:removeItem') 35 | AddEventHandler('px_crafting:removeItem', function(item, weapon) 36 | for k,v in pairs(item) do 37 | exports.ox_inventory:RemoveItem(source, v.itemName, v.quantity, nil, nil) 38 | end 39 | if Crafting.XpSystem then 40 | exports.ox_inventory:AddItem(source, weapon, 1, nil, nil) 41 | exports["px_weapon-crafting"]:addPlayerXp(source, Crafting.ExperiancePerCraft) 42 | else 43 | exports.ox_inventory:AddItem(source, weapon, 1, nil, nil) 44 | end 45 | end) 46 | 47 | RegisterNetEvent('px_crafting:SaveTable') 48 | AddEventHandler('px_crafting:SaveTable', function(name, coordsx, coordsy, coordsz, heading) 49 | local loadFile= LoadResourceFile(GetCurrentResourceName(), "./positionTable.json") 50 | if loadFile ~= nil then 51 | local extract = json.decode(loadFile) 52 | if type(extract) == "table" then 53 | debug(extract) 54 | table.insert(extract, {name = name, coords = vector3(coordsx, coordsy, coordsz), heading = heading}) 55 | SaveResourceFile(GetCurrentResourceName(), "positionTable.json", json.encode(extract, { indent = true }), -1) 56 | else 57 | local Table = {} 58 | table.insert(Table, {name = name, coords = vector3(coordsx, coordsy, coordsz), heading = heading}) 59 | SaveResourceFile(GetCurrentResourceName(), "positionTable.json", json.encode(Table, { indent = true }), -1) 60 | end 61 | end 62 | end) 63 | 64 | RegisterNetEvent('px_weapon_crafting:DeleteEntity') 65 | AddEventHandler('px_weapon_crafting:DeleteEntity', function(coords, name) 66 | local loadFile = LoadResourceFile(GetCurrentResourceName(), "./positionTable.json ") 67 | if loadFile ~= nil then 68 | local extract = json.decode(loadFile) 69 | if type(extract) == "table" then 70 | for k,v in ipairs(extract) do 71 | if v.name == name then 72 | debug(v.coords) 73 | debug(k) 74 | table.remove(extract, k) 75 | SaveResourceFile(GetCurrentResourceName(), "positionTable.json", json.encode(extract, { indent = true }), -1) 76 | end 77 | end 78 | end 79 | end 80 | end) 81 | 82 | lib.callback.register('px_crafting:getTablePosition', function(source) 83 | local loadFile= LoadResourceFile(GetCurrentResourceName(), "./positionTable.json") 84 | if loadFile ~= nil then 85 | local extract = json.decode(loadFile) 86 | return extract 87 | end 88 | end) 89 | 90 | RegisterCommand(Crafting.CommandGive, function(source, args, rawCommand) 91 | local xTarget = ESX.GetPlayerFromId(tonumber(args[1])) 92 | if args[1] ~= nil then 93 | if args[2] ~= nil then 94 | if xTarget ~= nil then 95 | exports["px_weapon-crafting"]:addPlayerXp(xTarget.source, args[2]) 96 | else 97 | debug('User not found') 98 | end 99 | end 100 | end 101 | end, false) 102 | 103 | RegisterCommand(Crafting.Command, function(source, args, rawCommand) 104 | local xPlayer = ESX.GetPlayerFromId(source) 105 | for k, v in pairs(Crafting.PermissionCommand) do 106 | if v == xPlayer.getGroup() then 107 | debug(v) 108 | TriggerClientEvent('px_crafting:placeProp', source, Crafting.PropBench) 109 | return 110 | end 111 | end 112 | end) 113 | 114 | RegisterCommand(Crafting.CommandShow, function(source, args, rawCommand) 115 | local xPlayer = ESX.GetPlayerFromId(source) 116 | for k, v in pairs(Crafting.PermissionCommand) do 117 | if v == xPlayer.getGroup() then 118 | debug(v) 119 | TriggerClientEvent('px_crafting:showCrafting', source) 120 | return 121 | end 122 | end 123 | end) 124 | 125 | exports("addPlayerXp", function(source, xp) 126 | local xPlayer = ESX.GetPlayerFromId(source) 127 | local player_xp = MySQL.scalar.await('SELECT `crafting_level` FROM `users` WHERE `identifier` = ?', { 128 | xPlayer.identifier 129 | }) 130 | local givexp = player_xp + tonumber(xp) 131 | local affectedRows = MySQL.update.await('UPDATE users SET `crafting_level` = ? WHERE identifier = ?', { 132 | givexp, xPlayer.identifier 133 | }) 134 | 135 | TriggerClientEvent('ox_lib:notify', source, { 136 | type = 'success', 137 | title = lang.notify_earned_xp.." "..Crafting.ExperiancePerCraft.."xp", 138 | position = 'top', 139 | description = '', 140 | 5000 141 | }) 142 | end) 143 | -------------------------------------------------------------------------------- /shared/config.lua: -------------------------------------------------------------------------------- 1 | Crafting = {} 2 | 3 | Crafting = { 4 | Command = 'createCrafting', 5 | CommandShow = 'showCrafting', 6 | CommandGive = 'givecraftingxp', 7 | PermissionCommand = {'admin'}, 8 | PropBench = 'gr_prop_gr_bench_02b', 9 | EnableDebug = true, 10 | XpSystem = true, 11 | ExperiancePerCraft = 2.5, 12 | Weapon = { 13 | ["prop_ld_ammo_pack_01"] = { 14 | itemCode = 'ammo-9', 15 | itemName = 'Ammo 9mm', 16 | requiredJob = false, 17 | requiredXp = 10, 18 | requiredTime = 1000, 19 | weapon = false, 20 | allowlistJob = { 21 | "police" 22 | }, 23 | ItemRequired = { 24 | {label = 'Phone', itemName = "phone", quantity = 2}, 25 | {label = 'Burger', itemName = "burger", quantity = 5} 26 | } 27 | }, 28 | ["w_pi_pistol"] = { 29 | itemCode = 'WEAPON_PISTOL', 30 | itemName = 'Pistol', 31 | requiredJob = false, 32 | requiredXp = 0, 33 | requiredTime = 2000, 34 | weapon = true, 35 | allowlistJob = { 36 | "police" 37 | }, 38 | ItemRequired = { 39 | {label = 'Radio', itemName = "radio", quantity = 1} 40 | } 41 | }, 42 | ["w_ar_carbinerifle"] = { 43 | itemCode = 'WEAPON_CARBINERIFLE', 44 | itemName = 'Carabine Rifle', 45 | requiredJob = false, 46 | requiredXp = 1, 47 | requiredTime = 10000, 48 | weapon = true, 49 | allowlistJob = { 50 | "police", 51 | }, 52 | ItemRequired = { 53 | {label = 'Water', itemName = "water", quantity = 1} 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /shared/language.lua: -------------------------------------------------------------------------------- 1 | lang = { 2 | target_label = 'Open Creation', 3 | menu_title = 'Workbench', 4 | menu2_title = 'Weapon Information Menu', 5 | menu2_options_1 = 'Create Weapon', 6 | menu2_options_2 = 'View Weapon Information', 7 | 8 | menu3_title = 'Weapon Information Menu', 9 | menu3_options_1 = 'Name: ', 10 | menu3_options_2 = 'Build', 11 | menu3_options_3 = 'Required XP: ', 12 | 13 | menu4_title = 'Weapon Information Menu', 14 | menu4_options_1 = 'Damage', 15 | menu4_options_2 = 'Speed', 16 | menu4_options_3 = 'Capacity', 17 | menu4_options_4 = 'Accuracy', 18 | menu4_options_5 = 'Range', 19 | 20 | --Notifications 21 | notify_enough_xp = 'You do not have enough experience', 22 | notify_copy_coords = 'You have copied the coordinates', 23 | notify_earned_xp = 'You have earned ', 24 | notify_cannot_craft = 'You cannot craft weapons' 25 | } 26 | --------------------------------------------------------------------------------