forked from Simnation/Main
1938 lines
70 KiB
Lua
1938 lines
70 KiB
Lua
-- Variables --
|
|
local CURRENT_RESOURCE = GetCurrentResourceName()
|
|
local MODELS <const> = GetModels()
|
|
local POLY_ZONES <const> = GetPolyZones()
|
|
|
|
-- Ignore this list, it's only used to detect NPC's sitting around and about, and has nothing to do with what scenarios players can use.
|
|
local SITTING_SCENARIOS <const> = {
|
|
"WORLD_HUMAN_SEAT_LEDGE", "WORLD_HUMAN_SEAT_LEDGE_EATING", "WORLD_HUMAN_SEAT_STEPS", "WORLD_HUMAN_SEAT_WALL", "WORLD_HUMAN_SEAT_WALL_EATING", "WORLD_HUMAN_SEAT_WALL_TABLET",
|
|
"PROP_HUMAN_SEAT_ARMCHAIR", "PROP_HUMAN_SEAT_BAR", "PROP_HUMAN_SEAT_BENCH", "PROP_HUMAN_SEAT_BENCH_FACILITY", "PROP_HUMAN_SEAT_BENCH_DRINK", "PROP_HUMAN_SEAT_BENCH_DRINK_FACILITY",
|
|
"PROP_HUMAN_SEAT_BENCH_DRINK_BEER", "PROP_HUMAN_SEAT_BENCH_FOOD", "PROP_HUMAN_SEAT_BENCH_FOOD_FACILITY", "PROP_HUMAN_SEAT_BUS_STOP_WAIT", "PROP_HUMAN_SEAT_CHAIR",
|
|
"PROP_HUMAN_SEAT_CHAIR_DRINK", "PROP_HUMAN_SEAT_CHAIR_DRINK_BEER", "PROP_HUMAN_SEAT_CHAIR_FOOD", "PROP_HUMAN_SEAT_CHAIR_UPRIGHT", "PROP_HUMAN_SEAT_CHAIR_MP_PLAYER",
|
|
"PROP_HUMAN_SEAT_COMPUTER", "PROP_HUMAN_SEAT_COMPUTER_LOW", "PROP_HUMAN_SEAT_DECKCHAIR", "PROP_HUMAN_SEAT_DECKCHAIR_DRINK", "PROP_HUMAN_SEAT_MUSCLE_BENCH_PRESS",
|
|
"PROP_HUMAN_SEAT_MUSCLE_BENCH_PRESS_PRISON", "PROP_HUMAN_SEAT_SEWING", "PROP_HUMAN_SEAT_STRIP_WATCH", "PROP_HUMAN_SEAT_SUNLOUNGER"
|
|
}
|
|
|
|
-- TODO: Add missing keys in the list + rename some of the mouse related ones
|
|
local SPECIAL_KEY_CODES <const> = {
|
|
['b_100'] = 'LMB', ['b_101'] = 'RMB', ['b_102'] = 'MMB', ['b_103'] = 'Mouse.ExtraBtn1', ['b_104'] = 'Mouse.ExtraBtn2', ['b_105'] = 'Mouse.ExtraBtn3', ['b_106'] = 'Mouse.ExtraBtn4', ['b_107'] = 'Mouse.ExtraBtn5', ['b_108'] = 'Mouse.ExtraBtn6', ['b_109'] = 'Mouse.ExtraBtn7', ['b_110'] = 'Mouse.ExtraBtn8', ['b_115'] = 'MouseWheel.Up', ['b_116'] = 'MouseWheel.Down', ['b_130'] = 'NumSubstract', ['b_131'] = 'NumAdd', ['b_134'] = 'Num Multiplication', ['b_135'] = 'Num Enter', ['b_137'] = 'Num1', ['b_138'] = 'Num2', ['b_139'] = 'Num3', ['b_140'] = 'Num4', ['b_141'] = 'Num5', ['b_142'] = 'Num6', ['b_143'] = 'Num7', ['b_144'] = 'Num8', ['b_145'] = 'Num9', ['b_170'] = 'F1', ['b_171'] = 'F2', ['b_172'] = 'F3', ['b_173'] = 'F4', ['b_174'] = 'F5', ['b_175'] = 'F6', ['b_176'] = 'F7', ['b_177'] = 'F8', ['b_178'] = 'F9', ['b_179'] = 'F10', ['b_180'] = 'F11', ['b_181'] = 'F12', ['b_182'] = 'F13', ['b_183'] = 'F14', ['b_184'] = 'F15', ['b_185'] = 'F16', ['b_186'] = 'F17', ['b_187'] = 'F18', ['b_188'] = 'F19', ['b_189'] = 'F20', ['b_190'] = 'F21', ['b_191'] = 'F22', ['b_192'] = 'F23', ['b_193'] = 'F24', ['b_194'] = 'Arrow Up', ['b_195'] = 'Arrow Down', ['b_196'] = 'Arrow Left', ['b_197'] = 'Arrow Right', ['b_198'] = 'Delete', ['b_199'] = 'Escape', ['b_200'] = 'Insert', ['b_201'] = 'End', ['b_210'] = 'Delete', ['b_211'] = 'Insert', ['b_212'] = 'End', ['b_1000'] = 'Shift', ['b_1002'] = 'Tab', ['b_1003'] = 'Enter', ['b_1004'] = 'Backspace', ['b_1009'] = 'PageUp', ['b_1008'] = 'Home', ['b_1010'] = 'PageDown', ['b_1012'] = 'CapsLock', ['b_1013'] = 'Control', ['b_1014'] = 'Right Control', ['b_1015'] = 'Alt', ['b_1055'] = 'Home', ['b_1056'] = 'PageUp', ['b_2000'] = 'Space'
|
|
}
|
|
|
|
local isSittingDisabled = false
|
|
local isLayingDisabled = false
|
|
local disableReasons = {
|
|
sit = {},
|
|
lay = {}
|
|
}
|
|
|
|
local metadata = {
|
|
isSitting = false,
|
|
isLaying = false,
|
|
entity = 0,
|
|
poly = false,
|
|
type = nil,
|
|
lastPos = nil,
|
|
targetPos = nil,
|
|
teleportOut = false,
|
|
frozen = false,
|
|
plyFrozen = false,
|
|
animation = {},
|
|
scenario = false,
|
|
showingPrompt = false,
|
|
attAction = false,
|
|
}
|
|
|
|
-- Ignore this if you don't use the TMC framework
|
|
local TMC = nil
|
|
if Config.Framework == "TMC" or Config.Notifications == "TMC" then
|
|
TMC = exports.core:getCoreObject()
|
|
end
|
|
|
|
-- Functions --
|
|
local function DisplayNotification(msg)
|
|
if Config.Notifications == "OX" then
|
|
lib.notify({
|
|
title = 'Sit',
|
|
description = msg,
|
|
type = 'error'
|
|
})
|
|
elseif Config.Notifications == "QBCore" then
|
|
TriggerEvent('QBCore:Notify', msg, "error", 5000)
|
|
elseif Config.Notifications == "TMC" then
|
|
TMC.Functions.SimpleNotify(msg, "error", 5000)
|
|
else
|
|
-- Native Notifications
|
|
BeginTextCommandThefeedPost("STRING")
|
|
AddTextComponentSubstringPlayerName(msg)
|
|
EndTextCommandThefeedPostTicker(false, false)
|
|
|
|
-- Mythic Notify (Variant)
|
|
--exports.mythic_notify:SendAlert('error', msg)
|
|
end
|
|
end
|
|
|
|
-- Debug printing
|
|
local function DebugPrint(text)
|
|
if Config.Debugmode then
|
|
print(text)
|
|
end
|
|
end
|
|
|
|
---Get's the label of a key mapping command
|
|
---@param commandHash any
|
|
---@return string
|
|
local function GetKeyLabel(commandHash)
|
|
local key = GetControlInstructionalButton(0, commandHash | 0x80000000, true)
|
|
if string.find(key, "t_") then
|
|
local label, _count = string.gsub(key, "t_", "")
|
|
return label
|
|
else
|
|
return SPECIAL_KEY_CODES[key] or "unknown"
|
|
end
|
|
end
|
|
|
|
-- Add your type of persistent notifications here
|
|
local function ShowPersistentNotification(type)
|
|
CreateThread(function()
|
|
if Config.Notifications == "OX" then
|
|
lib.showTextUI(string.format(Config.Lang.KeyMapping.GetUp, GetKeyLabel(`getup`)))
|
|
|
|
while metadata[type] do
|
|
Wait(100)
|
|
end
|
|
|
|
lib.hideTextUI()
|
|
elseif Config.Notifications == "QBCore" then
|
|
exports["qb-core"]:DrawText(string.format(Config.Lang.KeyMapping.GetUp, GetKeyLabel(`getup`)), "left")
|
|
|
|
while metadata[type] do
|
|
Wait(100)
|
|
end
|
|
|
|
exports["qb-core"]:HideText()
|
|
elseif Config.Notifications == "TMC" then
|
|
TMC.Functions.Notify({
|
|
message = string.format(Config.Lang.KeyMapping.GetUp, GetKeyLabel(`getup`)),
|
|
id = 'sit_notif',
|
|
persist = true,
|
|
notifType = 'info'
|
|
})
|
|
|
|
while metadata[type] do
|
|
Wait(100)
|
|
end
|
|
|
|
TMC.Functions.StopNotify("sit_notif")
|
|
else
|
|
while metadata[type] do
|
|
if IsUsingKeyboard(1) then
|
|
DisplayHelpTextThisFrame("sit_getup_keyboard", false)
|
|
else
|
|
DisplayHelpTextThisFrame("sit_getup_controller", false)
|
|
end
|
|
Wait(0)
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
|
|
local function LoadAnimDict(dict)
|
|
RequestAnimDict(dict)
|
|
while not HasAnimDictLoaded(dict) do
|
|
Wait(10)
|
|
end
|
|
end
|
|
|
|
local function GetAmountOfSeats(model)
|
|
return #MODELS[model].sit.seats
|
|
end
|
|
|
|
local function HandleLooseEntity(entity)
|
|
if not IsEntityPositionFrozen(entity) then
|
|
NetworkRequestControlOfEntity(entity)
|
|
FreezeEntityPosition(entity, true)
|
|
metadata.frozen = true
|
|
end
|
|
end
|
|
|
|
local function UnhandleLooseEntity(entity)
|
|
if metadata.frozen then
|
|
FreezeEntityPosition(entity, false)
|
|
metadata.frozen = false
|
|
end
|
|
end
|
|
|
|
local function HeadingToRotation(heading)
|
|
local rotation = heading
|
|
if rotation > 180.0 then
|
|
rotation = 180.0 - math.abs(rotation - 180.0)
|
|
rotation = rotation*-1
|
|
end
|
|
return rotation
|
|
end
|
|
|
|
local function GetOffsetFromCoordsInWorldCoords(position, rotation, offset)
|
|
local rotX, rotY, rotZ = math.rad(rotation.x), math.rad(rotation.y), math.rad(rotation.z)
|
|
local cosX, cosY, cosZ = math.cos(rotX), math.cos(rotY), math.cos(rotZ)
|
|
local sinX, sinY, sinZ = math.sin(rotX), math.sin(rotY), math.sin(rotZ)
|
|
local matrix = {}
|
|
|
|
matrix[1] = {}
|
|
matrix[1][1] = cosZ * cosY - sinZ * sinX * sinY
|
|
matrix[1][2] = cosY * sinZ + cosZ * sinX * sinY
|
|
matrix[1][3] = -cosX * sinY
|
|
matrix[1][4] = 1
|
|
|
|
matrix[2] = {}
|
|
matrix[2][1] = -cosX * sinZ
|
|
matrix[2][2] = cosZ * cosX
|
|
matrix[2][3] = sinX
|
|
matrix[2][4] = 1
|
|
|
|
matrix[3] = {}
|
|
matrix[3][1] = cosZ * sinY + cosY * sinZ * sinX
|
|
matrix[3][2] = sinZ * sinY - cosZ * cosY * sinX
|
|
matrix[3][3] = cosX * cosY
|
|
matrix[3][4] = 1
|
|
|
|
matrix[4] = {}
|
|
matrix[4][1], matrix[4][2], matrix[4][3] = position.x, position.y, position.z
|
|
matrix[4][4] = 1
|
|
|
|
local x = offset.x * matrix[1][1] + offset.y * matrix[2][1] + offset.z * matrix[3][1] + matrix[4][1]
|
|
local y = offset.x * matrix[1][2] + offset.y * matrix[2][2] + offset.z * matrix[3][2] + matrix[4][2]
|
|
local z = offset.x * matrix[1][3] + offset.y * matrix[2][3] + offset.z * matrix[3][3] + matrix[4][3]
|
|
|
|
return vector3(x, y, z)
|
|
end
|
|
|
|
local function IsPedPlayingAnyLayAnim(ped)
|
|
local checked = {}
|
|
for _type, settings in pairs(Config.LayTypes) do
|
|
local anim = settings.animation
|
|
if not checked[anim.dict] then
|
|
if IsEntityPlayingAnim(ped, anim.dict, anim.name, 3) then
|
|
return true
|
|
else
|
|
checked[anim.dict] = true
|
|
end
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local function IsPedSitting(ped)
|
|
for _index, scenario in pairs(SITTING_SCENARIOS) do
|
|
if IsPedUsingScenario(ped, scenario) then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local function IsSeatAvailable(coords, action)
|
|
local playerPed = PlayerPedId()
|
|
for _index, ped in pairs(GetGamePool('CPed')) do
|
|
if ped ~= playerPed then
|
|
local dist = #(GetEntityCoords(ped)-coords)
|
|
if dist < 1.35 then
|
|
if action == 'sit' then
|
|
if IsPedPlayingAnyLayAnim(ped) or dist < 0.55 then
|
|
return false
|
|
end
|
|
elseif action == 'lay' then
|
|
if IsPedPlayingAnyLayAnim(ped) or IsPedSitting(ped) then
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
local function SeatSort(a, b)
|
|
return a.dist < b.dist
|
|
end
|
|
|
|
local function Raycast(startCoords, destination, ignoreEntity)
|
|
local rayHandle = StartShapeTestLosProbe(startCoords.x, startCoords.y, startCoords.z, destination.x, destination.y, destination.z, -1, ignoreEntity, 4)
|
|
|
|
while true do
|
|
local result, hit, endCoords, surfaceNormal, entityHit = GetShapeTestResult(rayHandle)
|
|
if result ~= 1 then
|
|
return hit, endCoords, surfaceNormal, entityHit
|
|
end
|
|
|
|
Wait(0)
|
|
end
|
|
end
|
|
|
|
local function RaycastCamera()
|
|
local worldVector, normalVector = GetWorldCoordFromScreenCoord(0.5, 0.5)
|
|
local destination = worldVector + normalVector * 10
|
|
local hit, endCoords = Raycast(worldVector, destination, PlayerPedId())
|
|
|
|
if hit then
|
|
return endCoords
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
local function SortSeatsByDistance(seatCoords, seats, raycast)
|
|
local sortedSeats = {}
|
|
local coords = GetEntityCoords(PlayerPedId())
|
|
|
|
if raycast and Config.Target and Config.UseTargetingCoords then
|
|
local endCoords = RaycastCamera()
|
|
if endCoords then
|
|
coords = endCoords
|
|
end
|
|
end
|
|
|
|
for k, v in pairs(seats) do
|
|
sortedSeats[k] = {}
|
|
if seatCoords then
|
|
local heading = seatCoords.w
|
|
local rotation = vector3(0.0, 0.0, HeadingToRotation(seatCoords.w))
|
|
sortedSeats[k].coords = GetOffsetFromCoordsInWorldCoords(seatCoords.xyz, rotation, v)
|
|
heading = heading + v.w
|
|
if heading > 360.0 then
|
|
heading = heading - 360.0
|
|
end
|
|
sortedSeats[k].heading = heading
|
|
else
|
|
sortedSeats[k].coords = v.xyz
|
|
sortedSeats[k].heading = v.w
|
|
end
|
|
sortedSeats[k].dist = #(coords-sortedSeats[k].coords)
|
|
end
|
|
table.sort(sortedSeats, SeatSort)
|
|
|
|
return sortedSeats
|
|
end
|
|
|
|
local function GetAvailableSeat(seatCoords, seats, raycast)
|
|
local coords = nil
|
|
local heading = nil
|
|
local sortedSeats = SortSeatsByDistance(seatCoords, seats, raycast)
|
|
|
|
for _index, data in pairs(sortedSeats) do
|
|
if IsSeatAvailable(data.coords, 'sit') then
|
|
coords = data.coords
|
|
heading = data.heading
|
|
break
|
|
end
|
|
end
|
|
|
|
return coords, heading
|
|
end
|
|
|
|
local function LeaveSeat(clearTask, clearTaskImmediately, waitIfAttached)
|
|
metadata.isSitting = false
|
|
metadata.isLaying = false
|
|
metadata.scenario = false
|
|
local playerPed = PlayerPedId()
|
|
|
|
if metadata.plyFrozen then
|
|
SetEntityCollision(playerPed, true, false)
|
|
FreezeEntityPosition(playerPed, false)
|
|
metadata.plyFrozen = false
|
|
end
|
|
|
|
if metadata.entity ~= 0 then
|
|
UnhandleLooseEntity(metadata.entity)
|
|
metadata.entity = 0
|
|
end
|
|
|
|
if clearTask or clearTaskImmediately then
|
|
if waitIfAttached then
|
|
-- Wait until the person is no longer attached to another ped (aka. getting escorted or carried).
|
|
CreateThread(function()
|
|
while true do
|
|
if not IsEntityAttachedToAnyPed(PlayerPedId()) then
|
|
break
|
|
end
|
|
Wait(200)
|
|
end
|
|
|
|
playerPed = PlayerPedId()
|
|
if clearTask then
|
|
ClearPedTasks(playerPed)
|
|
else
|
|
ClearPedTasksImmediately(playerPed)
|
|
end
|
|
end)
|
|
elseif clearTask then
|
|
ClearPedTasks(playerPed)
|
|
else
|
|
ClearPedTasksImmediately(playerPed)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function StopSitting()
|
|
if metadata.lastPos and (Config.AlwaysTeleportOutOfSeat or Config.TeleportToLastPosWhenNoRoute or Config.SitTypes[metadata.type].teleportOut or metadata.teleportOut) then
|
|
ClearPedTasks(PlayerPedId())
|
|
Wait(1500)
|
|
SetEntityCoords(PlayerPedId(), metadata.lastPos.x, metadata.lastPos.y, metadata.lastPos.z - 0.95, false, false, false, false)
|
|
end
|
|
|
|
LeaveSeat(true, false, false)
|
|
end
|
|
|
|
local function GetScenario(type)
|
|
local scenarios = Config.SitTypes[type].scenarios
|
|
if not scenarios then return false, vector4(0.0, 0.0, 0.0, 0.0) end
|
|
|
|
local index = 1
|
|
if #scenarios > 1 then
|
|
index = math.floor(math.random(100, #scenarios*100)/100 + 0.5)
|
|
end
|
|
|
|
return scenarios[index].name, scenarios[index].offset or Config.SitTypes['default'].scenarios[1].offset
|
|
end
|
|
|
|
local function IsPlayerDoingAnyAction()
|
|
if IsPedUsingScenario(PlayerPedId(), metadata.scenario) or metadata.isSitting or metadata.isLaying then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
local function CanPlayerReachSeat(destination, entity)
|
|
local playerPed = PlayerPedId()
|
|
local coords = GetEntityCoords(playerPed)
|
|
local start = vector3(coords.x, coords.y, coords.z+0.25)
|
|
|
|
local _hit, endCoords, _surfaceNormal, entityHit = Raycast(start, destination, playerPed)
|
|
while true do
|
|
-- If a ped stands in the way just ignore it and start a new raycast from behind them
|
|
if GetEntityType(entityHit) ~= 1 then
|
|
local dist = #(endCoords - destination)
|
|
if (dist < 0.5 or endCoords.x == 0.0) or entityHit == entity then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
else
|
|
_hit, endCoords, _surfaceNormal, entityHit = Raycast(GetEntityCoords(entityHit), destination, entityHit)
|
|
end
|
|
|
|
Wait(0)
|
|
end
|
|
end
|
|
|
|
-- Checks if the seat is "sitable"
|
|
local function CanPlayerSitInSeat(coords, heading, entity)
|
|
local rotation = HeadingToRotation(heading)
|
|
local start = GetOffsetFromCoordsInWorldCoords(coords, vector3(0.0, 0.0, rotation), vector3(0.0, 0.25, 0.0))
|
|
local destination = vector3(start.x, start.y, start.z+0.30)
|
|
|
|
local hit, _endCoords, _surfaceNormal, entityHit = Raycast(start, destination, entity)
|
|
if hit == 0 or entityHit == entity or entityHit == PlayerPedId() then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
---Find the closest sit or lay position of spesified type
|
|
---@param type string 'sit' | 'lay' | 'both'
|
|
---@param maxDistance number|nil the maximum distance to check
|
|
---@return boolean found if any position was found or not
|
|
---@return table closest table containing the data about the closest position
|
|
local function GetClosestPositionOfType(type, maxDistance)
|
|
local playerCoords = GetEntityCoords(PlayerPedId())
|
|
local closest = {
|
|
type = "unknown",
|
|
distance = maxDistance or 10000.0
|
|
}
|
|
|
|
local entites = GetGamePool('CObject')
|
|
for _index, entity in pairs(entites) do
|
|
local model = GetEntityModel(entity)
|
|
if MODELS[model] and (MODELS[model][type] or type == "both") then
|
|
local coords = GetEntityCoords(entity)
|
|
local dist = #(coords - playerCoords)
|
|
if dist < closest.distance then
|
|
closest.type = (MODELS[model].sit and MODELS[model].lay and "both") or (MODELS[model].sit and "sit") or (MODELS[model].lay and "lay") or "unknown"
|
|
closest.entity = entity
|
|
closest.coords = coords
|
|
closest.distance = dist
|
|
end
|
|
end
|
|
end
|
|
|
|
for group, data in pairs(POLY_ZONES) do
|
|
if data.enabled then
|
|
if not data.radius or (#(data.center.xy - playerCoords.xy) < data.radius) then
|
|
for name, information in pairs(data.polys) do
|
|
for typeset, info in pairs(information) do
|
|
if typeset == type or (type == "both" and (typeset == "sit" or typeset == "lay")) then
|
|
for _index, coords in pairs(info.seats) do
|
|
local dist = #(playerCoords - coords.xyz)
|
|
if dist < closest.distance then
|
|
closest.type = (information.sit and information.lay and "both") or (information.sit and "sit") or (information.lay and "lay") or "unknown"
|
|
closest.entity = 0
|
|
closest.coords = coords.xyz
|
|
closest.distance = dist
|
|
closest.name = name
|
|
closest.group = group
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local found = closest.type ~= "unknown"
|
|
return found, found and closest or {}
|
|
end
|
|
|
|
local function SitOnSeat(data)
|
|
metadata.attAction = true
|
|
metadata.entity = data.entity
|
|
metadata.poly = data.poly
|
|
metadata.type = data.sit.type
|
|
|
|
local seat = data.sit
|
|
local settings = Config.SitTypes[seat.type]
|
|
local seatLocation = nil
|
|
|
|
if not settings then
|
|
if seat.type == nil then
|
|
if data.entity ~= 0 then
|
|
warn(string.format("No sit type was set for model with hash %s, the default settings were used instead!", GetEntityModel(data.entity)))
|
|
else
|
|
warn(string.format("No sit type was set for poly '%s', the default settings were used instead!", data.poly))
|
|
end
|
|
else
|
|
warn(string.format("No settings was set for sit type '%s' in Config.SitTypes, the default settings were used instead!", seat.type))
|
|
end
|
|
|
|
seat.type = 'default'
|
|
settings = Config.SitTypes['default']
|
|
end
|
|
|
|
if data.entity ~= nil and data.entity ~= 0 then
|
|
local rot = GetEntityRotation(data.entity)
|
|
local xRot = rot.x
|
|
local yRot = rot.y
|
|
|
|
if xRot < 0.0 then xRot = xRot*-1 end
|
|
if yRot < 0.0 then yRot = yRot*-1 end
|
|
|
|
local tilt = xRot + yRot
|
|
if tilt > Config.MaxTilt then
|
|
DisplayNotification(Config.Lang.Notification.TooTilted)
|
|
metadata.attAction = false
|
|
return
|
|
end
|
|
|
|
local seatCoords = GetEntityCoords(data.entity)
|
|
seatLocation = vector4(seatCoords.x, seatCoords.y, seatCoords.z, GetEntityHeading(data.entity))
|
|
end
|
|
|
|
local coords, heading = GetAvailableSeat(seatLocation, seat.seats, data.raycast)
|
|
if coords == nil then
|
|
local model = GetEntityModel(data.entity)
|
|
if model ~= 0 and GetAmountOfSeats(model) ~= 1 then
|
|
DisplayNotification(Config.Lang.Notification.NoAvailable)
|
|
else
|
|
DisplayNotification(Config.Lang.Notification.OccupiedSit)
|
|
end
|
|
metadata.attAction = false
|
|
return
|
|
end
|
|
|
|
if heading == nil then
|
|
DisplayNotification(Config.Lang.Notification.NoAvailable)
|
|
metadata.attAction = false
|
|
error("Heading of seat was nil!")
|
|
return
|
|
end
|
|
|
|
local skipReachCheck = seat.skipSeeCheck or false
|
|
if not skipReachCheck and not CanPlayerReachSeat(coords, data.entity) then
|
|
DisplayNotification(Config.Lang.Notification.CannotReachSeat)
|
|
metadata.attAction = false
|
|
return
|
|
end
|
|
|
|
if data.entity ~= 0 and not CanPlayerSitInSeat(coords, heading, data.entity) then
|
|
DisplayNotification(Config.Lang.Notification.CannotSitInSeat)
|
|
metadata.attAction = false
|
|
return
|
|
end
|
|
|
|
local playerPed = PlayerPedId()
|
|
if IsPedInAnyVehicle(playerPed, true) then
|
|
DisplayNotification(Config.Lang.Notification.InVehicleSit)
|
|
metadata.attAction = false
|
|
return
|
|
end
|
|
|
|
-- Scenario offsets
|
|
local scenario, offset = GetScenario(seat.type)
|
|
heading = heading + offset.w
|
|
if heading > 360.0 then
|
|
heading = heading - 360.0
|
|
end
|
|
|
|
local rotation = HeadingToRotation(heading)
|
|
coords = GetOffsetFromCoordsInWorldCoords(coords, vector3(0.0, 0.0, rotation), offset.xyz)
|
|
local lastPos = GetEntityCoords(playerPed)
|
|
|
|
metadata.teleportOut = false
|
|
metadata.lastPos = nil
|
|
if Config.AlwaysTeleportOutOfSeat or settings.teleportOut or seat.teleportOut then
|
|
metadata.teleportOut = true
|
|
metadata.lastPos = lastPos
|
|
end
|
|
|
|
-- If we are already sitting then leave the current seat first, however if we are attempting to sit on the current seat then stop sitting.
|
|
if metadata.isSitting or metadata.isLaying then
|
|
if #(coords-lastPos) < 0.2 then
|
|
StopSitting()
|
|
metadata.attAction = false
|
|
return
|
|
else
|
|
if metadata.teleportOut then
|
|
LeaveSeat(false, true, false)
|
|
else
|
|
LeaveSeat(true, false, false)
|
|
Wait(2000)
|
|
end
|
|
metadata.entity = data.entity
|
|
end
|
|
end
|
|
|
|
metadata.scenario = scenario
|
|
metadata.isLaying = false
|
|
metadata.animation = {}
|
|
|
|
ClearPedTasks(playerPed)
|
|
if data.entity ~= 0 then
|
|
HandleLooseEntity(data.entity)
|
|
end
|
|
|
|
local timeout = settings.timeout or Config.SitTypes['default'].timeout
|
|
local skipGoStraightTask = settings.skipGoStraightTask
|
|
local prevDist = #(coords.xy - GetEntityCoords(playerPed).xy)
|
|
local dist = prevDist
|
|
local teleport = Config.AlwaysTeleportToSeat or seat.teleportIn or settings.teleportIn
|
|
local breakCounter = 0
|
|
local tick = 0
|
|
|
|
if not teleport and not skipGoStraightTask then
|
|
local gotoCoords = GetOffsetFromCoordsInWorldCoords(coords, vector3(0.0, 0.0, rotation), vector3(0.0, 0.695, 0.0))
|
|
TaskGoStraightToCoord(playerPed, gotoCoords.x, gotoCoords.y, gotoCoords.z, 1, timeout*500, heading, 0.15)
|
|
|
|
while true do
|
|
Wait(500)
|
|
if not metadata.attAction then
|
|
return
|
|
end
|
|
|
|
local playerCoords = GetEntityCoords(playerPed)
|
|
dist = #(gotoCoords.xy - playerCoords.xy)
|
|
tick = tick + 1
|
|
|
|
if dist < prevDist then
|
|
lastPos = playerCoords
|
|
prevDist = dist
|
|
end
|
|
|
|
local diff = math.abs(dist - prevDist)
|
|
local taskStatus = GetScriptTaskStatus(playerPed, "SCRIPT_TASK_GO_STRAIGHT_TO_COORD")
|
|
|
|
if taskStatus == 0 or taskStatus == 7 then
|
|
break
|
|
elseif tick > timeout then
|
|
break
|
|
elseif dist > prevDist+0.1 and dist > 0.85 then
|
|
breakCounter = breakCounter + 1
|
|
elseif diff <= 0.085 and dist < Config.MaxInteractionDist and dist > 0.05 and tick > 1 then
|
|
breakCounter = breakCounter + 1
|
|
else
|
|
breakCounter = breakCounter - 1
|
|
if breakCounter < 0 then
|
|
breakCounter = 0
|
|
end
|
|
end
|
|
|
|
if breakCounter > 2 and seat.type ~= "sunlounger" then
|
|
break
|
|
end
|
|
end
|
|
teleport = dist > 0.5 or false
|
|
tick = 0
|
|
end
|
|
|
|
if metadata.scenario then
|
|
metadata.targetPos = coords
|
|
TaskStartScenarioAtPosition(playerPed, metadata.scenario, coords.x, coords.y, coords.z, heading, -1, false, teleport)
|
|
|
|
while true do
|
|
Wait(500)
|
|
local playerCoords = GetEntityCoords(playerPed)
|
|
dist = #(coords.xy - playerCoords.xy)
|
|
tick = tick + 1
|
|
|
|
local taskStatus = GetScriptTaskStatus(playerPed, "SCRIPT_TASK_START_SCENARIO_AT_POSITION")
|
|
if taskStatus == 0 or taskStatus == 7 then
|
|
break
|
|
elseif IsPedUsingScenario(playerPed, metadata.scenario) and dist < 0.40 then
|
|
metadata.isSitting = true
|
|
break
|
|
elseif tick > timeout then
|
|
break
|
|
elseif not IsPedUsingScenario(playerPed, metadata.scenario) then
|
|
break
|
|
end
|
|
end
|
|
else
|
|
local animation = settings.animation
|
|
if animation.offset then
|
|
coords = GetOffsetFromCoordsInWorldCoords(coords, vector3(0.0, 0.0, rotation), animation.offset.xyz)
|
|
heading = heading+animation.offset.w
|
|
end
|
|
|
|
metadata.targetPos = coords
|
|
|
|
SetEntityCollision(playerPed, false, false)
|
|
FreezeEntityPosition(playerPed, true)
|
|
SetEntityCoords(playerPed, coords.x, coords.y, coords.z, false, false, false, false)
|
|
SetEntityHeading(playerPed, heading)
|
|
|
|
LoadAnimDict(animation.dict)
|
|
TaskPlayAnim(playerPed, animation.dict, animation.name, 2.0, 2.0, -1, animation.flag or 1, 0, false, false, false)
|
|
RemoveAnimDict(animation.dict)
|
|
metadata.plyFrozen = true
|
|
metadata.isSitting = true
|
|
metadata.animation = animation
|
|
end
|
|
|
|
if metadata.isSitting then
|
|
Wait(350)
|
|
if Config.ShowHelpText then
|
|
TriggerEvent('sit:helpTextThread', 'isSitting')
|
|
end
|
|
TriggerEvent('sit:checkThread', 'isSitting')
|
|
TriggerEvent('sit:onSit')
|
|
elseif dist <= 2.0 then
|
|
TaskStartScenarioAtPosition(playerPed, metadata.scenario, coords.x, coords.y, coords.z, heading, -1, false, true)
|
|
metadata.lastPos = lastPos
|
|
metadata.isSitting = true
|
|
|
|
Wait(350)
|
|
if Config.ShowHelpText then
|
|
TriggerEvent('sit:helpTextThread', 'isSitting')
|
|
end
|
|
TriggerEvent('sit:checkThread', 'isSitting')
|
|
TriggerEvent('sit:onSit')
|
|
else
|
|
LeaveSeat(true, false, false)
|
|
end
|
|
|
|
metadata.attAction = false
|
|
end
|
|
|
|
local function SitOnClosestSeat()
|
|
if metadata.attAction then
|
|
DisplayNotification(Config.Lang.Notification.AlreadyAttemptingToSit)
|
|
return
|
|
end
|
|
|
|
local found, closest = GetClosestPositionOfType("sit", Config.MaxInteractionDist)
|
|
if not found then
|
|
DisplayNotification(Config.Lang.Notification.NoFound)
|
|
elseif closest.name ~= nil then
|
|
local seat = POLY_ZONES[closest.group].polys[closest.name]
|
|
SitOnSeat({entity = 0, poly = closest.name, sit = seat.sit, raycast = false})
|
|
elseif closest.entity ~= 0 then
|
|
SitOnSeat({entity = closest.entity, poly = false, sit = MODELS[GetEntityModel(closest.entity)].sit, raycast = false})
|
|
else
|
|
error('SitOnClosestSeat: Found a chair, but it was missing critical data')
|
|
end
|
|
end
|
|
|
|
local function LayDownOnBed(data)
|
|
metadata.attAction = true
|
|
metadata.isSitting = false
|
|
metadata.plyFrozen = true
|
|
metadata.scenario = false
|
|
metadata.teleportOut = false
|
|
metadata.entity = data.entity
|
|
metadata.poly = data.poly
|
|
metadata.type = data.bed.type
|
|
|
|
local bed = data.bed
|
|
local bedLocation = nil
|
|
|
|
if not Config.LayTypes[bed.type] then
|
|
if bed.type == nil then
|
|
if data.entity ~= 0 then
|
|
warn(string.format("No lay type was set for model with hash %s, the default settings were used instead!", GetEntityModel(data.entity)))
|
|
else
|
|
warn(string.format("No lay type was set for poly '%s', the default settings were used instead!", data.poly))
|
|
end
|
|
else
|
|
warn(string.format("No lay animation settings was set for type '%s' in Config.LayTypes, the default settings were used instead!", bed.type))
|
|
end
|
|
|
|
bed.type = 'default'
|
|
end
|
|
|
|
if data.entity then
|
|
local rot = GetEntityRotation(data.entity)
|
|
local xRot = rot.x
|
|
local yRot = rot.y
|
|
|
|
if xRot < 0.0 then xRot = xRot*-1 end
|
|
if yRot < 0.0 then yRot = yRot*-1 end
|
|
|
|
local tilt = xRot + yRot
|
|
if tilt > Config.MaxTilt then
|
|
DisplayNotification(Config.Lang.Notification.TooTilted)
|
|
metadata.attAction = false
|
|
return
|
|
end
|
|
|
|
local bedCoords = GetEntityCoords(data.entity)
|
|
bedLocation = vector4(bedCoords.x, bedCoords.y, bedCoords.z, GetEntityHeading(data.entity))
|
|
end
|
|
|
|
local coords, heading = GetAvailableSeat(bedLocation, bed.seats, data.raycast)
|
|
if coords == nil then
|
|
local model = GetEntityModel(data.entity)
|
|
if Config.SitTypes[bed.type] and GetAmountOfSeats(model) ~= 1 then
|
|
DisplayNotification(Config.Lang.Notification.NoAvailable)
|
|
else
|
|
DisplayNotification(Config.Lang.Notification.OccupiedSit)
|
|
end
|
|
metadata.attAction = false
|
|
return
|
|
end
|
|
|
|
if heading == nil then
|
|
DisplayNotification(Config.Lang.Notification.NoAvailable)
|
|
metadata.attAction = false
|
|
error('Heading of bed was nil!')
|
|
return
|
|
end
|
|
|
|
if not IsSeatAvailable(coords, 'lay') then
|
|
DisplayNotification(Config.Lang.Notification.OccupiedLay)
|
|
metadata.attAction = false
|
|
return
|
|
end
|
|
|
|
local skipReachCheck = bed.skipSeeCheck or false
|
|
if not skipReachCheck and not CanPlayerReachSeat(coords, data.entity) then
|
|
DisplayNotification(Config.Lang.Notification.CannotReachBed)
|
|
metadata.attAction = false
|
|
return
|
|
end
|
|
|
|
local playerPed = PlayerPedId()
|
|
if IsPedInAnyVehicle(playerPed, true) then
|
|
DisplayNotification(Config.Lang.Notification.InVehicleLay)
|
|
metadata.attAction = false
|
|
return
|
|
end
|
|
|
|
local animation = Config.LayTypes[bed.type].animation
|
|
metadata.animation = animation
|
|
|
|
if Config.AlwaysTeleportOutOfSeat or Config.LayTypes[bed.type].teleportOut or bed.teleportOut then
|
|
metadata.teleportOut = true
|
|
metadata.lastPos = GetEntityCoords(playerPed)
|
|
end
|
|
|
|
if animation.offset then
|
|
coords = GetOffsetFromCoordsInWorldCoords(coords, vector3(0.0, 0.0, HeadingToRotation(heading)), animation.offset.xyz)
|
|
heading = heading+animation.offset.w
|
|
if heading > 360 then
|
|
heading = heading - 360
|
|
end
|
|
end
|
|
|
|
LoadAnimDict(animation.dict)
|
|
ClearPedTasksImmediately(playerPed)
|
|
SetEntityCollision(playerPed, false, false) -- TODO: figure out why this is causing issues for some people but not others
|
|
FreezeEntityPosition(playerPed, true)
|
|
SetEntityCoords(playerPed, coords.x, coords.y, coords.z, false, false, false, false)
|
|
SetEntityHeading(playerPed, heading)
|
|
|
|
TaskPlayAnim(playerPed, animation.dict, animation.name, 2.0, 2.0, -1, animation.flag or 1, 0, false, false, false)
|
|
RemoveAnimDict(animation.dict)
|
|
|
|
Wait(350)
|
|
metadata.isLaying = true
|
|
metadata.attAction = false
|
|
metadata.targetPos = coords
|
|
if Config.ShowHelpText then
|
|
TriggerEvent('sit:helpTextThread', 'isLaying')
|
|
end
|
|
TriggerEvent('sit:checkThread', 'isLaying')
|
|
TriggerEvent('sit:onLay')
|
|
end
|
|
|
|
local function LayOnClosestBed()
|
|
if metadata.attAction then
|
|
DisplayNotification(Config.Lang.Notification.AlreadyAttemptingToLay)
|
|
return
|
|
end
|
|
|
|
local found, closest = GetClosestPositionOfType("lay", Config.MaxInteractionDist)
|
|
if not found then
|
|
DisplayNotification(Config.Lang.Notification.NoBedFound)
|
|
elseif closest.name ~= nil then
|
|
local seat = POLY_ZONES[closest.group].polys[closest.name]
|
|
LayDownOnBed({entity = 0, poly = closest.name, bed = seat.lay, raycast = false})
|
|
elseif closest.entity ~= 0 then
|
|
LayDownOnBed({entity = closest.entity, poly = false, bed = MODELS[GetEntityModel(closest.entity)].lay, raycast = false})
|
|
else
|
|
error('LayOnClosestBed: Found a bed, but it lacked critical data.')
|
|
end
|
|
end
|
|
|
|
local function GetUpFromBed()
|
|
local clearTask = true
|
|
local exitAnim = Config.LayTypes[metadata.type]?.exitAnim or Config.LayTypes['default'].exitAnim
|
|
metadata.isLaying = false
|
|
|
|
if metadata.teleportOut then
|
|
local playterPed = PlayerPedId()
|
|
ClearPedTasksImmediately(playterPed)
|
|
SetEntityCoords(playterPed, metadata.lastPos.x, metadata.lastPos.y, metadata.lastPos.z-0.95, false, false, false, false)
|
|
clearTask = false
|
|
elseif exitAnim then
|
|
local exitAnimType = Config.LayTypes[metadata.type]?.exitAnimType or Config.LayTypes['default'].exitAnimType
|
|
local direction = nil
|
|
|
|
if exitAnimType == 0 then
|
|
if GetGameplayCamRelativeHeading() < 0 then
|
|
direction = "m_getout_l"
|
|
else
|
|
direction = "m_getout_r"
|
|
end
|
|
elseif exitAnimType == 1 then
|
|
direction = "m_getout_l"
|
|
elseif exitAnimType == 2 then
|
|
direction = "m_getout_r"
|
|
else
|
|
warn(string.format("exitAnimType: '%s' was not an expcted type, please correct this, setting type to 1 for this instance ('m_getout_r').", exitAnimType))
|
|
direction = "m_getout_r"
|
|
end
|
|
|
|
LoadAnimDict("savem_default@")
|
|
TaskPlayAnim(PlayerPedId(), "savem_default@", direction, 1.0, 1.0, 3000, 0, 0, false, false, false)
|
|
RemoveAnimDict("savem_default@")
|
|
Wait(1400)
|
|
clearTask = false
|
|
end
|
|
|
|
metadata.animation = {}
|
|
LeaveSeat(clearTask, false, false)
|
|
end
|
|
|
|
local function StopCurrentAction()
|
|
if IsPedUsingScenario(PlayerPedId(), metadata.scenario) or metadata.isSitting then
|
|
StopSitting()
|
|
elseif metadata.isLaying then
|
|
GetUpFromBed()
|
|
elseif metadata.attAction then
|
|
metadata.attAction = false
|
|
LeaveSeat(true, false, false)
|
|
end
|
|
end
|
|
|
|
local function AddTargetModel(models, options)
|
|
if Config.Target == 'ox_target' then
|
|
exports.ox_target:addModel(models, options)
|
|
else
|
|
exports[Config.Target]:AddTargetModel(models, { options = options, distance = Config.MaxInteractionDist })
|
|
end
|
|
end
|
|
|
|
local function AddCircleZone(name, center, radius, heading, minZ, maxZ, useZ, options, debug)
|
|
if Config.Target == 'ox_target' then
|
|
exports.ox_target:addSphereZone({
|
|
coords = center,
|
|
radius = radius,
|
|
debug = Config.DebugPoly or debug,
|
|
options = options
|
|
})
|
|
else
|
|
exports[Config.Target]:AddCircleZone(name, center, radius, {
|
|
name = name,
|
|
heading = heading,
|
|
debugPoly = Config.DebugPoly or debug,
|
|
minZ = minZ,
|
|
maxZ = maxZ,
|
|
useZ = useZ
|
|
}, {
|
|
options = options,
|
|
distance = Config.MaxInteractionDist
|
|
})
|
|
end
|
|
end
|
|
|
|
local function AddBoxZone(name, center, heading, length, width, height, minZ, maxZ, useZ, options, debug)
|
|
if Config.Target == 'ox_target' then
|
|
exports.ox_target:addBoxZone({
|
|
coords = center,
|
|
size = vector3(width, length, height),
|
|
rotation = heading,
|
|
debug = Config.DebugPoly or debug,
|
|
drawSprite = Config.TargetDrawSprite,
|
|
options = options
|
|
})
|
|
else
|
|
exports[Config.Target]:AddBoxZone(name, center, length, width, {
|
|
name = name,
|
|
heading = heading,
|
|
debugPoly = Config.DebugPoly or debug,
|
|
minZ = minZ,
|
|
maxZ = maxZ
|
|
}, {
|
|
options = options,
|
|
distance = Config.MaxInteractionDist
|
|
})
|
|
end
|
|
end
|
|
|
|
local function AttemptToLay(entity, poly, info)
|
|
if not metadata.attAction then
|
|
if metadata.isLaying then
|
|
GetUpFromBed()
|
|
else
|
|
LayDownOnBed({entity = entity, poly = poly, bed = info.lay, raycast = true})
|
|
end
|
|
else
|
|
DisplayNotification(Config.Lang.Notification.AlreadyAttemptingToLay)
|
|
end
|
|
end
|
|
|
|
local function AttemptToSit(entity, poly, info)
|
|
if not metadata.attAction then
|
|
if metadata.isSitting or metadata.isLaying then
|
|
if poly == metadata.poly then
|
|
SitOnSeat({entity = entity, poly = poly, sit = info.sit, raycast = true})
|
|
else
|
|
StopSitting()
|
|
end
|
|
else
|
|
SitOnSeat({entity = entity, poly = poly, sit = info.sit, raycast = true})
|
|
end
|
|
else
|
|
DisplayNotification(Config.Lang.Notification.AlreadyAttemptingToSit)
|
|
end
|
|
end
|
|
|
|
local function SetupBeds()
|
|
local models = {}
|
|
for model, data in pairs(MODELS) do
|
|
if data.lay then
|
|
models[#models+1] = model
|
|
end
|
|
end
|
|
|
|
local options = {
|
|
{
|
|
icon = Config.Targeting.LayIcon,
|
|
label = Config.Targeting.LayLabel
|
|
}
|
|
}
|
|
|
|
if Config.Target == 'ox_target' then
|
|
options[1].distance = Config.MaxInteractionDist
|
|
options[1].onSelect = function(data)
|
|
local info = MODELS[GetEntityModel(data.entity)]
|
|
AttemptToLay(data.entity, false, info)
|
|
end
|
|
else
|
|
options[1].action = function(entity)
|
|
local info = MODELS[GetEntityModel(entity)]
|
|
AttemptToLay(entity, false, info)
|
|
end
|
|
end
|
|
|
|
AddTargetModel(models, options)
|
|
end
|
|
|
|
local function SetupSeats()
|
|
local models = {}
|
|
for model, data in pairs(MODELS) do
|
|
if data.sit then
|
|
models[#models+1] = model
|
|
end
|
|
end
|
|
|
|
local options = {
|
|
{
|
|
icon = Config.Targeting.SitIcon,
|
|
label = Config.Targeting.SitLabel
|
|
}
|
|
}
|
|
|
|
if Config.Target == 'ox_target' then
|
|
options[1].distance = Config.MaxInteractionDist
|
|
options[1].onSelect = function(data)
|
|
local info = MODELS[GetEntityModel(data.entity)]
|
|
AttemptToSit(data.entity, false, info)
|
|
end
|
|
else
|
|
options[1].action = function(entity)
|
|
local info = MODELS[GetEntityModel(entity)]
|
|
AttemptToSit(entity, false, info)
|
|
end
|
|
end
|
|
|
|
AddTargetModel(models, options)
|
|
end
|
|
|
|
local function SetupPolyZones()
|
|
for group, data in pairs(POLY_ZONES) do
|
|
if data.enabled then
|
|
for name, info in pairs(data.polys) do
|
|
-- Remove the zone if it already exists. (targeting script does the checking, ox_target does on resource restart so no need)
|
|
if Config.Target ~= 'ox_target' then
|
|
exports[Config.Target]:RemoveZone(name)
|
|
end
|
|
|
|
if info.poly == nil then
|
|
error("PolyZone '"..name.."' could not be generated! (lacks poly specifications)")
|
|
elseif info.lay == nil and info.sit == nil then
|
|
error("PolyZone '"..name.."' could not be generated! (no action assinged)")
|
|
else
|
|
local polyType = 'sit'
|
|
local options = {}
|
|
|
|
if info.lay then
|
|
local index = #options+1
|
|
polyType = 'lay'
|
|
|
|
options[index] = {
|
|
icon = Config.Targeting.LayIcon,
|
|
label = Config.Targeting.LayLabel
|
|
}
|
|
|
|
if Config.Target == 'ox_target' then
|
|
options[index].distance = Config.MaxInteractionDist
|
|
options[index].onSelect = function()
|
|
AttemptToLay(0, name, info)
|
|
end
|
|
|
|
-- This is to prevent duplicate interaction options in ox_target
|
|
options[index].canInteract = function(entity, _distance, _coords, _name, _bone)
|
|
if not entity or #GetEntityCoords(entity).xyz <= 0.0 then
|
|
return true
|
|
end
|
|
|
|
local model = GetEntityModel(entity)
|
|
if MODELS[model]?.lay then
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end
|
|
else
|
|
options[index].action = function()
|
|
AttemptToLay(0, name, info)
|
|
end
|
|
end
|
|
end
|
|
|
|
if info.sit then
|
|
local index = #options+1
|
|
polyType = 'sit'
|
|
|
|
options[index] = {
|
|
icon = Config.Targeting.SitIcon,
|
|
label = Config.Targeting.SitLabel
|
|
}
|
|
|
|
if Config.Target == 'ox_target' then
|
|
options[index].distance = Config.MaxInteractionDist
|
|
options[index].onSelect = function()
|
|
AttemptToSit(0, name, info)
|
|
end
|
|
|
|
-- This is to prevent duplicate interaction options in ox_target
|
|
options[index].canInteract = function(entity, _distance, _coords, _name, _bone)
|
|
if not entity or #GetEntityCoords(entity).xyz <= 0.0 then
|
|
return true
|
|
end
|
|
|
|
local model = GetEntityModel(entity)
|
|
if MODELS[model]?.sit then
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end
|
|
else
|
|
options[index].action = function()
|
|
AttemptToSit(0, name, info)
|
|
end
|
|
end
|
|
end
|
|
|
|
local minZ = info.poly.minZ or (info.poly.center and info.poly.center.z-(info.poly.height/2)) or info[polyType].seats[1].z-(info.poly.height/2)
|
|
local maxZ = info.poly.maxZ or (info.poly.center and info.poly.center.z+(info.poly.height/2)) or info[polyType].seats[1].z+(info.poly.height/2)
|
|
local heading = info.poly.heading or info[polyType].seats[1].w
|
|
local center = info.poly.center or info[polyType].seats[1].xyz
|
|
|
|
if info.poly.type == "circle" then
|
|
local radius = info.poly.radius
|
|
if radius == nil then
|
|
warn(string.format("PolyZone: '%s' did not have a specified radius! Radius was automatically set to 1.5!", name))
|
|
radius = 1.5
|
|
end
|
|
|
|
AddCircleZone(name, center, radius, heading, minZ, maxZ, true, options, data.debug)
|
|
else
|
|
AddBoxZone(name, center, heading, info.poly.length, info.poly.width, info.poly.height, minZ, maxZ, true, options, data.debug)
|
|
end
|
|
end
|
|
end
|
|
|
|
DebugPrint("^2Info: PolyZone group '"..group.."' was generated.")
|
|
else
|
|
DebugPrint("^3Info: PolyZone group '"..group.."' is disabled.")
|
|
end
|
|
end
|
|
end
|
|
|
|
local function SetupLocalization()
|
|
AddTextEntry("sit_getup_keyboard", string.format(Config.Lang.KeyMapping.GetUp, "~INPUT_BA1F4C6D~"))
|
|
AddTextEntry("sit_getup_controller", string.format(Config.Lang.KeyMapping.GetUp, "~INPUT_6ED7AA10~"))
|
|
|
|
if Config.UsePrompts then
|
|
AddTextEntry("sit_on_keyboard", string.format(Config.Lang.KeyMapping.SitDown, "~INPUT_93A2CC75~"))
|
|
AddTextEntry("sit_down_controller", string.format(Config.Lang.KeyMapping.SitDown, "~INPUT_53FA0B5E~"))
|
|
AddTextEntry("lay_on_keyboard", string.format(Config.Lang.KeyMapping.LayDown, "~INPUT_C15C4337~"))
|
|
AddTextEntry("lay_down_controller", string.format(Config.Lang.KeyMapping.LayDown, "~INPUT_49E4480F~"))
|
|
AddTextEntry("both_on_keyboard", string.format(Config.Lang.KeyMapping.SitDown, "~INPUT_93A2CC75~").."\n"..string.format(Config.Lang.KeyMapping.LayDown, "~INPUT_C15C4337~"))
|
|
AddTextEntry("both_down_controller", string.format(Config.Lang.KeyMapping.SitDown, "~INPUT_53FA0B5E~").."\n"..string.format(Config.Lang.KeyMapping.LayDown, "~INPUT_49E4480F~"))
|
|
end
|
|
end
|
|
|
|
-- Prompt System
|
|
local function StartPromptSystem()
|
|
-- Commands
|
|
RegisterCommand("siton", function()
|
|
if metadata.showingPrompt then
|
|
ExecuteCommand("sit")
|
|
end
|
|
end, false)
|
|
RegisterCommand("sitdown", function()
|
|
if metadata.showingPrompt then
|
|
ExecuteCommand("sit")
|
|
end
|
|
end, false)
|
|
|
|
RegisterCommand("layon", function()
|
|
if metadata.showingPrompt then
|
|
ExecuteCommand("lay")
|
|
end
|
|
end, false)
|
|
RegisterCommand("laydown", function()
|
|
if metadata.showingPrompt then
|
|
ExecuteCommand("lay")
|
|
end
|
|
end, false)
|
|
|
|
-- Keymapping
|
|
RegisterKeyMapping('siton', Config.Lang.KeyBindingDesc.Keyboard.SitDown, 'keyboard', Config.DefaultKeybinds.SitDown.SitKeyboard)
|
|
RegisterKeyMapping('sitdown', Config.Lang.KeyBindingDesc.PadAnalog.SitDown, 'PAD_ANALOGBUTTON', Config.DefaultKeybinds.SitDown.SitPadAnalog)
|
|
RegisterKeyMapping('layon', Config.Lang.KeyBindingDesc.Keyboard.LayDown, 'keyboard', Config.DefaultKeybinds.SitDown.LayKeyboard)
|
|
RegisterKeyMapping('laydown', Config.Lang.KeyBindingDesc.PadAnalog.LayDown, 'PAD_ANALOGBUTTON', Config.DefaultKeybinds.SitDown.LayPadAnalog)
|
|
|
|
-- A nested function!
|
|
local function ShowPromptText(type)
|
|
metadata.showingPrompt = true
|
|
local textHash = "sit_on_keyboard"
|
|
|
|
if IsUsingKeyboard(1) then
|
|
textHash = type.."_on_keyboard"
|
|
else
|
|
textHash = type.."_down_controller"
|
|
end
|
|
|
|
for _tick = 1, 25 do
|
|
DisplayHelpTextThisFrame(textHash, false)
|
|
Wait(0)
|
|
end
|
|
end
|
|
|
|
-- Prompt Thread
|
|
CreateThread(function()
|
|
while true do
|
|
local wait = 500
|
|
if not metadata.isSitting and not metadata.isLaying and not metadata.attAction then
|
|
local found, closest = GetClosestPositionOfType("both", Config.MaxPromptDist)
|
|
if found and CanPlayerReachSeat(closest.coords, closest.entity) then
|
|
-- Make sure that no other help message is being displayed when we start showing the prompt text, this will stop the annoying pling sounds when two help texts fights over priority
|
|
if metadata.showingPrompt or not (IsHelpMessageBeingDisplayed() and Config.PreventHelpTextOverwrites) then
|
|
ShowPromptText(closest.type)
|
|
wait = 0
|
|
end
|
|
else
|
|
metadata.showingPrompt = false
|
|
end
|
|
else
|
|
wait = 1000
|
|
end
|
|
|
|
Wait(wait)
|
|
end
|
|
end)
|
|
end
|
|
|
|
|
|
-- Commands --
|
|
RegisterCommand("sit", function()
|
|
if IsPauseMenuActive() then
|
|
return
|
|
end
|
|
|
|
if IsPlayerDoingAnyAction() then
|
|
if not Config.UsePrompts then
|
|
StopCurrentAction()
|
|
end
|
|
elseif not isSittingDisabled then
|
|
SitOnClosestSeat()
|
|
end
|
|
end, false)
|
|
|
|
RegisterCommand("lay", function()
|
|
if IsPauseMenuActive() then
|
|
return
|
|
end
|
|
|
|
if IsPlayerDoingAnyAction() then
|
|
if not Config.UsePrompts then
|
|
StopCurrentAction()
|
|
end
|
|
elseif not isLayingDisabled then
|
|
LayOnClosestBed()
|
|
end
|
|
end, false)
|
|
|
|
|
|
-- KeyMapping --
|
|
RegisterKeyMapping('getup', Config.Lang.KeyBindingDesc.Keyboard.GetUp, 'keyboard', Config.DefaultKeybinds.GetUp.Keyboard)
|
|
RegisterCommand('getup', function()
|
|
if not IsPauseMenuActive() then
|
|
StopCurrentAction()
|
|
end
|
|
end, false)
|
|
|
|
RegisterKeyMapping('standup', Config.Lang.KeyBindingDesc.PadAnalog.GetUp, 'PAD_ANALOGBUTTON', Config.DefaultKeybinds.GetUp.PadAnalog)
|
|
RegisterCommand('standup', function()
|
|
if not IsPauseMenuActive() then
|
|
StopCurrentAction()
|
|
end
|
|
end, false)
|
|
|
|
-- Events --
|
|
AddEventHandler('sit:helpTextThread', function(type)
|
|
ShowPersistentNotification(type)
|
|
end)
|
|
|
|
AddEventHandler('sit:checkThread', function(type)
|
|
CreateThread(function()
|
|
while true do
|
|
Wait(500)
|
|
if not metadata[type] then
|
|
break
|
|
end
|
|
|
|
-- Distance and animation check
|
|
local playerPed = PlayerPedId()
|
|
local playerPos = GetEntityCoords(playerPed)
|
|
local distance = #(playerPos.xy - metadata.targetPos.xy)
|
|
local distZ = playerPos.z - metadata.targetPos.z - 1.25
|
|
if distZ > 0.0 then
|
|
distance = distance + distZ
|
|
end
|
|
|
|
if distance > 0.5 or (metadata.scenario and not IsPedUsingScenario(playerPed, metadata.scenario) or (metadata.animation and metadata.animation.dict and not IsEntityPlayingAnim(playerPed, metadata.animation.dict, metadata.animation.name, 3))) or IsEntityDead(playerPed) or (metadata.entity ~= 0 and not DoesEntityExist(metadata.entity)) or IsPedRagdoll(playerPed) then
|
|
local clearTask = true
|
|
if IsEntityDead(playerPed) then
|
|
clearTask = false
|
|
end
|
|
|
|
LeaveSeat(clearTask, false, true)
|
|
break
|
|
end
|
|
end
|
|
|
|
if type == 'isSitting' then
|
|
TriggerEvent('sit:onGetUp', 'sit')
|
|
else
|
|
TriggerEvent('sit:onGetUp', 'lay')
|
|
end
|
|
end)
|
|
end)
|
|
|
|
-- Reduce stress
|
|
-- NOTE: This is set up to use qb-core functions, you may need to edit it!
|
|
if Config.ReduceStress then
|
|
local stressLevel = 0.0
|
|
|
|
if Config.Framework == "QBCore" then
|
|
RegisterNetEvent('QBCore:Player:SetPlayerData', function(data)
|
|
stressLevel = data?.metadata?.stress or 0.0
|
|
end)
|
|
end
|
|
|
|
AddEventHandler('sit:checkThread', function(type)
|
|
CreateThread(function()
|
|
Wait(5000)
|
|
|
|
while metadata[type] do
|
|
if stressLevel > 0.0 then
|
|
TriggerServerEvent('hud:server:RelieveStress', math.random(2, 4))
|
|
end
|
|
|
|
Wait(10000)
|
|
end
|
|
end)
|
|
end)
|
|
end
|
|
|
|
RegisterNetEvent('sit:sitDown', function()
|
|
SitOnClosestSeat()
|
|
end)
|
|
|
|
RegisterNetEvent('sit:layDown', function()
|
|
LayOnClosestBed()
|
|
end)
|
|
|
|
RegisterNetEvent('sit:getUp', function()
|
|
StopCurrentAction()
|
|
end)
|
|
|
|
if Config.InvalidateIdleCam then
|
|
AddEventHandler('sit:onSit', function()
|
|
local sitting = true
|
|
|
|
-- Register get up event
|
|
local getupEvent = AddEventHandler('sit:onGetUp', function(type)
|
|
if type == "sit" then
|
|
sitting = false
|
|
end
|
|
end)
|
|
|
|
-- We don't use DisableIdleCamera as we can't check if the idle cam already was disabled, and we don't want to overwrite any other scripts.
|
|
CreateThread(function()
|
|
while sitting do
|
|
InvalidateIdleCam()
|
|
Wait(1000)
|
|
end
|
|
|
|
RemoveEventHandler(getupEvent)
|
|
end)
|
|
end)
|
|
|
|
AddEventHandler('sit:onLay', function()
|
|
local laying = true
|
|
|
|
-- Register get up event
|
|
local getupEvent = AddEventHandler('sit:onGetUp', function(type)
|
|
if type == "lay" then
|
|
laying = false
|
|
end
|
|
end)
|
|
|
|
-- We don't use DisableIdleCamera as we can't check if the idle cam already was disabled, and we don't want to overwrite any other scripts.
|
|
CreateThread(function()
|
|
while laying do
|
|
InvalidateIdleCam()
|
|
Wait(1000)
|
|
end
|
|
|
|
RemoveEventHandler(getupEvent)
|
|
end)
|
|
end)
|
|
end
|
|
|
|
|
|
-- Initialization --
|
|
CreateThread(function()
|
|
-- Set up localization
|
|
SetupLocalization()
|
|
|
|
-- Chat command suggestions
|
|
if Config.AddChatSuggestions then
|
|
TriggerEvent('chat:addSuggestion', '/sit', Config.Lang.ChatSuggestions.Sit)
|
|
TriggerEvent('chat:addSuggestion', '/lay', Config.Lang.ChatSuggestions.Lay)
|
|
end
|
|
|
|
-- Prompts
|
|
if Config.UsePrompts then
|
|
StartPromptSystem()
|
|
end
|
|
|
|
-- Yes this is needed, people are just... not very good a reading.
|
|
if Config.Target == 'false' then
|
|
warn("Config.Target was set to 'false' (string), but it needs to be set to false (boolean).^7")
|
|
Config.Target = false
|
|
end
|
|
|
|
-- Targeting
|
|
if Config.Target then
|
|
SetupBeds()
|
|
SetupSeats()
|
|
SetupPolyZones()
|
|
end
|
|
end)
|
|
|
|
|
|
-- Debugging --
|
|
-- This is some of the code I used when I was debugging/adding new models and polys.
|
|
if Config.Debugmode then
|
|
local debugging = true
|
|
local function DrawText3D(coords, text, rgb)
|
|
SetTextColour(rgb.r, rgb.g, rgb.b, 255)
|
|
SetTextScale(0.0, 0.35)
|
|
SetTextFont(4)
|
|
SetTextOutline()
|
|
SetTextCentre(true)
|
|
|
|
-- Diplay the text
|
|
BeginTextCommandDisplayText("STRING")
|
|
AddTextComponentSubstringPlayerName(text)
|
|
SetDrawOrigin(coords.x, coords.y, coords.z, 0)
|
|
EndTextCommandDisplayText(0.0, 0.0)
|
|
ClearDrawOrigin()
|
|
DrawRect(coords.x, coords.y, 1.0, 1.0, 230, 230, 230, 255)
|
|
end
|
|
|
|
local function GetDebugEntities(playerPed)
|
|
local playerCoords = GetEntityCoords(playerPed)
|
|
local objectPool = GetGamePool('CObject')
|
|
local objects = {}
|
|
|
|
for i = 1, #objectPool do
|
|
local pos = GetEntityCoords(objectPool[i])
|
|
local distance = #(playerCoords - pos)
|
|
if distance < 8.0 then
|
|
objects[i] = {pos = pos, entity = objectPool[i]}
|
|
end
|
|
end
|
|
|
|
return objects
|
|
end
|
|
|
|
local function DebugIsSeatAvailable(coords, action)
|
|
for _index, ped in pairs(GetGamePool('CPed')) do
|
|
local dist = #(GetEntityCoords(ped)-coords)
|
|
if dist < 1.35 then
|
|
if action == 'sit' then
|
|
if IsPedPlayingAnyLayAnim(ped) or dist < 0.55 then
|
|
return false
|
|
end
|
|
elseif action == 'lay' then
|
|
if IsPedPlayingAnyLayAnim(ped) or IsPedSitting(ped) then
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
-- A debug thread, the messiest shit I've ever made.
|
|
local function StartDebuging()
|
|
local toDisplay = {}
|
|
local colors = {
|
|
['occupied'] = {r=200, g=0, b=0},
|
|
['sit'] = {r=255, g=255, b=255},
|
|
['lay'] = {r=150, g=150, b=150},
|
|
['sit_line'] = {r=255, g=0, b=0, a=200},
|
|
['lay_line'] = {r=0, g=102, b=204, a=255}
|
|
}
|
|
|
|
CreateThread(function()
|
|
while debugging do
|
|
local globalIndex = 0
|
|
local playerPed = PlayerPedId()
|
|
local playerCoords = GetEntityCoords(playerPed)
|
|
toDisplay = {}
|
|
|
|
local entites = GetDebugEntities(playerPed)
|
|
for _key, info in pairs(entites) do
|
|
local model = GetEntityModel(info.entity)
|
|
local information = MODELS[model]
|
|
if information then
|
|
local modelName = GetEntityArchetypeName(info.entity) or model
|
|
for typeset, data in pairs(information) do
|
|
for seatIndex, seat in pairs(data.seats) do
|
|
local subName = typeset..": "..modelName
|
|
if #data.seats > 1 then
|
|
subName = subName.." ("..seatIndex..")"
|
|
end
|
|
local heading = GetEntityHeading(info.entity)
|
|
local coords = nil
|
|
if typeset == "lay" then
|
|
coords = GetOffsetFromCoordsInWorldCoords(info.pos, vector3(0.0, 0.0, HeadingToRotation(heading)), vector3(seat.x, seat.y, seat.z+0.25))
|
|
else
|
|
coords = GetOffsetFromCoordsInWorldCoords(info.pos, vector3(0.0, 0.0, HeadingToRotation(heading)), seat.xyz)
|
|
end
|
|
local newHeading = heading + seat.w
|
|
if newHeading > 360 then
|
|
newHeading = newHeading - 360
|
|
end
|
|
|
|
local textColor = colors[typeset]
|
|
if not DebugIsSeatAvailable(coords.xyz, typeset) then
|
|
textColor = colors['occupied']
|
|
end
|
|
globalIndex = globalIndex + 1
|
|
toDisplay[globalIndex] = { vector4(coords.x, coords.y, coords.z, newHeading), subName, textColor, colors[typeset.."_line"]}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
for _group, data in pairs(POLY_ZONES) do
|
|
if data.enabled then
|
|
if not data.radius or (#(data.center.xy - playerCoords.xy) < data.radius) then
|
|
for name, information in pairs(data.polys) do
|
|
for typeset, info in pairs(information) do
|
|
if typeset == "sit" or typeset == "lay" then
|
|
for index, coords in pairs(info.seats) do
|
|
if #(playerCoords-coords.xyz) < 8.0 then
|
|
local subName = typeset..": "..name
|
|
if #info.seats > 1 then
|
|
subName = subName.." ("..index..")"
|
|
end
|
|
local location = coords.xyz
|
|
if typeset == "lay" then
|
|
location = GetOffsetFromCoordsInWorldCoords(coords, vector3(0.0, 0.0, 0.0), vector3(0.0, 0.0, 0.25))
|
|
end
|
|
|
|
local textColor = colors[typeset]
|
|
if not DebugIsSeatAvailable(location, typeset) then
|
|
textColor = colors['occupied']
|
|
end
|
|
globalIndex = globalIndex + 1
|
|
toDisplay[globalIndex] = { vector4(location.x, location.y, location.z, coords.w), subName, textColor, colors[typeset.."_line"]}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
Wait(1000)
|
|
end
|
|
end)
|
|
|
|
CreateThread(function()
|
|
while debugging do
|
|
for _index, data in pairs(toDisplay) do
|
|
DrawText3D(data[1].xyz, data[2], data[3])
|
|
end
|
|
Wait(0)
|
|
end
|
|
end)
|
|
|
|
CreateThread(function()
|
|
while debugging do
|
|
for _index, data in pairs(toDisplay) do
|
|
if data[1].w ~= nil then
|
|
local offset = GetOffsetFromCoordsInWorldCoords(data[1].xyz, vector3(0.0, 0.0, HeadingToRotation(data[1].w)), vector3(0.0, 0.50, 0.0))
|
|
local offset2 = GetOffsetFromCoordsInWorldCoords(data[1].xyz, vector3(0.0, 0.0, HeadingToRotation(data[1].w)), vector3(0.0, 0.00, 0.20))
|
|
DrawLine(data[1].x, data[1].y, data[1].z, offset.x, offset.y, offset.z, data[4].r, data[4].g, data[4].b, data[4].a)
|
|
DrawLine(data[1].x, data[1].y, data[1].z, offset2.x, offset2.y, offset2.z, data[4].r, data[4].g, data[4].b, data[4].a)
|
|
end
|
|
end
|
|
Wait(0)
|
|
end
|
|
end)
|
|
end
|
|
|
|
RegisterKeyMapping('sit:debug', 'Sit Debuging', 'keyboard', 'G')
|
|
RegisterCommand('sit:debug', function()
|
|
debugging = not debugging
|
|
if debugging then
|
|
StartDebuging()
|
|
end
|
|
end, false)
|
|
|
|
local function GetAverage(table)
|
|
local sum = 0
|
|
for key, value in pairs(table) do
|
|
sum = sum + value
|
|
end
|
|
return sum / #table
|
|
end
|
|
|
|
-- Not a true "center", as it calculates the average of all the points, but it's good enough for our purpose.
|
|
RegisterCommand('sit:getcenter', function(_source, args, _rawCommand)
|
|
local group = args[1]
|
|
if POLY_ZONES[group] then
|
|
local xPoints = {}
|
|
local yPoints = {}
|
|
local zPoints = {}
|
|
|
|
local index = 0
|
|
for k, v in pairs(POLY_ZONES[group].polys) do
|
|
index = index + 1
|
|
xPoints[index] = (v.poly.center and v.poly.center.x) or (v.sit and v.sit.seats[1].x) or (v.lay and v.lay.seats[1].x)
|
|
yPoints[index] = (v.poly.center and v.poly.center.y) or (v.sit and v.sit.seats[1].y) or (v.lay and v.lay.seats[1].y)
|
|
zPoints[index] = (v.poly.center and v.poly.center.z) or (v.sit and v.sit.seats[1].z) or (v.lay and v.lay.seats[1].z)
|
|
end
|
|
|
|
local average = vector3(GetAverage(xPoints), GetAverage(yPoints), GetAverage(zPoints))
|
|
print('average "center":', average)
|
|
else
|
|
print(group, 'is not a valid poly group!')
|
|
end
|
|
end, false)
|
|
|
|
RegisterCommand('sit:getfarthestdist', function(_source, args, _rawCommand)
|
|
local group = args[1]
|
|
if POLY_ZONES[group] and POLY_ZONES[group].center then
|
|
local center = POLY_ZONES[group].center
|
|
local farthest = {
|
|
dist = 0,
|
|
name = 'error'
|
|
}
|
|
|
|
for name, data in pairs(POLY_ZONES[group].polys) do
|
|
local pos = data.poly.center or (data.sit and data.sit.seats[1].xyz) or (data.lay and data.lay.seats[1].xyz)
|
|
local distance = #(center-pos)
|
|
if distance > farthest.dist then
|
|
farthest.dist = distance
|
|
farthest.name = name
|
|
end
|
|
end
|
|
|
|
print(farthest.name, farthest.dist)
|
|
else
|
|
print(group, 'is not a valid poly group!')
|
|
end
|
|
end, false)
|
|
|
|
-- Load poly groups (only on your client)
|
|
RegisterCommand('sit:loadGroup', function(_source, args, _rawCommand)
|
|
local group = args[1]
|
|
if POLY_ZONES[group] and POLY_ZONES[group].center then
|
|
POLY_ZONES[group].enabled = true
|
|
SetupPolyZones()
|
|
else
|
|
print(group, 'is not a valid poly group!')
|
|
end
|
|
end, false)
|
|
|
|
-- Unload poly groups (only on your client)
|
|
RegisterCommand('sit:unloadGroup', function(_source, args, _rawCommand)
|
|
if Config.Target == 'ox_target' then
|
|
print("ox_target does not support this action!")
|
|
return
|
|
end
|
|
|
|
local group = args[1]
|
|
if POLY_ZONES[group] and POLY_ZONES[group].center then
|
|
POLY_ZONES[group].enabled = false
|
|
for name, _info in pairs(POLY_ZONES[group].polys) do
|
|
exports[Config.Target]:RemoveZone(name)
|
|
end
|
|
else
|
|
print(group, 'is not a valid poly group!')
|
|
end
|
|
end, false)
|
|
|
|
-- Events with some prints
|
|
AddEventHandler('sit:onSit', function()
|
|
print('sit:onSit')
|
|
end)
|
|
|
|
AddEventHandler('sit:onLay', function()
|
|
print('sit:onLay')
|
|
end)
|
|
|
|
AddEventHandler('sit:onGetUp', function(type)
|
|
print('sit:onGetUp', type)
|
|
end)
|
|
|
|
StartDebuging()
|
|
end
|
|
|
|
AddEventHandler('onResourceStop', function(resourceName)
|
|
if resourceName ~= CURRENT_RESOURCE then
|
|
return
|
|
end
|
|
|
|
if not metadata.isSitting and not metadata.isLaying then
|
|
return
|
|
end
|
|
|
|
local playerPed = PlayerPedId()
|
|
ClearPedTasks(playerPed)
|
|
|
|
if metadata.plyFrozen then
|
|
SetEntityCollision(playerPed, true, false)
|
|
FreezeEntityPosition(playerPed, false)
|
|
metadata.plyFrozen = false
|
|
end
|
|
|
|
if metadata.entity ~= 0 then
|
|
UnhandleLooseEntity(metadata.entity)
|
|
metadata.entity = 0
|
|
end
|
|
end)
|
|
|
|
|
|
-- Exports --
|
|
local function IsSitting()
|
|
return metadata.isSitting
|
|
end
|
|
|
|
local function IsLaying()
|
|
return metadata.isLaying
|
|
end
|
|
|
|
local function IsSittingOrLaying()
|
|
return metadata.isSitting or metadata.isLaying
|
|
end
|
|
|
|
local function IsNearSeat()
|
|
local found, _closest = GetClosestPositionOfType("sit", Config.MaxInteractionDist)
|
|
return found
|
|
end
|
|
|
|
local function IsNearBed()
|
|
local found, _closest = GetClosestPositionOfType("lay", Config.MaxInteractionDist)
|
|
return found
|
|
end
|
|
|
|
local function GetClosestSeat(maxDistance)
|
|
return GetClosestPositionOfType("sit", maxDistance)
|
|
end
|
|
|
|
local function GetClosestBed(maxDistance)
|
|
return GetClosestPositionOfType("lay", maxDistance)
|
|
end
|
|
|
|
local function RefreshIsSitDisabled()
|
|
for _index, status in pairs(disableReasons.sit) do
|
|
if status then
|
|
return
|
|
end
|
|
end
|
|
|
|
isSittingDisabled = false
|
|
end
|
|
|
|
local function RefreshIsLayDisabled()
|
|
for _index, status in pairs(disableReasons.lay) do
|
|
if status then
|
|
return
|
|
end
|
|
end
|
|
|
|
isLayingDisabled = false
|
|
end
|
|
|
|
local function DisableSitting(state, reason)
|
|
if not reason then reason = 'default' end
|
|
|
|
if state then
|
|
disableReasons.sit[reason] = true
|
|
if not isSittingDisabled then
|
|
isSittingDisabled = true
|
|
end
|
|
else
|
|
disableReasons.sit[reason] = nil
|
|
RefreshIsSitDisabled()
|
|
end
|
|
end
|
|
|
|
local function DisableLaying(state, reason)
|
|
if not reason then reason = 'default' end
|
|
|
|
if state then
|
|
disableReasons.lay[reason] = true
|
|
if not isLayingDisabled then
|
|
isLayingDisabled = true
|
|
end
|
|
else
|
|
disableReasons.lay[reason] = nil
|
|
RefreshIsLayDisabled()
|
|
end
|
|
end
|
|
|
|
local function DisableSittingAndLaying(state, reason)
|
|
if not reason then reason = 'default' end
|
|
|
|
if state then
|
|
disableReasons.sit[reason] = true
|
|
disableReasons.lay[reason] = true
|
|
|
|
if not isSittingDisabled then
|
|
isSittingDisabled = true
|
|
end
|
|
|
|
if not isLayingDisabled then
|
|
isLayingDisabled = true
|
|
end
|
|
else
|
|
disableReasons.sit[reason] = nil
|
|
disableReasons.lay[reason] = nil
|
|
|
|
RefreshIsSitDisabled()
|
|
RefreshIsLayDisabled()
|
|
end
|
|
end
|
|
|
|
local function IsSittingDisabled()
|
|
return isSittingDisabled, disableReasons.sit
|
|
end
|
|
|
|
local function IsLayingDisabled()
|
|
return isLayingDisabled, disableReasons.lay
|
|
end
|
|
|
|
local function GetSitDisableReasons()
|
|
return disableReasons.sit
|
|
end
|
|
|
|
local function GetLayDisableReasons()
|
|
return disableReasons.lay
|
|
end
|
|
|
|
exports('IsSitting', IsSitting)
|
|
exports('IsLaying', IsLaying)
|
|
exports('IsSittingOrLaying', IsSittingOrLaying)
|
|
exports('IsNearSeat', IsNearSeat)
|
|
exports('IsNearBed', IsNearBed)
|
|
exports('GetClosestSeat', GetClosestSeat)
|
|
exports('GetClosestBed', GetClosestBed)
|
|
exports('SitOnClosestSeat', SitOnClosestSeat)
|
|
exports('LayOnClosestBed', LayOnClosestBed)
|
|
exports('StopCurrentAction', StopCurrentAction)
|
|
exports('DisableSitting', DisableSitting)
|
|
exports('DisableLaying', DisableLaying)
|
|
exports('DisableSittingAndLaying', DisableSittingAndLaying)
|
|
exports('IsSittingDisabled', IsSittingDisabled)
|
|
exports('IsLayingDisabled', IsLayingDisabled)
|
|
exports('GetSitDisableReasons', GetSitDisableReasons)
|
|
exports('GetLayDisableReasons', GetLayDisableReasons)
|