2025-06-07 08:51:21 +02:00
|
|
|
local createdRope = nil
|
|
|
|
local ropeLength = 10.0
|
|
|
|
local minRopeLength = 1.0
|
|
|
|
local lengthTick = 0.02
|
|
|
|
local enteredCloseVehicle = false
|
|
|
|
local closestTrailer = nil
|
|
|
|
local closestBoat = nil
|
|
|
|
local hasTakenRope = false
|
|
|
|
local hookedToBoat = false
|
|
|
|
local controlRope = false
|
|
|
|
local colObj = nil
|
|
|
|
local boatOnTrailer = false
|
|
|
|
|
|
|
|
local function getTrailerOffset(trailer)
|
|
|
|
local model = GetEntityModel(trailer)
|
2025-07-02 06:24:51 +02:00
|
|
|
if model == `boattrailer6` then
|
2025-07-02 06:50:59 +02:00
|
|
|
return vector3(0.0, 6.0, 0.3) -- Adjusted values for boattrailer6 (moved forward toward hitch)
|
2025-06-07 08:51:21 +02:00
|
|
|
else
|
2025-07-02 06:35:52 +02:00
|
|
|
return vector3(0.0, 1.8, 0.0) -- Original boattrailer values
|
2025-06-07 08:51:21 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function getBoatAttachmentOffset(trailer)
|
|
|
|
local model = GetEntityModel(trailer)
|
2025-07-02 06:24:51 +02:00
|
|
|
if model == `boattrailer6` then
|
2025-07-02 06:59:46 +02:00
|
|
|
return vector3(0.0, -1.0, 0.75) -- Adjusted values for boattrailer6
|
2025-06-07 08:51:21 +02:00
|
|
|
else
|
2025-07-02 06:35:52 +02:00
|
|
|
return vector3(0.0, -1.02, 0.3) -- Original boattrailer values
|
2025-06-07 08:51:21 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function isSupportedBoat(closest)
|
2025-07-02 06:35:52 +02:00
|
|
|
if not closest then return false end
|
2025-06-07 08:51:21 +02:00
|
|
|
for _, model in pairs(Config.SupportedBoats) do
|
|
|
|
if GetEntityModel(closest) == model then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
2025-07-02 06:35:52 +02:00
|
|
|
return false
|
2025-06-07 08:51:21 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
local function isTrailer(vehicle)
|
2025-07-02 06:35:52 +02:00
|
|
|
if not vehicle then return false end
|
2025-06-07 08:51:21 +02:00
|
|
|
local model = GetEntityModel(vehicle)
|
2025-07-02 06:24:51 +02:00
|
|
|
return model == `boattrailer` or model == `boattrailer6`
|
2025-06-07 08:51:21 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
local function getClosestVehicle(coords, maxDistance)
|
|
|
|
local vehicles = GetGamePool('CVehicle')
|
|
|
|
local closestVehicle, closestCoords
|
|
|
|
maxDistance = maxDistance or 2.0
|
|
|
|
|
|
|
|
for i = 1, #vehicles do
|
|
|
|
local vehicle = vehicles[i]
|
|
|
|
local vehicleCoords = GetEntityCoords(vehicle)
|
|
|
|
local distance = #(coords - vehicleCoords)
|
|
|
|
|
|
|
|
if distance < maxDistance then
|
|
|
|
maxDistance = distance
|
|
|
|
closestVehicle = vehicle
|
|
|
|
closestCoords = vehicleCoords
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return closestVehicle, closestCoords
|
|
|
|
end
|
|
|
|
|
|
|
|
local function notify(text)
|
|
|
|
SetNotificationTextEntry("STRING")
|
|
|
|
AddTextComponentString(text)
|
|
|
|
DrawNotification(true, false)
|
|
|
|
end
|
|
|
|
|
2025-07-02 06:50:59 +02:00
|
|
|
-- Replace the IsBoatOverTrailer function with this improved version
|
2025-07-02 06:35:52 +02:00
|
|
|
local function IsBoatOverTrailer(boat)
|
|
|
|
if not boat or not isSupportedBoat(boat) then return false end
|
|
|
|
|
|
|
|
local boatCoords = GetEntityCoords(boat)
|
|
|
|
local vehicles = GetGamePool('CVehicle')
|
|
|
|
local foundTrailer = nil
|
|
|
|
|
|
|
|
for i = 1, #vehicles do
|
|
|
|
local vehicle = vehicles[i]
|
|
|
|
if isTrailer(vehicle) then
|
|
|
|
local trailerCoords = GetEntityCoords(vehicle)
|
|
|
|
local distance = #(boatCoords - trailerCoords)
|
|
|
|
if distance < 3.0 then
|
|
|
|
-- Additional check for proper alignment
|
|
|
|
local trailerHeading = GetEntityHeading(vehicle)
|
|
|
|
local boatHeading = GetEntityHeading(boat)
|
|
|
|
local headingDiff = math.abs(trailerHeading - boatHeading)
|
|
|
|
if headingDiff < 30.0 or headingDiff > 330.0 then
|
|
|
|
foundTrailer = vehicle
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return foundTrailer
|
|
|
|
end
|
|
|
|
|
2025-07-02 06:50:59 +02:00
|
|
|
-- Add this new thread to handle E key press for securing boat
|
2025-06-07 08:51:21 +02:00
|
|
|
CreateThread(function()
|
|
|
|
while true do
|
2025-07-02 06:50:59 +02:00
|
|
|
Wait(0)
|
|
|
|
|
2025-06-07 08:51:21 +02:00
|
|
|
local playerPed = PlayerPedId()
|
2025-07-02 06:50:59 +02:00
|
|
|
|
|
|
|
-- Only check if player is in a vehicle
|
|
|
|
if IsPedInAnyVehicle(playerPed, false) then
|
|
|
|
local boat = GetVehiclePedIsIn(playerPed, false)
|
|
|
|
|
|
|
|
-- Check if it's a supported boat
|
|
|
|
if isSupportedBoat(boat) then
|
|
|
|
-- Check if boat is over a trailer
|
|
|
|
local trailer = IsBoatOverTrailer(boat)
|
2025-06-07 08:51:21 +02:00
|
|
|
|
2025-07-02 06:50:59 +02:00
|
|
|
if trailer then
|
|
|
|
-- Display help text
|
|
|
|
BeginTextCommandDisplayHelp("STRING")
|
|
|
|
AddTextComponentSubstringPlayerName("Press ~INPUT_CONTEXT~ to secure boat to trailer")
|
|
|
|
EndTextCommandDisplayHelp(0, false, true, -1)
|
|
|
|
|
|
|
|
-- Check for E key press
|
|
|
|
if IsControlJustReleased(0, 38) then -- 38 is E key
|
|
|
|
-- Exit the boat
|
|
|
|
TaskLeaveVehicle(playerPed, boat, 0)
|
|
|
|
|
|
|
|
-- Wait for player to exit
|
|
|
|
Wait(1500)
|
|
|
|
|
|
|
|
-- Attach boat to trailer
|
|
|
|
local attachOffset = getBoatAttachmentOffset(trailer)
|
|
|
|
AttachEntityToEntity(boat, trailer, 0, attachOffset.x, attachOffset.y, attachOffset.z, 0.0, 0.0, 0.0, false, false, false, false, 2, true)
|
|
|
|
|
|
|
|
closestBoat = boat
|
|
|
|
closestTrailer = trailer
|
|
|
|
boatOnTrailer = true
|
|
|
|
notify("Boot am Anhänger befestigt")
|
|
|
|
end
|
2025-06-07 08:51:21 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
2025-07-02 06:50:59 +02:00
|
|
|
Wait(1000) -- Wait longer if not in a vehicle
|
2025-06-07 08:51:21 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
2025-07-02 06:50:59 +02:00
|
|
|
|
2025-06-07 08:51:21 +02:00
|
|
|
CreateThread(function()
|
|
|
|
if GetResourceState('qb-target') == 'started' then
|
|
|
|
exports['qb-target']:AddGlobalVehicle({
|
|
|
|
options = {
|
|
|
|
{
|
|
|
|
type = "client",
|
|
|
|
event = "re_boat_winch:client:takeRope",
|
|
|
|
icon = 'fas fa-clipboard',
|
|
|
|
label = "Take the rope",
|
|
|
|
canInteract = function(entity)
|
|
|
|
if closestTrailer ~= entity or not isTrailer(entity) then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
return not boatOnTrailer and not hookedToBoat and not hasTakenRope
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type = "client",
|
|
|
|
event = "re_boat_winch:client:returnRope",
|
|
|
|
icon = 'fas fa-clipboard',
|
|
|
|
label = "Return the rope",
|
|
|
|
canInteract = function(entity)
|
|
|
|
if closestTrailer ~= entity or not isTrailer(entity) then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
return hasTakenRope and not hookedToBoat and not boatOnTrailer
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type = "client",
|
|
|
|
event = "re_boat_winch:client:controlRope",
|
|
|
|
icon = 'fas fa-clipboard',
|
|
|
|
label = "Control the rope",
|
|
|
|
canInteract = function(entity)
|
|
|
|
if closestTrailer ~= entity or not isTrailer(entity) then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
return not hasTakenRope and hookedToBoat
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type = "client",
|
|
|
|
event = "re_boat_winch:client:detachBoat",
|
|
|
|
icon = 'fas fa-clipboard',
|
|
|
|
label = "Detach boat from trailer",
|
|
|
|
canInteract = function(entity)
|
|
|
|
if closestTrailer ~= entity or not isTrailer(entity) then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
return boatOnTrailer
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type = "client",
|
|
|
|
event = "re_boat_winch:client:hookUpRope",
|
|
|
|
icon = 'fas fa-clipboard',
|
|
|
|
label = "Hook up the rope",
|
|
|
|
canInteract = function(entity)
|
|
|
|
if closestBoat ~= entity or not isSupportedBoat(entity) then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
return hasTakenRope and not hookedToBoat
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type = "client",
|
|
|
|
event = "re_boat_winch:client:unHookRope",
|
|
|
|
icon = 'fas fa-clipboard',
|
|
|
|
label = "Unhook the rope",
|
|
|
|
canInteract = function(entity)
|
|
|
|
if closestBoat ~= entity or not isSupportedBoat(entity) then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
return not hasTakenRope and hookedToBoat and createdRope
|
|
|
|
end
|
2025-07-02 06:35:52 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
type = "client",
|
|
|
|
event = "re_boat_winch:client:selfDockBoat",
|
|
|
|
icon = 'fas fa-anchor',
|
|
|
|
label = "Secure boat to trailer",
|
|
|
|
canInteract = function(entity)
|
|
|
|
if not isSupportedBoat(entity) then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
local trailer = IsBoatOverTrailer(entity)
|
|
|
|
return trailer ~= nil
|
|
|
|
end
|
2025-06-07 08:51:21 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
distance = 2.5
|
|
|
|
})
|
|
|
|
else
|
|
|
|
exports.ox_target:addGlobalVehicle({
|
|
|
|
{
|
|
|
|
event = "re_boat_winch:client:takeRope",
|
|
|
|
icon = 'fas fa-clipboard',
|
|
|
|
label = "Take the rope",
|
|
|
|
name = "re_boat_winch:client:takeRope",
|
|
|
|
distance = 2.5,
|
|
|
|
canInteract = function(entity)
|
|
|
|
if closestTrailer ~= entity or not isTrailer(entity) then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
return not boatOnTrailer and not hookedToBoat and not hasTakenRope
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
|
|
|
event = "re_boat_winch:client:returnRope",
|
|
|
|
icon = 'fas fa-clipboard',
|
|
|
|
label = "Return the rope",
|
|
|
|
name = "re_boat_winch:client:returnRope",
|
|
|
|
distance = 2.5,
|
|
|
|
canInteract = function(entity)
|
|
|
|
if closestTrailer ~= entity or not isTrailer(entity) then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
return hasTakenRope and not hookedToBoat and not boatOnTrailer
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
|
|
|
event = "re_boat_winch:client:controlRope",
|
|
|
|
icon = 'fas fa-clipboard',
|
|
|
|
label = "Control the rope",
|
|
|
|
name = "re_boat_winch:client:controlRope",
|
|
|
|
distance = 2.5,
|
|
|
|
canInteract = function(entity)
|
|
|
|
if closestTrailer ~= entity or not isTrailer(entity) then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
return not hasTakenRope and hookedToBoat
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
|
|
|
event = "re_boat_winch:client:detachBoat",
|
|
|
|
icon = 'fas fa-clipboard',
|
|
|
|
label = "Detach boat from trailer",
|
|
|
|
name = "re_boat_winch:client:detachBoat",
|
|
|
|
distance = 2.5,
|
|
|
|
canInteract = function(entity)
|
|
|
|
if closestTrailer ~= entity or not isTrailer(entity) then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
return boatOnTrailer
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
|
|
|
event = "re_boat_winch:client:hookUpRope",
|
|
|
|
icon = 'fas fa-clipboard',
|
|
|
|
label = "Hook up the rope",
|
|
|
|
name = "re_boat_winch:client:hookUpRope",
|
|
|
|
distance = 2.5,
|
|
|
|
canInteract = function(entity)
|
|
|
|
if closestBoat ~= entity or not isSupportedBoat(entity) then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
return hasTakenRope and not hookedToBoat
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
|
|
|
event = "re_boat_winch:client:unHookRope",
|
|
|
|
icon = 'fas fa-clipboard',
|
|
|
|
label = "Unhook the rope",
|
|
|
|
name = "re_boat_winch:client:unHookRope",
|
|
|
|
distance = 2.5,
|
|
|
|
canInteract = function(entity)
|
|
|
|
if closestBoat ~= entity or not isSupportedBoat(entity) then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
return not hasTakenRope and hookedToBoat and createdRope
|
|
|
|
end
|
2025-07-02 06:35:52 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
event = "re_boat_winch:client:selfDockBoat",
|
|
|
|
icon = 'fas fa-anchor',
|
|
|
|
label = "Secure boat to trailer",
|
|
|
|
name = "re_boat_winch:client:selfDockBoat",
|
|
|
|
distance = 2.5,
|
|
|
|
canInteract = function(entity)
|
|
|
|
if not isSupportedBoat(entity) then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
local trailer = IsBoatOverTrailer(entity)
|
|
|
|
return trailer ~= nil
|
|
|
|
end
|
2025-06-07 08:51:21 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
|
|
|
AddEventHandler('onResourceStop', function(resourceName)
|
|
|
|
if resourceName ~= "re_boat_winch" then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
if GetResourceState('qb-target') == 'started' then
|
|
|
|
exports['qb-target']:RemoveGlobalVehicle("Nimm das Seil")
|
|
|
|
exports['qb-target']:RemoveGlobalVehicle("Häng das SEil zurück")
|
|
|
|
exports['qb-target']:RemoveGlobalVehicle("Winde kontrollieren")
|
|
|
|
exports['qb-target']:RemoveGlobalVehicle("Boot vom Anhänger lösen")
|
|
|
|
exports['qb-target']:RemoveGlobalVehicle("Seil einhängen")
|
|
|
|
exports['qb-target']:RemoveGlobalVehicle("Seil aushängen")
|
2025-07-02 06:35:52 +02:00
|
|
|
exports['qb-target']:RemoveGlobalVehicle("Boot am Anhänger befestigen")
|
2025-06-07 08:51:21 +02:00
|
|
|
else
|
|
|
|
exports.ox_target:removeGlobalVehicle("re_boat_winch:client:takeRope")
|
|
|
|
exports.ox_target:removeGlobalVehicle("re_boat_winch:client:returnRope")
|
|
|
|
exports.ox_target:removeGlobalVehicle("re_boat_winch:client:controlRope")
|
|
|
|
exports.ox_target:removeGlobalVehicle("re_boat_winch:client:detachBoat")
|
|
|
|
exports.ox_target:removeGlobalVehicle("re_boat_winch:client:hookUpRope")
|
|
|
|
exports.ox_target:removeGlobalVehicle("re_boat_winch:client:unHookRope")
|
2025-07-02 06:35:52 +02:00
|
|
|
exports.ox_target:removeGlobalVehicle("re_boat_winch:client:selfDockBoat")
|
2025-06-07 08:51:21 +02:00
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
|
|
|
local function createRope(obj1, obj2, obj1Coords, obj2Coords, lenght)
|
|
|
|
DeleteRope(createdRope)
|
|
|
|
|
|
|
|
RopeLoadTextures()
|
|
|
|
while not RopeAreTexturesLoaded() do
|
|
|
|
Wait(0)
|
|
|
|
RopeLoadTextures()
|
|
|
|
end
|
|
|
|
|
|
|
|
RopeLoadTextures()
|
|
|
|
|
|
|
|
createdRope = AddRope(obj1Coords.x, obj1Coords.y, obj1Coords.z, 0.0, 0.0, 0.0, lenght, 1, Config.MaxRopeLength, 1.0,
|
|
|
|
1.0, false, true, false, 5.0, false, 0)
|
|
|
|
|
|
|
|
while not createdRope do
|
|
|
|
Wait(0)
|
|
|
|
end
|
|
|
|
|
|
|
|
ActivatePhysics(createdRope)
|
|
|
|
|
|
|
|
Wait(50)
|
|
|
|
|
|
|
|
AttachEntitiesToRope(createdRope, obj1, obj2, obj1Coords.x, obj1Coords.y, obj1Coords.z, obj2Coords.x, obj2Coords.y,
|
|
|
|
obj2Coords.z, lenght, false, false, nil, nil)
|
|
|
|
end
|
|
|
|
|
|
|
|
RegisterNetEvent('re_boat_winch:client:unHookRope', function()
|
|
|
|
hookedToBoat = false
|
|
|
|
controlRope = false
|
|
|
|
hasTakenRope = true
|
|
|
|
|
|
|
|
notify("Du hast das Seil ausgehakt")
|
|
|
|
|
|
|
|
local playerPed = PlayerPedId()
|
|
|
|
local trailerOffset = getTrailerOffset(closestTrailer)
|
|
|
|
local trailerCoords = GetOffsetFromEntityInWorldCoords(closestTrailer, trailerOffset.x, trailerOffset.y, trailerOffset.z)
|
|
|
|
local boneIndex = GetPedBoneIndex(playerPed, 0x49D9)
|
|
|
|
|
|
|
|
ropeLength = 5.0
|
|
|
|
colObj = CreateObject(`xm3_prop_xm3_hook_01a`, 0, 0, 0, true, true, true)
|
|
|
|
|
|
|
|
AttachEntityToEntity(colObj, playerPed, boneIndex, 0.11, 0.02, 0.02, -80.0, -90.0, 15.0, true,
|
|
|
|
true, false, true, 1, true)
|
|
|
|
|
|
|
|
local objCoords = GetEntityCoords(colObj)
|
|
|
|
|
|
|
|
createRope(closestTrailer, colObj, trailerCoords, objCoords, ropeLength)
|
|
|
|
end)
|
|
|
|
|
|
|
|
RegisterNetEvent('re_boat_winch:client:returnRope', function()
|
|
|
|
hasTakenRope = false
|
|
|
|
|
|
|
|
DeleteEntity(colObj)
|
|
|
|
DeleteRope(createdRope)
|
2025-07-02 06:35:52 +02:00
|
|
|
|
|
|
|
-- Unfreeze the trailer when returning the rope
|
|
|
|
FreezeEntityPosition(closestTrailer, false)
|
|
|
|
SetEntityInvincible(closestTrailer, false)
|
2025-06-07 08:51:21 +02:00
|
|
|
end)
|
|
|
|
|
|
|
|
RegisterNetEvent('re_boat_winch:client:detachBoat', function()
|
|
|
|
boatOnTrailer = false
|
|
|
|
|
|
|
|
DetachEntity(closestBoat, true, true)
|
|
|
|
|
|
|
|
local trailerOffset = getTrailerOffset(closestTrailer)
|
|
|
|
local trailerCoords = GetOffsetFromEntityInWorldCoords(closestTrailer, 0.0, -8.0, 0.0)
|
|
|
|
|
|
|
|
SetEntityCoords(closestBoat, trailerCoords.x, trailerCoords.y, trailerCoords.z)
|
|
|
|
end)
|
|
|
|
|
|
|
|
RegisterNetEvent('re_boat_winch:client:takeRope', function()
|
|
|
|
hasTakenRope = true
|
|
|
|
|
|
|
|
FreezeEntityPosition(closestTrailer, true)
|
|
|
|
SetEntityInvincible(closestTrailer, true)
|
|
|
|
|
|
|
|
local playerPed = PlayerPedId()
|
|
|
|
local playerCoords = GetEntityCoords(playerPed)
|
|
|
|
local trailerOffset = getTrailerOffset(closestTrailer)
|
|
|
|
local trailerCoords = GetOffsetFromEntityInWorldCoords(closestTrailer, trailerOffset.x, trailerOffset.y, trailerOffset.z)
|
|
|
|
local coordsDiff = #(trailerCoords - playerCoords)
|
|
|
|
local boneIndex = GetPedBoneIndex(playerPed, 0x49D9)
|
|
|
|
|
|
|
|
ropeLength = coordsDiff
|
|
|
|
colObj = CreateObject(`xm3_prop_xm3_hook_01a`, 0, 0, 0, true, true, true)
|
|
|
|
|
|
|
|
AttachEntityToEntity(colObj, playerPed, boneIndex, 0.11, 0.02, 0.02, -80.0, -90.0, 15.0, true, true, false, true, 1,
|
|
|
|
true)
|
|
|
|
|
|
|
|
local objCoords = GetEntityCoords(colObj)
|
|
|
|
|
|
|
|
createRope(closestTrailer, colObj, trailerCoords, objCoords, ropeLength)
|
|
|
|
end)
|
|
|
|
|
|
|
|
RegisterNetEvent('re_boat_winch:client:hookUpRope', function()
|
|
|
|
hookedToBoat = true
|
|
|
|
hasTakenRope = false
|
|
|
|
|
|
|
|
notify("Du hast den Haken genommen")
|
|
|
|
|
|
|
|
local trailerOffset = getTrailerOffset(closestTrailer)
|
|
|
|
local trailerCoords = GetOffsetFromEntityInWorldCoords(closestTrailer, trailerOffset.x, trailerOffset.y, trailerOffset.z)
|
|
|
|
local boatCoords = GetOffsetFromEntityInWorldCoords(closestBoat, 0.0, 3.9, 0.9)
|
|
|
|
local coordsDiff = #(trailerCoords - boatCoords)
|
|
|
|
|
|
|
|
ropeLength = coordsDiff
|
|
|
|
|
|
|
|
SetEntityInvincible(closestBoat, true)
|
|
|
|
DeleteEntity(colObj)
|
|
|
|
|
|
|
|
createRope(closestTrailer, closestBoat, trailerCoords, boatCoords, ropeLength)
|
|
|
|
end)
|
|
|
|
|
|
|
|
RegisterNetEvent('re_boat_winch:client:controlRope', function()
|
|
|
|
controlRope = true
|
|
|
|
|
|
|
|
notify("Du kontrollierst die Winde")
|
|
|
|
end)
|
|
|
|
|
2025-07-02 06:35:52 +02:00
|
|
|
-- New event for self-docking boats
|
|
|
|
RegisterNetEvent('re_boat_winch:client:selfDockBoat', function()
|
|
|
|
local playerPed = PlayerPedId()
|
|
|
|
local boat = GetVehiclePedIsIn(playerPed, false)
|
|
|
|
|
|
|
|
if not boat or not isSupportedBoat(boat) then
|
|
|
|
notify("Du musst in einem unterstützten Boot sein")
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
local trailer = IsBoatOverTrailer(boat)
|
|
|
|
if not trailer then
|
|
|
|
notify("Kein Anhänger in der Nähe gefunden")
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Exit the boat
|
|
|
|
TaskLeaveVehicle(playerPed, boat, 0)
|
|
|
|
Wait(1500)
|
|
|
|
|
|
|
|
-- Attach boat to trailer
|
|
|
|
local attachOffset = getBoatAttachmentOffset(trailer)
|
|
|
|
AttachEntityToEntity(boat, trailer, 0, attachOffset.x, attachOffset.y, attachOffset.z, 0.0, 0.0, 0.0, false, false, false, false, 2, true)
|
|
|
|
|
|
|
|
closestBoat = boat
|
|
|
|
closestTrailer = trailer
|
|
|
|
boatOnTrailer = true
|
|
|
|
notify("Boot am Anhänger befestigt")
|
|
|
|
end)
|
|
|
|
|
2025-06-07 08:51:21 +02:00
|
|
|
local function attachBoatToTrailer()
|
|
|
|
if not DoesEntityExist(closestTrailer) or not DoesEntityExist(closestBoat) then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
local attachOffset = getBoatAttachmentOffset(closestTrailer)
|
|
|
|
AttachEntityToEntity(closestBoat, closestTrailer, 0, attachOffset.x, attachOffset.y, attachOffset.z, 0.0, 0.0, 0.0, false, false, false, false, 2, true)
|
|
|
|
|
|
|
|
DeleteRope(createdRope)
|
|
|
|
FreezeEntityPosition(closestTrailer, false)
|
|
|
|
SetEntityInvincible(closestTrailer, false)
|
|
|
|
SetEntityInvincible(closestBoat, false)
|
|
|
|
|
|
|
|
hasTakenRope = false
|
|
|
|
hookedToBoat = false
|
|
|
|
controlRope = false
|
|
|
|
createdRope = nil
|
|
|
|
colObj = nil
|
|
|
|
boatOnTrailer = true
|
|
|
|
end
|
|
|
|
|
2025-07-02 06:59:46 +02:00
|
|
|
-- Replace the existing winch control thread with this improved version
|
2025-06-07 08:51:21 +02:00
|
|
|
CreateThread(function()
|
|
|
|
while true do
|
2025-07-02 06:59:46 +02:00
|
|
|
Wait(0)
|
2025-06-07 08:51:21 +02:00
|
|
|
|
|
|
|
if createdRope and controlRope then
|
2025-07-02 06:59:46 +02:00
|
|
|
-- Display help text for winch controls
|
|
|
|
BeginTextCommandDisplayHelp("STRING")
|
|
|
|
AddTextComponentSubstringPlayerName("~INPUT_FRONTEND_UP~ Extend winch | ~INPUT_FRONTEND_DOWN~ Retract winch | ~INPUT_FRONTEND_RRIGHT~ Cancel")
|
|
|
|
EndTextCommandDisplayHelp(0, false, true, -1)
|
|
|
|
|
|
|
|
-- Show current rope length
|
|
|
|
DrawTextOnScreen("Rope Length: " .. string.format("%.1f", ropeLength) .. "m", 0.5, 0.05, 0.4, {r=255, g=255, b=255, a=200})
|
|
|
|
|
|
|
|
-- Extend rope (Arrow Up)
|
2025-06-07 08:51:21 +02:00
|
|
|
if IsControlPressed(0, 172) then
|
2025-07-02 06:59:46 +02:00
|
|
|
ropeLength = ropeLength + lengthTick * 2 -- Doubled speed for better responsiveness
|
2025-06-07 08:51:21 +02:00
|
|
|
|
|
|
|
if ropeLength > Config.MaxRopeLength then
|
|
|
|
ropeLength = Config.MaxRopeLength
|
|
|
|
end
|
|
|
|
|
|
|
|
StopRopeWinding(createdRope)
|
|
|
|
StartRopeUnwindingFront(createdRope)
|
|
|
|
RopeForceLength(createdRope, ropeLength)
|
2025-07-02 06:59:46 +02:00
|
|
|
|
|
|
|
-- Add visual feedback
|
|
|
|
PlaySoundFrontend(-1, "NAV_UP_DOWN", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
|
|
|
|
|
2025-06-07 08:51:21 +02:00
|
|
|
elseif IsControlJustReleased(0, 172) then
|
|
|
|
StopRopeUnwindingFront(createdRope)
|
|
|
|
StopRopeWinding(createdRope)
|
|
|
|
RopeConvertToSimple(createdRope)
|
2025-07-02 06:59:46 +02:00
|
|
|
|
|
|
|
-- Retract rope (Arrow Down)
|
2025-06-07 08:51:21 +02:00
|
|
|
elseif IsControlPressed(0, 173) then
|
2025-07-02 06:59:46 +02:00
|
|
|
ropeLength = ropeLength - lengthTick * 2 -- Doubled speed for better responsiveness
|
2025-06-07 08:51:21 +02:00
|
|
|
|
|
|
|
if ropeLength < minRopeLength then
|
|
|
|
ropeLength = minRopeLength
|
2025-07-02 06:59:46 +02:00
|
|
|
|
|
|
|
-- Auto-attach boat when fully retracted
|
|
|
|
if hookedToBoat then
|
|
|
|
attachBoatToTrailer()
|
|
|
|
notify("Boot erfolgreich auf den Anhänger gezogen")
|
|
|
|
end
|
2025-06-07 08:51:21 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
StopRopeUnwindingFront(createdRope)
|
|
|
|
StartRopeWinding(createdRope)
|
|
|
|
RopeForceLength(createdRope, ropeLength)
|
2025-07-02 06:59:46 +02:00
|
|
|
|
|
|
|
-- Add visual feedback
|
|
|
|
PlaySoundFrontend(-1, "NAV_UP_DOWN", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
|
|
|
|
|
2025-06-07 08:51:21 +02:00
|
|
|
elseif IsControlJustReleased(0, 173) then
|
|
|
|
StopRopeUnwindingFront(createdRope)
|
|
|
|
StopRopeWinding(createdRope)
|
|
|
|
RopeConvertToSimple(createdRope)
|
2025-07-02 06:59:46 +02:00
|
|
|
|
|
|
|
-- Cancel winch control (Backspace)
|
|
|
|
elseif IsControlJustPressed(0, 194) then
|
2025-06-07 08:51:21 +02:00
|
|
|
controlRope = false
|
2025-07-02 06:59:46 +02:00
|
|
|
notify("Windensteuerung beendet")
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Visual indicator for rope tension
|
|
|
|
local tensionColor = {r=255, g=255, b=255, a=200}
|
|
|
|
if ropeLength < 3.0 then
|
|
|
|
-- High tension (red)
|
|
|
|
tensionColor = {r=255, g=0, b=0, a=200}
|
|
|
|
elseif ropeLength < 6.0 then
|
|
|
|
-- Medium tension (yellow)
|
|
|
|
tensionColor = {r=255, g=255, b=0, a=200}
|
2025-06-07 08:51:21 +02:00
|
|
|
end
|
2025-07-02 06:59:46 +02:00
|
|
|
|
|
|
|
DrawTextOnScreen("Tension: " .. GetTensionText(ropeLength), 0.5, 0.08, 0.4, tensionColor)
|
|
|
|
else
|
|
|
|
Wait(1000) -- Wait longer if not controlling the winch
|
2025-06-07 08:51:21 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end)
|
2025-07-02 06:59:46 +02:00
|
|
|
|
|
|
|
-- Helper function to draw text on screen
|
|
|
|
function DrawTextOnScreen(text, x, y, scale, color)
|
|
|
|
SetTextFont(4)
|
|
|
|
SetTextProportional(true)
|
|
|
|
SetTextScale(scale, scale)
|
|
|
|
SetTextColour(color.r, color.g, color.b, color.a)
|
|
|
|
SetTextDropShadow(0, 0, 0, 0, 255)
|
|
|
|
SetTextEdge(2, 0, 0, 0, 150)
|
|
|
|
SetTextDropShadow()
|
|
|
|
SetTextOutline()
|
|
|
|
SetTextCentre(true)
|
|
|
|
SetTextEntry("STRING")
|
|
|
|
AddTextComponentString(text)
|
|
|
|
DrawText(x, y)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Helper function to get tension text based on rope length
|
|
|
|
function GetTensionText(length)
|
|
|
|
if length < 3.0 then
|
|
|
|
return "HIGH"
|
|
|
|
elseif length < 6.0 then
|
|
|
|
return "MEDIUM"
|
|
|
|
else
|
|
|
|
return "LOW"
|
|
|
|
end
|
|
|
|
end
|