local POSITION_CLAMP = 0.2 local DEFAULT_SPEED = 0.001 local FAST_SPEED = 0.01 DEFAULT_BONE = 24816 Sling = { isPreset = false, cachedPositions = {}, cachedPresets = {}, cachedWeapons = {}, cachedAttachments = {}, currentAttachedAmount = 0, inPositioning = false, data = { object = nil, } } local otherPlayersWeapons = {} function Sling:SyncWeaponAttachment(weaponName, weaponVal, coords, action) local weaponData = { weaponName = weaponName, weaponVal = weaponVal, coords = coords } TriggerServerEvent('force-sling:server:syncWeapons', weaponData, action) end function Sling:InitMain() Debug("info", "Initializing main thread") Sling:InitSling() Sling:InitCommands() Debug("info", "Main thread initialized") end function Sling:InitSling() local libCallbackAwait = lib.callback.await Sling.cachedPositions = libCallbackAwait("force-sling:callback:getCachedPositions", false) Sling.cachedPresets = libCallbackAwait("force-sling:callback:getCachedPresets", false) Sling.cachedWeapons = Inventory:GetWeapons() Sling:WeaponThread() local function loadBoneOptions() local bones = {} for boneName, _ in pairs(Config.Bones) do table.insert(bones, boneName) end return bones end local function loadWeaponOptions() local weapons = {} for weaponName, _ in pairs(Config.Weapons) do table.insert(weapons, weaponName) end return weapons end local bones = loadBoneOptions() local weapons = loadWeaponOptions() local selectData = { boneId = DEFAULT_BONE, weapon = `w_pi_pistol50`, weaponName = "weapon_pistol50" } lib.registerMenu({ id = 'sling_select', title = locale("slingConfig"), position = 'top-right', onSideScroll = function(selected, scrollIndex, args) if selected == 1 then selectData.boneId = Config.Bones[args[scrollIndex]] elseif selected == 2 then local weapon = Config.Weapons[args[scrollIndex]] selectData.weapon = weapon.model selectData.weaponName = args[scrollIndex] end end, onSelected = function(selected, secondary, args) end, onClose = function(keyPressed) Sling.inPositioning = false end, options = { { label = 'Bone', values = bones, args = bones }, { label = 'Weapon', values = weapons, args = weapons }, { label = 'Continue' }, } }, function(selected, scrollIndex, args) Debug("info", "Selected weapon: " .. selectData.weapon) Debug("info", "Selected bone: " .. selectData.boneId) Sling:StartPositioning(selectData) end) end function Sling:WeaponThread() local function handleWeaponAttachment(weaponName, weaponVal, playerPed, weapon) if not Sling.cachedAttachments[weaponName] then Sling.cachedAttachments[weaponName] = {} end -- Check if player is in vehicle local isInVehicle = IsPedInAnyVehicle(playerPed, false) if weapon == weaponVal.name or isInVehicle then if DoesEntityExist(Sling.cachedAttachments[weaponName].obj) then Utils:DeleteWeapon(weaponName) end else if not DoesEntityExist(Sling.cachedAttachments[weaponName].obj) then local coords = Sling.cachedPositions[weaponName] or Sling.cachedPresets[weaponName] or { coords = { x = 0.0, y = -0.15, z = 0.0 }, rot = { x = 0.0, y = 0.0, z = 0.0 }, boneId = DEFAULT_BONE } Utils:CreateAndAttachWeapon(weaponName, weaponVal, coords, playerPed) else if not IsEntityAttachedToAnyPed(Sling.cachedAttachments[weaponName].placeholder) then Utils:DeleteWeapon(weaponName) end end end end CreateThread(function() local lastVehicleState = false while true do local weapon = GetSelectedPedWeapon(cache.ped) local isInVehicle = IsPedInAnyVehicle(cache.ped, false) if lastVehicleState ~= isInVehicle then lastVehicleState = isInVehicle if isInVehicle then for weaponName, _ in pairs(Sling.cachedAttachments) do if DoesEntityExist(Sling.cachedAttachments[weaponName].obj) then Utils:DeleteWeapon(weaponName) Sling:SyncWeaponAttachment(weaponName, nil, nil, 'detach') end end else for weaponName, weaponVal in pairs(Sling.cachedWeapons) do if not DoesEntityExist(Sling.cachedAttachments[weaponName]?.obj) then local coords = Sling.cachedPositions[weaponName] or Sling.cachedPresets[weaponName] or { coords = { x = 0.0, y = -0.15, z = 0.0 }, rot = { x = 0.0, y = 0.0, z = 0.0 }, boneId = DEFAULT_BONE } Utils:CreateAndAttachWeapon(weaponName, weaponVal, coords, cache.ped) Sling:SyncWeaponAttachment(weaponName, weaponVal, coords, 'attach') end end end end while Sling.inPositioning do Wait(1000) end if not isInVehicle then for weaponName, weaponVal in pairs(Sling.cachedWeapons) do handleWeaponAttachment(weaponName, weaponVal, cache.ped, weapon) end end Wait(1000) end end) end function Sling:OnPositioningDone(coords, selectData) lib.hideTextUI() Sling.inPositioning = false local weapon = selectData.weapon coords.position = vec3(coords.position.x, coords.position.y, coords.position.z) local distanceFromMiddle = #(coords.position - vec3(0.0, 0.0, 0.0)) local distanceFromMiddle2 = #(coords.position - vec3(0.0, 0.0, -0.2)) local distanceFromMiddle3 = #(coords.position - vec3(0.0, 0.0, 0.2)) if distanceFromMiddle < 0.14 or distanceFromMiddle2 < 0.14 or distanceFromMiddle3 < 0.14 then coords.position = vec3(coords.position.x, 0.17, coords.position.z) end TriggerServerEvent("force-sling:server:saveWeaponPosition", coords.position, coords.rotation, weapon, selectData.weaponName, selectData.boneId, Sling.isPreset) Sling.cachedPositions[selectData.weaponName] = { coords = coords.position, rot = coords.rotation, boneId = selectData.boneId } if Sling.cachedAttachments[selectData.weaponName] then if DoesEntityExist(Sling.cachedAttachments[selectData.weaponName].obj) or DoesEntityExist(Sling.cachedAttachments[selectData.weaponName].placeholder) then DeleteObject(Sling.cachedAttachments[selectData.weaponName].obj) DeleteObject(Sling.cachedAttachments[selectData.weaponName].placeholder) end end DeleteObject(Sling.object) SetModelAsNoLongerNeeded(selectData.weapon) end local function DisableControls() local controls = { 25, 44, 45, 51, 140, 141, 143, 263, 264, 24, 96, 97, 47, 74, 177 } for i = 1, #controls do DisableControlAction(0, controls[i], true) end end function Sling:StartPositioning(selectData) if Sling.inPositioning then return end local coords = { position = vec3(0.0, 0.0, 0.0), rotation = vec3(0.0, 0.0, 0.0) } if Sling.cachedAttachments[selectData.weaponName] and DoesEntityExist(Sling.cachedAttachments[selectData.weaponName].obj) then Utils:DeleteWeapon(selectData.weaponName) end if Sling.cachedPositions[selectData.weaponName] and selectData.boneId == Sling.cachedPositions[selectData.weaponName].boneId then coords.position = Sling.cachedPositions[selectData.weaponName].coords coords.rotation = Sling.cachedPositions[selectData.weaponName].rot elseif Sling.cachedPresets[selectData.weaponName] and selectData.boneId == Sling.cachedPresets[selectData.weaponName].boneId then coords.position = Sling.cachedPresets[selectData.weaponName].coords coords.rotation = Sling.cachedPresets[selectData.weaponName].rot end Sling.inPositioning = true CreateThread(function() local speed = DEFAULT_SPEED local function updatePosition(axis, delta) local x, y, z = coords.position.x, coords.position.y, coords.position.z if axis == 'x' then x = lib.math.clamp(x + delta, -POSITION_CLAMP, POSITION_CLAMP) elseif axis == 'y' then y = lib.math.clamp(y + delta, -POSITION_CLAMP, POSITION_CLAMP) elseif axis == 'z' then z = lib.math.clamp(z + delta, -POSITION_CLAMP, POSITION_CLAMP) end coords.position = vec3(x, y, z) AttachEntityToEntity(Sling.object, cache.ped, GetPedBoneIndex(cache.ped, selectData.boneId), coords.position.x, coords.position.y, coords.position.z, coords.rotation.x, coords.rotation.y, coords.rotation.z, true, true, false, true, 2, true) end local function updateRotation(axis, delta) local x, y, z = coords.rotation.x, coords.rotation.y, coords.rotation.z if axis == 'x' then x = x + delta elseif axis == 'y' then y = y + delta elseif axis == 'z' then z = z + delta end coords.rotation = vec3(x, y, z) AttachEntityToEntity(Sling.object, cache.ped, GetPedBoneIndex(cache.ped, selectData.boneId), coords.position.x, coords.position.y, coords.position.z, coords.rotation.x, coords.rotation.y, coords.rotation.z, true, true, false, true, 2, true) end while Sling.inPositioning do if not DoesEntityExist(Sling.object) then if not HasModelLoaded(selectData.weapon) then lib.requestModel(selectData.weapon) end Sling.object = CreateObject(selectData.weapon, 0, 0, 0, false, true, false) AttachEntityToEntity(Sling.object, cache.ped, GetPedBoneIndex(cache.ped, selectData.boneId), coords.position.x, coords.position.y, coords.position.z, coords.rotation.x, coords.rotation.y, coords.rotation.z, true, true, false, true, 2, true) SetEntityCollision(Sling.object, false, false) end if IsDisabledControlJustPressed(0, 18) then Sling:OnPositioningDone(coords, selectData) break end if IsDisabledControlJustPressed(0, 177) then DeleteObject(Sling.object) Sling.inPositioning = false lib.hideTextUI() SetModelAsNoLongerNeeded(selectData.weapon) break end if IsDisabledControlPressed(0, 21) then speed = FAST_SPEED end if IsDisabledControlReleased(0, 21) then speed = DEFAULT_SPEED end if IsDisabledControlPressed(0, 44) then updatePosition('x', -speed) end if IsDisabledControlPressed(0, 46) then updatePosition('x', speed) end if IsDisabledControlPressed(0, 188) then updatePosition('y', speed) end if IsDisabledControlPressed(0, 187) then updatePosition('y', -speed) end if IsDisabledControlPressed(0, 189) then updatePosition('z', speed) end if IsDisabledControlPressed(0, 190) then updatePosition('z', -speed) end if IsDisabledControlPressed(0, 96) then updateRotation('x', speed + 1.0) end if IsDisabledControlPressed(0, 97) then updateRotation('x', -(speed + 1.0)) end if IsDisabledControlPressed(0, 48) then updateRotation('z', speed + 1.0) end if IsDisabledControlPressed(0, 73) then updateRotation('z', -(speed + 1.0)) end if IsDisabledControlPressed(0, 47) then updateRotation('y', speed + 1.0) end if IsDisabledControlPressed(0, 74) then updateRotation('y', -(speed + 1.0)) end local text = ("pos: (%.2f, %.2f, %.2f) | rot: (%.2f, %.2f, %.2f)"):format(coords.position.x, coords.position.y, coords.position.z, coords.rotation.x, coords.rotation.y, coords.rotation.z) lib.showTextUI((locale("currentPosition") .. ": %s"):format(text) .. ' \n ' .. '[QE] - ' .. locale("up") .. '/' .. locale("down") .. ' \n' .. '[Arrows] - ' .. locale("move") .. ', XY \n' .. '[Scroll]- ' .. locale("rotate") .. ' \n' .. '[XZ]- ' .. locale("rotate") .. ' \n' .. '[GH] - ' .. locale("rotate") .. ' Z \n' .. '[Shift] - ' .. locale("speed") .. ' \n' .. '[ENTER] - ' .. locale("confirm") .. ' \n' .. '[BACKSPACE] - ' .. locale("cancel")) DisableControls() Wait(4) end end) end function Sling:StartConfiguration(isPreset) Sling.isPreset = isPreset lib.showMenu('sling_select') end function Sling:InitCommands() Debug("info", "Initializing commands") local admin = lib.callback.await("force-sling:callback:isPlayerAdmin", false) if Config.Debug or admin.isAdmin then RegisterCommand(Config.Command.name, function(source, args, raw) if Config.Command.permission ~= "any" and admin ~= Config.Command.permission then return end Sling:StartConfiguration(false) end, false) RegisterCommand(Config.Command.reset, function(source, args, raw) if Config.Command.permission ~= "any" and admin ~= Config.Command.permission then return end local weapon = args[1] and args[1]:lower() or GetSelectedPedWeapon(cache.ped) if type(weapon) == "number" then for weaponName, weaponVal in pairs(Sling.cachedWeapons) do if weaponVal.name == weapon then weapon = weaponName break end end end Sling.cachedPositions = lib.callback.await("force-sling:callback:resetWeaponPositions", false, weapon) end, false) RegisterCommand(Config.Presets.command, function(source, args, raw) if Config.Presets.permission ~= "any" and admin ~= Config.Presets.permission then return end Sling:StartConfiguration(true) end, false) end Debug("info", "Commands initialized") end