vRP = nil ESX = nil if ConfigHose.Notifications.Enabled and ConfigHose.Notifications.Framework.vRP then local Tunnel = module("vrp", "lib/Tunnel") local Proxy = module("vrp", "lib/Proxy") vRP = Proxy.getInterface("vRP") end if ConfigHose.Notifications.Enabled and ConfigHose.Notifications.Framework.ESX then ESX = exports["es_extended"]:getSharedObject() end if ConfigHose.EnablePositioningCommand then TriggerEvent('chat:addSuggestion', '/'.."findhosepositioning", "Find rope spawn point with rotation", { { name="type", help="hose or hand" } }) RegisterCommand("findhosepositioning", function(source, args) local ropeLabel = (args[1] or "hose"):lower() local ped = PlayerPedId() local ropeType = 4 if RopeManager.RopeTypes and RopeManager.RopeTypes[ropeLabel] then ropeType = RopeManager.RopeTypes[ropeLabel] end local targetVehicle = GetVehiclePedIsIn(ped, false) if targetVehicle == 0 then targetVehicle = GetClosestVehicle(GetEntityCoords(ped)) end if targetVehicle == 0 then return Notify("No vehicle found!") end RopeLoadTextures() local vehPos = GetEntityCoords(targetVehicle) local previewRope = AddRope(vehPos.x, vehPos.y, vehPos.z, 0.0, 0.0, 0.0, 20.0, ropeType, 20.0, 0.5, 1.0, false, false, false, 5.0, false, 0) local offSet = {x = 0.0, y = 0.0, z = 0.0} local rotation = {x = 0.0, y = 0.0, z = 0.0} local depth = 0.2 local mode = "MOVE" local editing = true Notify("~b~POSITIONING MODE~w~\n[TAB] Toggle Move/Rotate\n[Arrows] Adjust\n[INSERT/DELETE] Depth\n[ENTER] Save") SetEntityAlpha(targetVehicle, 150, false) while editing do Wait(0) local speed = IsControlPressed(0, 21) and 0.05 or 0.005 if IsControlJustPressed(0, 192) then mode = (mode == "MOVE") and "ROTATE" or "MOVE" Notify("Mode: ~y~" .. mode) end if IsControlPressed(0, 121) then depth = depth + 0.005 end if IsControlPressed(0, 178) then depth = depth - 0.005 end local up = IsControlPressed(0, ConfigHose.PositioningControls.up) local down = IsControlPressed(0, ConfigHose.PositioningControls.down) local forward = IsControlPressed(0, ConfigHose.PositioningControls.forwards) local backward = IsControlPressed(0, ConfigHose.PositioningControls.backwards) local left = IsControlPressed(0, ConfigHose.PositioningControls.left) local right = IsControlPressed(0, ConfigHose.PositioningControls.right) if mode == "MOVE" then if up then offSet.z = offSet.z + speed end if down then offSet.z = offSet.z - speed end if forward then offSet.y = offSet.y + speed end if backward then offSet.y = offSet.y - speed end if left then offSet.x = offSet.x - speed end if right then offSet.x = offSet.x + speed end else local rSpeed = speed * 50.0 if up then rotation.x = rotation.x + rSpeed end if down then rotation.x = rotation.x - rSpeed end if left then rotation.z = rotation.z + rSpeed end if right then rotation.z = rotation.z - rSpeed end end local forwardVec = RotationToDirection(rotation) local localPortPos = vector3(offSet.x, offSet.y, offSet.z) local localBendPosA = localPortPos - (forwardVec * depth) local localBendPosB = localPortPos - (forwardVec * (depth * 0.5)) local worldPointC = GetOffsetFromEntityInWorldCoords(targetVehicle, localPortPos.x, localPortPos.y, localPortPos.z) local worldPointA = GetOffsetFromEntityInWorldCoords(targetVehicle, localBendPosA.x, localBendPosA.y, localBendPosA.z) local worldPointB = GetOffsetFromEntityInWorldCoords(targetVehicle, localBendPosB.x, localBendPosB.y, localBendPosB.z) local handPos = GetWorldPositionOfEntityBone(ped, GetPedBoneIndex(ped, 6286)) local vertexCount = GetRopeVertexCount(previewRope) PinRopeVertex(previewRope, 0, handPos.x, handPos.y, handPos.z) PinRopeVertex(previewRope, vertexCount - 1, worldPointA.x, worldPointA.y, worldPointA.z) PinRopeVertex(previewRope, vertexCount - 2, worldPointB.x, worldPointB.y, worldPointB.z) PinRopeVertex(previewRope, vertexCount - 3, worldPointC.x, worldPointC.y, worldPointC.z) if IsControlJustPressed(0, ConfigHose.PositioningControls.enter) then editing = false end end ResetEntityAlpha(targetVehicle) print(string.format("^2--- %s RESULT ---^7", ropeLabel:upper())) if ropeLabel == "hose" then print(string.format('{ x = %.3f, y = %.3f, z = %.3f, rx = %.3f, ry = %.3f, rz = %.3f, depth = %.3f, type = "%s", portId = "attack1" },', offSet.x, offSet.y, offSet.z, rotation.x, rotation.y, rotation.z, depth, ropeLabel)) else print(string.format('{ x = %.3f, y = %.3f, z = %.3f, rx = %.3f, ry = %.3f, rz = %.3f, depth = %.3f, type = "%s" },', offSet.x, offSet.y, offSet.z, rotation.x, rotation.y, rotation.z, depth, ropeLabel)) end DeleteRope(previewRope) Notify("Data printed to F8 console.") end, false) end function Notify(text) if not ConfigHose.Notifications.Enabled then return end if ConfigHose.Notifications.Framework.ESX then if ESX ~= nil then ESX.ShowNotification(text) end elseif ConfigHose.Notifications.Framework.QBCore then TriggerEvent('QBCore:Notify', text, 'primary') elseif ConfigHose.Notifications.Framework.QBX then exports.qbx_core:Notify(text, 'primary') elseif ConfigHose.Notifications.Framework.vRP then vRP.notify(source, {text}) elseif ConfigHose.Notifications.Framework.okok then exports['okokNotify']:Alert("Smart Hose", text, 2000, 'info', true) else showBaseNotification(text) end end function showBaseNotification(message) -- Base game notifications SetNotificationTextEntry("STRING") AddTextComponentString(message) DrawNotification(0,1) end -- @GeneralFunctions function DisplayHelpText(text) SetTextComponentFormat("STRING") AddTextComponentString(text) DisplayHelpTextFromStringLabel(0, 0, ConfigHose.Notifications.HelpTextSound, -1) end function drawInstructionalText(msg, coords) AddTextEntry('instructionalText', msg) SetFloatingHelpTextWorldPosition(1, coords) SetFloatingHelpTextStyle(1, 1, 2, -1, 3, 0) BeginTextCommandDisplayHelp('instructionalText') AddTextComponentSubstringPlayerName(msg) EndTextCommandDisplayHelp(2, false, false, -1) end function IsFireHoseEnabled() return (HoseManager.hasFireHose and not HoseManager.nozzleDropped) end exports("IsFireHoseEnabled", IsFireHoseEnabled) function IsFireHoseShooting() return HoseManager.pressed end exports("IsFireHoseShooting", IsFireHoseShooting) function IsFireHoseShootingWater() return ParticleManager.isWaterDecal end exports("IsFireHoseShootingWater", IsFireHoseShootingWater) function IsVehicleUsingWater(vehicleNetID) local settings = HoseManager.vehicleSettings[vehicleNetID] if not settings then return true end return settings.decalType end exports("IsVehicleUsingWater", IsVehicleUsingWater) function GetCurrentDecalCoords() return ParticleManager.CurrentDecalCoords end exports("GetCurrentDecalCoords", GetCurrentDecalCoords) function GetCurrentHosePressure() return ParticleManager.particleScale end exports("GetCurrentHosePressure", GetCurrentHosePressure) if ConfigHose.InteractType.TruckInteraction.Custom then local function _toNetId(ent) if ent and DoesEntityExist(ent) then return ToNetId(ent) end return nil end local function _neighborsFor(netId) local list = {} if not netId then return list end local RM = RopeManager or {} local cv = RM.connectedVehicles and RM.connectedVehicles[netId] if type(cv) == 'number' then list[#list+1] = cv elseif type(cv) == 'table' then for nid,_ in pairs(cv) do list[#list+1] = tonumber(nid) or nid end end local ic = RM.incomingConnections and RM.incomingConnections[netId] if type(ic) == 'number' then list[#list+1] = ic elseif type(ic) == 'table' then for nid,_ in pairs(ic) do list[#list+1] = tonumber(nid) or nid end end return list end local function _buildCtx(vehicle) local ped = PlayerPedId() local pos = GetEntityCoords(ped) local model = GetEntityModel(vehicle) local vs = (ConfigHose.Rope.VehicleSettings[model] or {}) local useBone = vs.useBone or false local bones = vs.bones or {} local offsets = vs.offsets or { { x = 0.0, y = 0.0, z = 0.0 } } local pts = getAttachmentPoints(vehicle, useBone, bones, offsets) if type(pts) ~= 'table' or #pts == 0 then pts = { GetEntityCoords(vehicle) } end local closest = pts[1] local minDist = #(pos - closest) for i = 2, #pts do local p = pts[i] local d = #(pos - p) if d < minDist then closest, minDist = p, d end end local meId = GetPlayerServerId(PlayerId()) local vehNet = _toNetId(vehicle) local neigh = _neighborsFor(vehNet) return { ped = ped, playerServerId = meId, vehicle = vehicle, vehNet = vehNet, vehModel = model, promptCoords = closest, vehicleDistance = minDist, neighbors = neigh, nozzle = HoseManager.nozzle, nozzleDropped = HoseManager.nozzleDropped, currentVehicle = HoseManager.currentVehicle, handLineActive = HoseManager.handLine[meId] and true or false, currentVehicleRelay = HoseManager.currentVehicleRelay, supplyEnabled = IsSupplyLineEnabled(), } end -- Actions you can call from your custom interactions HoseCustomActions = { GrabHose = function(ctx) HoseManager.currentVehicle = ctx.vehicle InitializeFireHose() AttachRopeToProp() end, StoreHose = function(ctx) if HoseManager.nozzle and HoseManager.currentVehicle == ctx.vehicle and not HoseManager.nozzleDropped then UnequipFireHose() else Notify(ConfigHose.Translations.cannotStoreHoseWhileOnGround) end end, GrabRelay = function(ctx) HoseManager.currentVehicleRelay = ctx.vehicle InitializeHandLine() AttachRopeToHand() end, StoreRelay = function(_ctx) DeinitializeHandLine() end, ConnectRelayHere = function(ctx) DeinitializeHandLine() ConnectRelayToVehicle(ctx.vehicle) end, QuickDisconnectNearest = function(ctx) if not ctx.vehNet or #ctx.neighbors == 0 then return end local best, bestD, myPos = nil, 1e9, GetEntityCoords(PlayerPedId()) for _, n in ipairs(ctx.neighbors) do local ent = NetEnt(n) if ent and DoesEntityExist(ent) then local d = #(myPos - GetEntityCoords(ent)) if d < bestD then best, bestD = n, d end elseif not best then best = n end end if best then local sId, tId = canonicalPair(ctx.vehNet, best) DisconnectRelayLine(sId, tId, ctx.vehNet) end end, OpenPanel = function(_ctx) OpenControlPanel() end } -- ctx = _buildCtx(vehicle) (state for the vehicle/player) -- A = HoseCustomActions (ready actions) -- Draw helpers available: drawInstructionalText() / DisplayHelpText() from this file. -- Example below mirrors the original E/G/Z flow aka ConfigHose.TruckInteraction.InteractType.Drawtext. if not CustomInteractThink then -- CustomInteractThink = function(ctx, A) -- local lines = {} -- if ctx.nozzle then -- if ctx.currentVehicle == ctx.vehicle and not ctx.nozzleDropped then -- lines[#lines+1] = ConfigHose.Translations.storeHoseMessage -- if IsControlJustReleased(0, ConfigHose.Keys.Interact) then A.StoreHose(ctx) end -- else -- lines[#lines+1] = ConfigHose.Translations.cannotStoreHoseWhileOnGround -- end -- elseif ctx.handLineActive then -- if ctx.supplyEnabled then -- if ctx.currentVehicleRelay == ctx.vehicle then -- if #ctx.neighbors > 0 then lines[#lines+1] = ConfigHose.Translations.disconnectLine end -- lines[#lines+1] = ConfigHose.Translations.storeSupplyLine -- if IsControlJustReleased(0, ConfigHose.Keys.GrabLine) then -- if #ctx.neighbors > 0 then A.QuickDisconnectNearest(ctx) else A.StoreRelay(ctx) end -- end -- else -- if #ctx.neighbors > 0 then -- lines[#lines+1] = ConfigHose.Translations.disconnectLine -- if IsControlJustReleased(0, ConfigHose.Keys.GrabLine) then A.QuickDisconnectNearest(ctx) end -- else -- lines[#lines+1] = ConfigHose.Translations.connectRelayLineMessage -- if IsControlJustReleased(0, ConfigHose.Keys.GrabLine) then A.ConnectRelayHere(ctx) end -- end -- end -- end -- else -- if ctx.supplyEnabled then -- if #ctx.neighbors > 0 then -- lines[#lines+1] = ConfigHose.Translations.grabHoseMessage -- lines[#lines+1] = ConfigHose.Translations.disconnectLine -- else -- lines[#lines+1] = ConfigHose.Translations.grabHoseMessage -- lines[#lines+1] = ConfigHose.Translations.SupplyLine -- end -- else -- lines[#lines+1] = ConfigHose.Translations.grabHoseMessage -- end -- if IsControlJustReleased(0, ConfigHose.Keys.Interact) then A.GrabHose(ctx) end -- if ctx.supplyEnabled and IsControlJustReleased(0, ConfigHose.Keys.GrabLine) then A.GrabRelay(ctx) end -- end -- -- SHS Panel -- if not ctx.nozzle and ctx.supplyEnabled then -- local showPanelHint = (ctx.handLineActive and ctx.currentVehicleRelay == ctx.vehicle) or (not ctx.nozzle and not ctx.handLineActive) -- if showPanelHint then -- lines[#lines+1] = ConfigHose.Translations.interactDisplay -- if IsControlJustReleased(0, ConfigHose.Keys.OpenDisplayPrompt) then A.OpenPanel(ctx) end -- end -- end -- if #lines > 0 then drawInstructionalText(table.concat(lines, "\n"), ctx.promptCoords) end -- end CustomInteractThink = function(ctx, A) -- Write logic here -- ctx = live state for the closest fire vehicle -- A = actions you can call end end CreateThread(function() while true do Wait(1) local ped = PlayerPedId() if IsPedInAnyVehicle(ped, false) then Wait(500) else local pos = GetEntityCoords(ped) local vehicle = GetClosestVehicle(pos) if vehicle and DoesEntityExist(vehicle) and IsVehicleDriveable(vehicle, false) and HasWaterTank(vehicle) then local ctx = _buildCtx(vehicle) if ctx.vehicleDistance < 3.0 then local ok, err = pcall(CustomInteractThink, ctx, HoseCustomActions) if not ok then print("^1[CustomInteract] error:^0", err) end else Wait(250) end else Wait(750) end end end end) end