This commit is contained in:
Nordi98 2025-07-14 18:34:49 +02:00
parent 19fb68f805
commit 01d047b3cc
53 changed files with 3222 additions and 5 deletions

View file

@ -0,0 +1,349 @@
if not LoadResourceFile(cache.resource, 'web/build/index.html') then
error('Unable to load UI. Build ox_doorlock or download the latest release.\n ^3https://github.com/overextended/ox_doorlock/releases/latest/download/ox_doorlock.zip^0')
end
if not lib.checkDependency('ox_lib', '3.14.0', true) then return end
local function createDoor(door)
local double = door.doors
door.zone = GetLabelText(GetNameOfZone(door.coords.x, door.coords.y, door.coords.z))
if double then
for i = 1, 2 do
AddDoorToSystem(double[i].hash, double[i].model, double[i].coords.x, double[i].coords.y, double[i].coords.z, false, false, false)
DoorSystemSetDoorState(double[i].hash, 4, false, false)
DoorSystemSetDoorState(double[i].hash, door.state, false, false)
if door.doorRate or not door.auto then
DoorSystemSetAutomaticRate(double[i].hash, door.doorRate or 10.0, false, false)
end
end
else
AddDoorToSystem(door.hash, door.model, door.coords.x, door.coords.y, door.coords.z, false, false, false)
DoorSystemSetDoorState(door.hash, 4, false, false)
DoorSystemSetDoorState(door.hash, door.state, false, false)
if door.doorRate or not door.auto then
DoorSystemSetAutomaticRate(door.hash, door.doorRate or 10.0, false, false)
end
end
end
local nearbyDoors = {}
local Entity = Entity
lib.callback('ox_doorlock:getDoors', false, function(data)
doors = data
for _, door in pairs(data) do
createDoor(door)
end
while true do
table.wipe(nearbyDoors)
local coords = GetEntityCoords(cache.ped)
for _, door in pairs(doors) do
local double = door.doors
door.distance = #(coords - door.coords)
if double then
if door.distance < 80 then
for i = 1, 2 do
if not double[i].entity and IsModelValid(double[i].model) then
local entity = GetClosestObjectOfType(double[i].coords.x, double[i].coords.y, double[i].coords.z, 1.0, double[i].model, false, false, false)
if entity ~= 0 then
double[i].entity = entity
Entity(entity).state.doorId = door.id
end
end
end
if door.distance < 20 then
nearbyDoors[#nearbyDoors + 1] = door
end
else
for i = 1, 2 do
double[i].entity = nil
end
end
elseif door.distance < 80 then
if not door.entity and IsModelValid(door.model) then
local entity = GetClosestObjectOfType(door.coords.x, door.coords.y, door.coords.z, 1.0, door.model, false, false, false)
if entity ~= 0 then
local min, max = GetModelDimensions(door.model)
local points = {
GetOffsetFromEntityInWorldCoords(entity, min.x, min.y, min.z).xy,
GetOffsetFromEntityInWorldCoords(entity, min.x, min.y, max.z).xy,
GetOffsetFromEntityInWorldCoords(entity, min.x, max.y, max.z).xy,
GetOffsetFromEntityInWorldCoords(entity, min.x, max.y, min.z).xy,
GetOffsetFromEntityInWorldCoords(entity, max.x, min.y, min.z).xy,
GetOffsetFromEntityInWorldCoords(entity, max.x, min.y, max.z).xy,
GetOffsetFromEntityInWorldCoords(entity, max.x, max.y, max.z).xy,
GetOffsetFromEntityInWorldCoords(entity, max.x, max.y, min.z).xy
}
local centroid = vec2(0, 0)
for i = 1, 8 do
centroid += points[i]
end
centroid = centroid / 8
door.coords = vec3(centroid.x, centroid.y, door.coords.z)
door.entity = entity
Entity(entity).state.doorId = door.id
end
end
if door.distance < 20 then
nearbyDoors[#nearbyDoors + 1] = door
end
elseif door.entity then
door.entity = nil
end
end
Wait(500)
end
end)
RegisterNetEvent('ox_doorlock:setState', function(id, state, source, data)
if not doors then return end
if data then
doors[id] = data
createDoor(data)
if NuiHasLoaded then
SendNuiMessage(json.encode({
action = 'updateDoorData',
data = data
}))
end
end
if Config.Notify and source == cache.serverId then
if state == 0 then
lib.notify({
type = 'success',
icon = 'unlock',
description = locale('unlocked_door')
})
else
lib.notify({
type = 'success',
icon = 'lock',
description = locale('locked_door')
})
end
end
local door = data or doors[id]
local double = door.doors
door.state = state
if double then
DoorSystemSetDoorState(double[1].hash, door.state, false, false)
DoorSystemSetDoorState(double[2].hash, door.state, false, false)
if door.holdOpen then
DoorSystemSetHoldOpen(double[1].hash, door.state == 0)
DoorSystemSetHoldOpen(double[2].hash, door.state == 0)
end
while door.state == 1 and (not IsDoorClosed(double[1].hash) or not IsDoorClosed(double[2].hash)) do Wait(0) end
else
DoorSystemSetDoorState(door.hash, door.state, false, false)
if door.holdOpen then DoorSystemSetHoldOpen(door.hash, door.state == 0) end
while door.state == 1 and not IsDoorClosed(door.hash) do Wait(0) end
end
if door.state == state and door.distance and door.distance < 20 then
if Config.NativeAudio then
RequestScriptAudioBank('dlc_oxdoorlock/oxdoorlock', false)
local sound = state == 0 and door.unlockSound or door.lockSound or 'door_bolt'
local soundId = GetSoundId()
PlaySoundFromCoord(soundId, sound, door.coords.x, door.coords.y, door.coords.z, 'DLC_OXDOORLOCK_SET', false, 0, false)
ReleaseSoundId(soundId)
ReleaseNamedScriptAudioBank('dlc_oxdoorlock/oxdoorlock')
else
local volume = (0.01 * GetProfileSetting(300)) / (door.distance / 2)
if volume > 1 then volume = 1 end
local sound = state == 0 and door.unlockSound or door.lockSound or 'door-bolt-4'
SendNUIMessage({
action = 'playSound',
data = {
sound = sound,
volume = volume
}
})
end
end
end)
RegisterNetEvent('ox_doorlock:editDoorlock', function(id, data)
if source == '' then return end
local door = doors[id]
local double = door.doors
local doorState = data and data.state or 0
if data then
data.zone = door.zone or GetLabelText(GetNameOfZone(door.coords.x, door.coords.y, door.coords.z))
-- hacky method to resolve a bug with "closest door" by forcing a distance recalculation
if door.distance < 20 then door.distance = 80 end
elseif ClosestDoor?.id == id then
ClosestDoor = nil
end
if double then
for i = 1, 2 do
local doorHash = double[i].hash
if data then
if data.doorRate or door.doorRate or not data.auto then
DoorSystemSetAutomaticRate(doorHash, data.doorRate or door.doorRate and 0.0 or 10.0, false, false)
end
DoorSystemSetDoorState(doorHash, doorState, false, false)
if data.holdOpen then DoorSystemSetHoldOpen(doorHash, doorState == 0) end
else
DoorSystemSetDoorState(doorHash, 4, false, false)
DoorSystemSetDoorState(doorHash, 0, false, false)
if double[i].entity then
Entity(double[i].entity).state.doorId = nil
end
end
end
else
if data then
if data.doorRate or door.doorRate or not data.auto then
DoorSystemSetAutomaticRate(door.hash, data.doorRate or door.doorRate and 0.0 or 10.0, false, false)
end
DoorSystemSetDoorState(door.hash, doorState, false, false)
if data.holdOpen then DoorSystemSetHoldOpen(door.hash, doorState == 0) end
else
DoorSystemSetDoorState(door.hash, 4, false, false)
DoorSystemSetDoorState(door.hash, 0, false, false)
if door.entity then
Entity(door.entity).state.doorId = nil
end
end
end
doors[id] = data
if NuiHasLoaded then
SendNuiMessage(json.encode({
action = 'updateDoorData',
data = data or id
}))
end
end)
ClosestDoor = nil
lib.callback.register('ox_doorlock:inputPassCode', function()
return ClosestDoor?.passcode and lib.inputDialog(locale('door_lock'), {
{
type = 'input',
label = locale('passcode'),
password = true,
icon = 'lock'
},
})?[1]
end)
local lastTriggered = 0
local function useClosestDoor()
if not ClosestDoor then return false end
local gameTimer = GetGameTimer()
if gameTimer - lastTriggered > 500 then
lastTriggered = gameTimer
TriggerServerEvent('ox_doorlock:setState', ClosestDoor.id, ClosestDoor.state == 1 and 0 or 1)
end
end
exports('useClosestDoor', useClosestDoor)
CreateThread(function()
local lockDoor = locale('lock_door')
local unlockDoor = locale('unlock_door')
local showUI
local drawSprite = Config.DrawSprite
if drawSprite then
local sprite1 = drawSprite[0]?[1]
local sprite2 = drawSprite[1]?[1]
if sprite1 then
RequestStreamedTextureDict(sprite1, true)
end
if sprite2 then
RequestStreamedTextureDict(sprite2, true)
end
end
local SetDrawOrigin = SetDrawOrigin
local ClearDrawOrigin = ClearDrawOrigin
local DrawSprite = drawSprite and DrawSprite
while true do
local num = #nearbyDoors
if num > 0 then
local ratio = drawSprite and GetAspectRatio(true)
for i = 1, num do
local door = nearbyDoors[i]
if door.distance < door.maxDistance then
if door.distance < (ClosestDoor?.distance or 10) then
ClosestDoor = door
end
if drawSprite and not door.hideUi then
local sprite = drawSprite[door.state]
if sprite then
SetDrawOrigin(door.coords.x, door.coords.y, door.coords.z)
DrawSprite(sprite[1], sprite[2], sprite[3], sprite[4], sprite[5], sprite[6] * ratio, sprite[7], sprite[8], sprite[9], sprite[10], sprite[11])
ClearDrawOrigin()
end
end
end
end
else ClosestDoor = nil end
if ClosestDoor and ClosestDoor.distance < ClosestDoor.maxDistance then
if Config.DrawTextUI and not ClosestDoor.hideUi and ClosestDoor.state ~= showUI then
lib.showTextUI(ClosestDoor.state == 0 and lockDoor or unlockDoor)
showUI = ClosestDoor.state
end
if not PickingLock and IsDisabledControlJustReleased(0, 38) then
useClosestDoor()
end
elseif showUI then
lib.hideTextUI()
showUI = nil
end
Wait(num > 0 and 0 or 500)
end
end)

View file

@ -0,0 +1,410 @@
local Entity = Entity
local function getDoorFromEntity(data)
local entity = type(data) == 'table' and data.entity or data
if not entity then return end
local state = Entity(entity)?.state
local doorId = state?.doorId
if not doorId then return end
local door = doors[doorId]
if not door then
state.doorId = nil
end
return door
end
exports('getClosestDoorId', function() return ClosestDoor?.id end)
exports('getDoorIdFromEntity', function(entityId) return getDoorFromEntity(entityId)?.id end) -- same as Entity(entityId).state.doorId
local function entityIsNotDoor(data)
local entity = type(data) == 'number' and data or data.entity
return not getDoorFromEntity(entity)
end
PickingLock = false
local function canPickLock(entity)
if PickingLock then return false end
local door = getDoorFromEntity(entity)
return door and door.lockpick and (Config.CanPickUnlockedDoors or door.state == 1)
end
---@param entity number
local function pickLock(entity)
local door = getDoorFromEntity(entity)
if not door or PickingLock or not door.lockpick or (not Config.CanPickUnlockedDoors and door.state == 0) then return end
PickingLock = true
TaskTurnPedToFaceCoord(cache.ped, door.coords.x, door.coords.y, door.coords.z, 4000)
Wait(500)
local animDict = lib.requestAnimDict('mp_common_heist')
TaskPlayAnim(cache.ped, animDict, 'pick_door', 3.0, 1.0, -1, 49, 0, true, true, true)
local success = lib.skillCheck(door.lockpickDifficulty or Config.LockDifficulty)
local rand = math.random(1, success and 100 or 5)
if success then
TriggerServerEvent('ox_doorlock:setState', door.id, door.state == 1 and 0 or 1, true)
end
if rand == 1 then
TriggerServerEvent('ox_doorlock:breakLockpick')
lib.notify({ type = 'error', description = locale('lockpick_broke') })
end
StopEntityAnim(cache.ped, 'pick_door', animDict, 0)
RemoveAnimDict(animDict)
PickingLock = false
end
exports('pickClosestDoor', function()
if not ClosestDoor then return end
pickLock(ClosestDoor.entity)
end)
local tempData = {}
local function addDoorlock(data)
local entity = type(data) == 'number' and data or data.entity
local model = GetEntityModel(entity)
local coords = GetEntityCoords(entity)
AddDoorToSystem(`temp`, model, coords.x, coords.y, coords.z, false, false, false)
DoorSystemSetDoorState(`temp`, 4, false, false)
coords = GetEntityCoords(entity)
tempData[#tempData + 1] = {
entity = entity,
model = model,
coords = coords,
heading = math.floor(GetEntityHeading(entity) + 0.5)
}
RemoveDoorFromSystem(`temp`)
end
local isAddingDoorlock = false
RegisterNUICallback('notify', function(data, cb)
cb(1)
lib.notify({ title = data })
end)
RegisterNUICallback('createDoor', function(data, cb)
cb(1)
SetNuiFocus(false, false)
data.state = data.state and 1 or 0
if data.items and not next(data.items) then
data.items = nil
end
if data.characters and not next(data.characters) then
data.characters = nil
end
if data.lockpickDifficulty and not next(data.lockpickDifficulty) then
data.lockpickDifficulty = nil
end
if data.groups and not next(data.groups) then
data.groups = nil
end
if not data.id then
isAddingDoorlock = true
local doorCount = data.doors and 2 or 1
local lastEntity = 0
lib.showTextUI(locale('add_door_textui'))
repeat
DisablePlayerFiring(cache.playerId, true)
DisableControlAction(0, 25, true)
local hit, entity, coords = lib.raycast.cam(1|16)
local changedEntity = lastEntity ~= entity
local doorA = tempData[1]?.entity
if changedEntity and lastEntity ~= doorA then
SetEntityDrawOutline(lastEntity, false)
end
lastEntity = entity
if hit then
---@diagnostic disable-next-line: param-type-mismatch
DrawMarker(28, coords.x, coords.y, coords.z, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2, 0.2, 0.2, 255, 42, 24,
100, false, false, 0, true, false, false, false)
end
if hit and entity > 0 and GetEntityType(entity) == 3 and (doorCount == 1 or doorA ~= entity) and entityIsNotDoor(entity) then
if changedEntity then
SetEntityDrawOutline(entity, true)
end
if IsDisabledControlJustPressed(0, 24) then
addDoorlock(entity)
end
end
if IsDisabledControlJustPressed(0, 25) then
SetEntityDrawOutline(entity, false)
if not doorA then
isAddingDoorlock = false
return lib.hideTextUI()
end
SetEntityDrawOutline(doorA, false)
table.wipe(tempData)
end
until tempData[doorCount]
lib.hideTextUI()
SetEntityDrawOutline(tempData[1].entity, false)
if data.doors then
SetEntityDrawOutline(tempData[2].entity, false)
tempData[1].entity = nil
tempData[2].entity = nil
data.doors = tempData
else
data.model = tempData[1].model
data.coords = tempData[1].coords
data.heading = tempData[1].heading
end
else
if data.doors then
for i = 1, 2 do
local coords = data.doors[i].coords
data.doors[i].coords = vector3(coords.x, coords.y, coords.z)
data.doors[i].entity = nil
end
else
data.entity = nil
end
data.coords = vector3(data.coords.x, data.coords.y, data.coords.z)
data.distance = nil
data.zone = nil
end
isAddingDoorlock = false
------------------------------------
------- Brutal Housing Editing -----
------------------------------------
if PropertyID ~= nil then
if data.name:match("^(.-)_") == nil or data.name:match("^(.-)_") ~= PropertyID then
data.name = PropertyID.."_"..data.name
end
local coords = data.coords or data.doors[1].coords
if coords ~= nil then
if #(coords - vector3(PropertyCoords.x, PropertyCoords.y, PropertyCoords.z)) > MaxDoorlockDistance then
TriggerEvent('brutal_housing:client:SendNotifyNumber', 75)
return
end
else
return
end
PropertyID = nil
PropertyCoords = nil
end
------------------------------------
------- Brutal Housing Editing -----
------------------------------------
TriggerServerEvent('ox_doorlock:editDoorlock', data.id or false, data)
table.wipe(tempData)
end)
RegisterNUICallback('deleteDoor', function(id, cb)
cb(1)
TriggerServerEvent('ox_doorlock:editDoorlock', id)
end)
RegisterNUICallback('teleportToDoor', function(id, cb)
cb(1)
SetNuiFocus(false, false)
local doorCoords = doors[id].coords
if not doorCoords then return end
SetEntityCoords(cache.ped, doorCoords.x, doorCoords.y, doorCoords.z, false, false, false, false)
end)
RegisterNUICallback('exit', function(_, cb)
cb(1)
SetNuiFocus(false, false)
end)
------------------------------------
------- Brutal Housing Editing -----
------------------------------------
PropertyID = nil
PropertyCoords = nil
MaxDoorlockDistance = 50.0
RegisterNetEvent("ox_doorlock:dataFromHousing")
AddEventHandler("ox_doorlock:dataFromHousing", function(propertyID, coords, maxdoorlockdistance)
PropertyID = propertyID
PropertyCoords = coords
MaxDoorlockDistance = maxdoorlockdistance
if isAddingDoorlock then return end
NuiHasLoaded = true
local allowedDoors = {}
for k,v in pairs(doors) do
print(v.name:match("^(.-)_"), PropertyID)
if v.name:match("^(.-)_") == PropertyID then
table.insert(allowedDoors, v)
end
end
SendNuiMessage(json.encode({
action = 'updateDoorData',
data = allowedDoors
}, { with_hole = false }))
Wait(100)
SendNUIMessage({
action = 'setSoundFiles',
data = lib.callback.await('ox_doorlock:getSounds', false)
})
SetNuiFocus(true, true)
SendNuiMessage(json.encode({
action = 'setVisible',
data = id
}))
end)
------------------------------------
------- Brutal Housing Editing -----
------------------------------------
local function openUi(id)
if source == '' or isAddingDoorlock then return end
------------------------------------
------- Brutal Housing Editing -----
------------------------------------
PropertyID = nil
PropertyCoords = nil
------------------------------------
------- Brutal Housing Editing -----
------------------------------------
NuiHasLoaded = true
SendNuiMessage(json.encode({
action = 'updateDoorData',
data = doors
}, { with_hole = false }))
Wait(100)
SendNUIMessage({
action = 'setSoundFiles',
data = lib.callback.await('ox_doorlock:getSounds', false)
})
SetNuiFocus(true, true)
SendNuiMessage(json.encode({
action = 'setVisible',
data = id
}))
end
RegisterNetEvent('ox_doorlock:triggeredCommand', function(closest)
openUi(closest and ClosestDoor?.id or nil)
end)
CreateThread(function()
local target
if GetResourceState('ox_target'):find('start') then
target = {
ox = true,
exp = exports.ox_target
}
elseif GetResourceState('qb-target'):find('start') then
target = {
qb = true,
exp = exports['qb-target']
}
elseif GetResourceState('qtarget'):find('start') then
target = {
qt = true,
exp = exports.qtarget
}
end
if not target then return end
if target.ox then
target.exp:addGlobalObject({
{
name = 'pickDoorlock',
label = locale('pick_lock'),
icon = 'fas fa-user-lock',
onSelect = pickLock,
canInteract = canPickLock,
items = Config.LockpickItems,
anyItem = true,
distance = 1
}
})
else
local options = {
{
label = locale('pick_lock'),
icon = 'fas fa-user-lock',
action = pickLock,
canInteract = canPickLock,
item = Config.LockpickItems[1],
distance = 1
}
}
---@cast target table
if target.qt then
target.exp:Object({ options = options })
elseif target.qb then
target.exp:AddGlobalObject({ options = options })
end
options = { locale('pick_lock') }
AddEventHandler('onResourceStop', function(resource)
if resource == cache.resource then
if target.qt then
return target.exp:RemoveObject(options)
end
if target.qb then
return target.exp:RemoveGlobalObject(options)
end
end
end)
end
end)