Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions bridge/esx/server.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
local ESX = exports["es_extended"]:getSharedObject()
local Bridge = {}

-- Alpha

-- Framework-specific character unload handler
function Bridge.onCharacterUnloaded(callback)
AddEventHandler("esx:playerLogout", callback)
end

-- Alpha

--[[ NEEDS TO BE CREATED ]]
-- SetTimeout(500, function()
-- NDCore:loadSQL({
Expand Down
23 changes: 16 additions & 7 deletions bridge/nd/server.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
local NDCore = exports["ND_Core"]
local Bridge = {}

-- Alpha

-- Framework-specific character unload handler
function Bridge.onCharacterUnloaded(callback)
AddEventHandler("ND:characterUnloaded", callback)
end

-- Alpha

SetTimeout(500, function()
NDCore:loadSQL({
"bridge/nd/database/bolos.sql",
Expand Down Expand Up @@ -29,7 +38,7 @@ local function queryDatabaseProfiles(first, last)
local firstname = (item.firstname or ""):lower()
local lastname = (item.lastname or ""):lower()

if first ~= "" and firstname:find(first) or last ~= "" and lastname:find(last) then
if first ~= "" and firstname:find(first) or last ~= "" and lastname:find(last) then
profiles[item.charid] = {
firstName = item.firstname,
lastName = item.lastname,
Expand Down Expand Up @@ -337,10 +346,10 @@ function Bridge.viewEmployees(src, search)
if ply.id == info.charid then
local job, jobInfo = getPermsFromGroups(ply.groups)
if not config.policeAccess[job] then goto next end

local metadata = ply.metadata
if not filterEmployeeSearch(ply, metadata, search or "") then goto next end

employees[#employees+1] = {
source = ply.source,
charId = ply.id,
Expand All @@ -357,15 +366,15 @@ function Bridge.viewEmployees(src, search)
goto next
end
end

local groups = info.groups and json.decode(info.groups) or {}
local job, jobInfo = getPermsFromGroups(groups)

if not config.policeAccess[job] then goto next end

local metadata = info.metadata and json.decode(info.metadata) or {}
if not filterEmployeeSearch(info, metadata, search or "") then goto next end

employees[#employees+1] = {
charId = info.charid,
first = info.firstname,
Expand All @@ -390,7 +399,7 @@ function Bridge.employeeUpdateCallsign(src, charid, callsign)
if not player then
return false, "An issue occured try again later!"
end

if not tonumber(callsign) then
return false, "Callsign must be a number!"
end
Expand Down Expand Up @@ -453,7 +462,7 @@ function Bridge.employeeUpdateCallsign(src, charid, callsign)
end

characterMetadata.callsign = callsign

MySQL.update.await("UPDATE nd_characters SET `metadata` = ? WHERE charid = ?", {
json.encode(characterMetadata),
charid
Expand Down
87 changes: 87 additions & 0 deletions bridge/qb/client.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
local QBCore = exports["qb-core"]:GetCoreObject()
local Bridge = {}

---@return table
function Bridge.getPlayerInfo()
local player = QBCore.Functions.GetPlayerData() or {}
return {
firstName = player.charinfo.firstname,
lastName = player.charinfo.lastname,
job = player.job.name,
jobLabel = player.job.label,
callsign = player.metadata.callsign,
img = player.metadata.img or "user.jpg",
isBoss = player.job.isboss
}
end

---@param job string
---@return boolean
function Bridge.hasAccess(job)
return config.policeAccess[job] or config.fireAccess[job]
end

---@return string
function Bridge.rankName()
local player = QBCore.Functions.GetPlayerData()
return player.job.grade.name or ""
end

---@param id number
---@param info table
---@return table
--- info is from returned profiles in server.lua
function Bridge.getCitizenInfo(id, info)
return {
img = info.img or "user.jpg",
characterId = id,
firstName = info.firstName,
lastName = info.lastName,
dob = info.dob,
gender = info.gender,
phone = info.phone,
ethnicity = info.ethnicity
}
end

function Bridge.getRanks(job)
local jobGrades = QBCore.Shared.Jobs[job] and QBCore.Shared.Jobs[job].grades or nil
if not jobGrades then return end

local options = {}
for grade, info in pairs(jobGrades) do
options[#options + 1] = {
value = tonumber(grade),
label = info.name
}
end
table.sort(options, function(a, b) return a.value < b.value end)

return options, job
end

AddEventHandler("QBCore:Client:OnPlayerUnload", function()
TriggerServerEvent("ND_MDT:qb:server:playerUnloaded")
end)

if GetResourceState("qb-inventory") == "started" then
RegisterNetEvent("ND_MDT:qb:client:useTablet")
AddEventHandler("ND_MDT:qb:client:useTablet", function()
if not exports["qb-inventory"]:HasItem("mdt", 1) then return end
exports["ND_MDT"]:useTablet()
end)
end

-- Waypoint event for house locations
RegisterNetEvent("ND_MDT:setWaypoint")
AddEventHandler("ND_MDT:setWaypoint", function(x, y)
local playerInfo = Bridge.getPlayerInfo()
if not Bridge.hasAccess(playerInfo.job) then
QBCore.Functions.Notify('You do not have access to this command', 'error')
return
end

SetNewWaypoint(x, y)
end)

return Bridge
7 changes: 7 additions & 0 deletions bridge/qb/database/bolos.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS `nd_mdt_bolos` (
`id` INT(11) AUTO_INCREMENT,
`type` VARCHAR(50) DEFAULT NULL,
`data` LONGTEXT DEFAULT '[]',
`timestamp` INT(11) DEFAULT unix_timestamp(),
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
2 changes: 2 additions & 0 deletions bridge/qb/database/improvements.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE `player_vehicles`
ADD COLUMN IF NOT EXISTS `stolen` BOOLEAN NOT NULL DEFAULT FALSE;
6 changes: 6 additions & 0 deletions bridge/qb/database/records.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS `nd_mdt_records` (
`character` VARCHAR(50) NOT NULL COLLATE utf8mb4_general_ci,
`records` LONGTEXT DEFAULT '[]',
PRIMARY KEY (`character`),
CONSTRAINT `FK_nd_mdt_records_players` FOREIGN KEY (`character`) REFERENCES `players` (`citizenid`) ON UPDATE CASCADE ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
8 changes: 8 additions & 0 deletions bridge/qb/database/reports.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS `nd_mdt_reports` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`type` VARCHAR(50) DEFAULT NULL,
`data` LONGTEXT DEFAULT '[]',
`timestamp` INT(11) DEFAULT unix_timestamp(),
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

11 changes: 11 additions & 0 deletions bridge/qb/database/weapons.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
CREATE TABLE IF NOT EXISTS `nd_mdt_weapons` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`character` VARCHAR(50) NOT NULL COLLATE utf8mb4_general_ci,
`weapon` VARCHAR(50) DEFAULT NULL,
`serial` VARCHAR(50) DEFAULT NULL,
`owner_name` VARCHAR(100) DEFAULT NULL,
`stolen` INT(1) DEFAULT '0',
PRIMARY KEY (`id`),
INDEX `FK_nd_mdt_weapons_players` (`character`) USING BTREE,
CONSTRAINT `FK_nd_mdt_weapons_players` FOREIGN KEY (`character`) REFERENCES `players` (`citizenid`) ON UPDATE CASCADE ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
107 changes: 107 additions & 0 deletions bridge/qb/notes.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# QBCore Integration Notes

## Weapon Registration Integration for qb-inventory

If you are using qb-inventory instead of ox_inventory, you need to manually add weapon registration to your qb-inventory.

### Installation Steps:

1. Open your `qb-inventory/server/functions.lua` file
2. Find the `AddItem` function (around line 667-774)
3. Locate the weapon handling section that looks like this:

```lua
if itemInfo.type == 'weapon' then
if not inventory[slot].info.serie then
inventory[slot].info.serie = tostring(QBCore.Shared.RandomInt(2) .. QBCore.Shared.RandomStr(3) .. QBCore.Shared.RandomInt(1) .. QBCore.Shared.RandomStr(2) .. QBCore.Shared.RandomInt(3) .. QBCore.Shared.RandomStr(4))
end
if not inventory[slot].info.quality then
inventory[slot].info.quality = 100
end
end
```

4. Replace that section with this enhanced version:

```lua
if itemInfo.type == 'weapon' then
if not inventory[slot].info.serie then
inventory[slot].info.serie = tostring(QBCore.Shared.RandomInt(2) .. QBCore.Shared.RandomStr(3) .. QBCore.Shared.RandomInt(1) .. QBCore.Shared.RandomStr(2) .. QBCore.Shared.RandomInt(3) .. QBCore.Shared.RandomStr(4))
end
if not inventory[slot].info.quality then
inventory[slot].info.quality = 100
end

-- MDT Weapon Registration with duplicate check
if player and inventory[slot].info.serie then
local success, result = pcall(function()
return exports['ND_MDT']:registerWeapon(identifier, itemInfo.label, inventory[slot].info.serie)
end)
if not success then
print('[qb-inventory] Failed to register weapon in MDT:', result)
end
end
end
```

### What this does:

- Automatically registers weapons in the MDT database when they are added to a player's inventory
- Includes duplicate check to prevent re-registering the same weapon serial number
- Uses the weapon's serial number, label, and player information
- Includes error handling to prevent inventory issues if MDT is not available
- Only registers weapons for actual players (not for inventories or drops)

### Requirements:

- Make sure ND_MDT resource is started before qb-inventory
- The player must be online when receiving the weapon
- The weapon must have a valid serial number

### Notes:

- This integration works for all weapons added through the AddItem function
- Weapons added through other methods may not be automatically registered
- You can verify registration by checking the weapons section in the MDT interface

🐞 KNOWN BUGS
1-)
Bug Report: Only the Last Added Record Is Saved on "Save All Changes"

Description:
When multiple records are added in the "Records" section (e.g., 5 records), and then "Save All Changes" is clicked, only the last record added gets saved. The previously added records are not persisted unless "Save All Changes" is clicked after each individual record addition.

Steps to Reproduce:

Go to the Records section.

Add multiple records (e.g., 5 records) without clicking "Save All Changes" after each addition.

Click "Save All Changes" only once after adding all the records.

Refresh or revisit the page.

Expected Result:
All added records should be saved when "Save All Changes" is clicked once.

Actual Result:
Only the last added record is saved; the others are lost.

2-)
Important Notice About License Dates

**WARNING**: The license system currently does not implement proper date/time management.

The timestamp data for licenses (issued date, expiry date, etc.) should be considered as **random/placeholder data** and not reliable for actual license validation or expiry checking.

### Current Limitations:
- License dates are generated but not validated
- No automatic expiry system is implemented
- Timestamps are for display/record purposes only
- No background job checks for expired licenses

### Recommendations:
- Treat all license dates as informational only
- Do not rely on expiry dates for game mechanics
- Consider implementing proper date validation if needed for your server
- Manual license management is recommended until proper date system is implemented
Loading