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('oxmysql', '2.4.0') then return end if not lib.checkDependency('ox_lib', '3.14.0') then return end lib.versionCheck('overextended/ox_doorlock') require 'server.convert' local utils = require 'server.utils' local doors = {} local function encodeData(door) local double = door.doors return json.encode({ auto = door.auto, autolock = door.autolock, coords = door.coords, doors = double and { { coords = double[1].coords, heading = double[1].heading, model = double[1].model, }, { coords = double[2].coords, heading = double[2].heading, model = double[2].model, }, }, characters = door.characters, groups = door.groups, heading = door.heading, items = door.items, lockpick = door.lockpick, hideUi = door.hideUi, holdOpen = door.holdOpen, lockSound = door.lockSound, maxDistance = door.maxDistance, doorRate = door.doorRate, model = door.model, state = door.state, unlockSound = door.unlockSound, passcode = door.passcode, lockpickDifficulty = door.lockpickDifficulty }) end local function getDoor(door) door = type(door) == 'table' and door or doors[door] if not door then return false end return { id = door.id, name = door.name, state = door.state, coords = door.coords, characters = door.characters, groups = door.groups, items = door.items, maxDistance = door.maxDistance, } end exports('getDoor', getDoor) exports('getAllDoors', function() local allDoors = {} for _, door in pairs(doors) do allDoors[#allDoors+1] = getDoor(door) end return allDoors end) exports('getDoorFromName', function(name) for _, door in pairs(doors) do if door.name == name then return getDoor(door) end end end) exports('editDoor', function(id, data) local door = doors[id] if door then for k, v in pairs(data) do if k ~= 'id' then local current = door[k] local t1 = type(current) local t2 = type(v) if t1 ~= 'nil' and v ~= '' and t1 ~= t2 then error(("Expected '%s' for door.%s, received %s (%s)"):format(t1, k, t2, v)) end door[k] = v ~= '' and v or nil end end MySQL.update('UPDATE ox_doorlock SET name = ?, data = ? WHERE id = ?', { door.name, encodeData(door), id }) TriggerClientEvent('ox_doorlock:editDoorlock', -1, id, door) end end) local soundDirectory = Config.NativeAudio and 'audio/dlc_oxdoorlock/oxdoorlock' or 'web/build/sounds' local fileFormat = Config.NativeAudio and '%.wav' or '%.ogg' local sounds = utils.getFilesInDirectory(soundDirectory, fileFormat) lib.callback.register('ox_doorlock:getSounds', function() return sounds end) local function createDoor(id, door, name) local double = door.doors door.id = id door.name = name if double then for i = 1, 2 do double[i].hash = joaat(('ox_door_%s_%s'):format(id, i)) local coords = double[i].coords double[i].coords = vector3(coords.x, coords.y, coords.z) end if not door.coords then door.coords = double[1].coords - ((double[1].coords - double[2].coords) / 2) end else door.hash = joaat(('ox_door_%s'):format(id)) end door.coords = vector3(door.coords.x, door.coords.y, door.coords.z) if not door.state then door.state = 1 end if type(door.items?[1]) == 'string' then local items = {} for i = 1, #door.items do items[i] = { name = door.items[i], remove = false, } end door.items = items MySQL.update('UPDATE ox_doorlock SET data = ? WHERE id = ?', { encodeData(door), id }) end doors[id] = door return door end local isLoaded = false local ox_inventory = exports.ox_inventory SetTimeout(0, function() if GetPlayer then return end function GetPlayer(_) end end) function RemoveItem(playerId, item, slot) local player = GetPlayer(playerId) if player then ox_inventory:RemoveItem(playerId, item, 1, nil, slot) end end ---@param player table ---@param items string[] | { name: string, remove?: boolean, metadata?: string }[] ---@param removeItem? boolean ---@return string? function DoesPlayerHaveItem(player, items, removeItem) local playerId = player.source or player.PlayerData.source for i = 1, #items do local item = items[i] local itemName = item.name or item local data = ox_inventory:Search(playerId, 'slots', itemName, item.metadata)[1] if data and data.count > 0 then if removeItem or item.remove then ox_inventory:RemoveItem(playerId, itemName, 1, nil, data.slot) end return itemName end end end local function isAuthorised(playerId, door, lockpick) if Config.PlayerAceAuthorised and IsPlayerAceAllowed(playerId, 'command.doorlock') then return true end -- e.g. add_ace group.police "doorlock.mrpd locker rooms" allow -- add_principal fivem:123456 group.police -- or add_ace identifier.fivem:123456 "doorlock.mrpd locker rooms" allow if IsPlayerAceAllowed(playerId, ('doorlock.%s'):format(door.name)) then return true end local player = GetPlayer(playerId) local authorised = door.passcode or false --[[@as boolean | string | nil]] if player then if lockpick then return DoesPlayerHaveItem(player, Config.LockpickItems) end if door.characters and table.contains(door.characters, GetCharacterId(player)) then return true end if door.groups then authorised = IsPlayerInGroup(player, door.groups) and true or nil end if not authorised and door.items then authorised = DoesPlayerHaveItem(player, door.items) or nil end if authorised ~= nil and door.passcode then authorised = door.passcode == lib.callback.await('ox_doorlock:inputPassCode', playerId) end ------------------------------------ ------- Brutal Housing Editing ----- ------------------------------------ if GetResourceState("brutal_housing") == "started" then local propertyID = door.name:match("^(.-)_") if propertyID ~= nil then TriggerEvent('brutal_housing:server:hasKeyToHouse', playerId, propertyID, function(hasKey) authorised = hasKey end) end end ------------------------------------ ------- Brutal Housing Editing ----- ------------------------------------ end return authorised end local sql = LoadResourceFile(cache.resource, 'sql/ox_doorlock.sql') if sql then MySQL.query(sql) end MySQL.ready(function() while Config.DoorList do Wait(100) end local response = MySQL.query.await('SELECT `id`, `name`, `data` FROM `ox_doorlock`') for i = 1, #response do local door = response[i] createDoor(door.id, json.decode(door.data), door.name) end isLoaded = true TriggerEvent('ox_doorlock:loaded') end) ---@param id number ---@param state 0 | 1 | boolean ---@param lockpick? boolean ---@return boolean local function setDoorState(id, state, lockpick) local door = doors[id] state = (state == 1 or state == 0) and state or (state and 1 or 0) if door then local authorised = not source or source == '' or isAuthorised(source, door, lockpick) if authorised then door.state = state TriggerClientEvent('ox_doorlock:setState', -1, id, state, source) if door.autolock and state == 0 then SetTimeout(door.autolock * 1000, function() if door.state ~= 1 then door.state = 1 TriggerClientEvent('ox_doorlock:setState', -1, id, door.state) TriggerEvent('ox_doorlock:stateChanged', nil, door.id, door.state == 1) end end) end TriggerEvent('ox_doorlock:stateChanged', source, door.id, state == 1, type(authorised) == 'string' and authorised) return true end if source then lib.notify(source, { type = 'error', icon = 'lock', description = state == 0 and 'cannot_unlock' or 'cannot_lock' }) end end return false end RegisterNetEvent('ox_doorlock:setState', setDoorState) exports('setDoorState', setDoorState) lib.callback.register('ox_doorlock:getDoors', function() while not isLoaded do Wait(100) end return doors, sounds end) RegisterNetEvent('ox_doorlock:editDoorlock', function(id, data) if IsPlayerAceAllowed(source, 'command.doorlock') then if data then if not data.coords then local double = data.doors data.coords = double[1].coords - ((double[1].coords - double[2].coords) / 2) end if not data.name then data.name = tostring(data.coords) end end if id then if data then MySQL.update('UPDATE ox_doorlock SET name = ?, data = ? WHERE id = ?', { data.name, encodeData(data), id }) else MySQL.update('DELETE FROM ox_doorlock WHERE id = ?', { id }) end doors[id] = data TriggerClientEvent('ox_doorlock:editDoorlock', -1, id, data) else local insertId = MySQL.insert.await('INSERT INTO ox_doorlock (name, data) VALUES (?, ?)', { data.name, encodeData(data) }) local door = createDoor(insertId, data, data.name) TriggerClientEvent('ox_doorlock:setState', -1, door.id, door.state, false, door) end end end) RegisterNetEvent('ox_doorlock:breakLockpick', function() local player = GetPlayer(source) return player and DoesPlayerHaveItem(player, Config.LockpickItems, true) end) lib.addCommand('doorlock', { help = locale('create_modify_lock'), params = { { name = 'closest', help = locale('command_closest'), optional = true, }, }, restricted = Config.CommandPrincipal }, function(source, args) TriggerClientEvent('ox_doorlock:triggeredCommand', source, args.closest) end)