Files
Elite-Gaming-FiveM/resources/[ERS]/night_ers/server/s_functions.lua
T
2025-08-21 07:59:16 -07:00

828 lines
33 KiB
Lua

QBCore = nil
ESX = nil
Citizen.CreateThread(function()
if Config.Enable_QBCore_Permissions.Check_By_Job or Config.Enable_QBCore_Permissions.Check_By_Permissions or (Config.GearData.Enable_Gear_Permissions and Config.GearData.Enable_QBCore_Permissions.Check_By_Job or Config.GearData.Enable_QBCore_Permissions.Check_By_Permissions) then
QBCore = exports["qb-core"]:GetCoreObject()
end
if Config.Enable_ESX_Permissions.Check_By_Job or Config.Enable_ESX_Permissions.Check_By_Permissions or (Config.GearData.Enable_Gear_Permissions and Config.GearData.Enable_ESX_Permissions.Check_By_Job or Config.GearData.Enable_ESX_Permissions.Check_By_Permissions) then
ESX = exports["es_extended"]:getSharedObject()
end
end)
-- ============================================
-- ERS INTEGRATION (For developers only, no customization support will be provided for this by Nights Software)
-- ============================================
--- Handles user shift toggle events.
-- @param src number The source ID of the user who toggled shift.
-- @param isOnShift boolean Whether the user is now on shift or off shift.
-- @param serviceType string The service type of the shift (police, fire, ambulance, tow).
RegisterServerEvent("ErsIntegration::OnToggleShift")
AddEventHandler("ErsIntegration::OnToggleShift", function(source, isOnShift, serviceType)
-- Add your custom shift toggle logic here
-- print(source, isOnShift, serviceType)
end)
--- Handles first-time NPC interaction events.
-- @param src number The source ID of the user who interacted with the NPC.
-- @param pedData table The complete data table of the NPC being interacted with.
-- @param context string The interaction context:
RegisterServerEvent("ErsIntegration::OnFirstNPCInteraction")
AddEventHandler("ErsIntegration::OnFirstNPCInteraction", function(source, pedData, context)
-- Add your custom NPC interaction logic here
--[[
Context frequency analysis:
-- Explained: "First interaction with this specific NPC instance in current session, unless the network ID was replaced. Then it can be an other instance for the same NPC."
- "on_interaction": Very common (normal ped interactions)
- "on_aiming_at_ped": Common (when aiming at peds and ordering them to kneel)
- "on_pullover": Common (traffic stops)
- "on_pursuit_start": Uncommon (callout/world event peds fleeing, regular peds are usually interacted with first)
- "on_pursuit_end": Very rare (edge cases only)
- "on_pullover_end": Very rare (edge cases only)
]]
-- print(source, json.encode(pedData, { indent = true }), context)
end)
-- Handles first vehicle interaction events.
-- @param src number The source ID of the user who interacted with the vehicle.
-- @param vehicleData table The complete data table of the vehicle being interacted with.
-- @param context string The interaction context:
RegisterServerEvent("ErsIntegration::OnFirstVehicleInteraction")
AddEventHandler("ErsIntegration::OnFirstVehicleInteraction", function(source, vehicleData, context)
-- Add your custom vehicle interaction logic here
--[[
Context frequency analysis:
-- Explained: "First interaction with this specific vehicle instance in current session, unless the network ID was replaced. Then it can be an other instance for the same vehicle."
- "on_pullover": Very common (traffic stops)
- "on_vehicle_search": Common (when searching for vehicles)
- "on_pursuit_start": Uncommon (callout/world event peds fleeing whilst in a vehicle and not interacted with yet)
- "on_pullover_end": Very rare (edge cases only)
]]
-- print(source, json.encode(vehicleData, { indent = true }), context)
end)
--- Handles when a callout is offered.
-- @param calloutData table The data of the callout.
RegisterServerEvent("ErsIntegration::OnIsOfferedCallout")
AddEventHandler("ErsIntegration::OnIsOfferedCallout", function(calloutData)
local src = source
-- Add your custom callout offered logic here
-- print(src, calloutData)
end)
--- Handles when a callout is accepted.
-- @param calloutData table The data of the callout.
RegisterServerEvent("ErsIntegration::OnAcceptedCalloutOffer")
AddEventHandler("ErsIntegration::OnAcceptedCalloutOffer", function(calloutData)
local src = source
-- Add your custom callout accepted logic here
-- print(src, calloutData)
end)
--- Handles when a callout is arrived at.
-- @param calloutData table The data of the callout.
RegisterServerEvent("ErsIntegration::OnArrivedAtCallout")
AddEventHandler("ErsIntegration::OnArrivedAtCallout", function(calloutData)
local src = source
-- Add your custom callout arrived at logic here
-- print(src, calloutData)
end)
--- Handles when a callout is ended.
RegisterServerEvent("ErsIntegration::OnEndedACallout")
AddEventHandler("ErsIntegration::OnEndedACallout", function()
local src = source
-- Add your custom callout ended logic here
-- print(src)
end)
--- Handles when a callout is completed successfully.
-- @param calloutData table The data of the callout.
RegisterServerEvent("ErsIntegration::OnCalloutCompletedSuccesfully")
AddEventHandler("ErsIntegration::OnCalloutCompletedSuccesfully", function(calloutData)
local src = source
-- Add your custom callout completed successfully logic here
-- print(src, calloutData)
end)
--- Handles when a pullover is initiated.
-- @param pedData table The data of the ped.
-- @param vehicleData table The data of the vehicle.
RegisterServerEvent("ErsIntegration::OnPullover")
AddEventHandler("ErsIntegration::OnPullover", function(pedData, vehicleData)
local src = source
-- Add your custom pullover logic here
-- print(src, pedData, vehicleData)
end)
--- Handles when a pullover is ended.
-- @param pedData table The data of the ped.
-- @param vehicleData table The data of the vehicle.
RegisterServerEvent("ErsIntegration::OnPulloverEnded")
AddEventHandler("ErsIntegration::OnPulloverEnded", function(pedData, vehicleData)
local src = source
-- Add your custom pullover ended logic here
-- print(src, pedData, vehicleData)
end)
--- Handles when a pursuit is started.
-- @param pedNetId number The network ID of the ped.
RegisterServerEvent("ErsIntegration::OnPursuitStarted")
AddEventHandler("ErsIntegration::OnPursuitStarted", function(pedData)
local src = source
-- Add your custom pursuit started logic here
-- print(src, pedData)
end)
--- Handles when a pursuit is ended.
-- @param pedData table The data of the ped.
RegisterServerEvent("ErsIntegration::OnPursuitEnded")
AddEventHandler("ErsIntegration::OnPursuitEnded", function(pedData)
local src = source
-- Add your custom pursuit ended logic here, note that it's possible that the ped is being deleted on losing the target.
-- print(src, pedData)
end)
-- ============================================
-- GEAR SYSTEM
-- ============================================
--- Handles weapon, ammo, and component distribution to players.
-- Supports QBCore, ESX, and default GTA V weapon systems.
-- @param weaponData table The weapon data containing weapons, ammo, and components.
RegisterServerEvent(Config.EventPrefix..":setWeaponsAmmoComponents")
AddEventHandler(Config.EventPrefix..":setWeaponsAmmoComponents", function(weaponData)
local src = source
if QBCore then
local Player = QBCore.Functions.GetPlayer(src)
if not Player then
DebugPrint("ERROR: Could not find QBCore Player with ID: "..src)
return
end
if weaponData then
for k, v in pairs(weaponData) do
if exports['qb-inventory']:CanAddItem(src, v.weaponName, 1) then
exports['qb-inventory']:AddItem(src, v.weaponName, 1)
-- Add ammo for this weapon
if v.ammoType and v.ammoCount then
if exports['qb-inventory']:CanAddItem(src, v.ammoType, v.ammoCount) then
exports['qb-inventory']:AddItem(src, v.ammoType, v.ammoCount)
end
end
-- Add weapon attachments (components)
for _, component in pairs(v.componentList) do
if exports['qb-inventory']:CanAddItem(src, component, 1) then
exports['qb-inventory']:AddItem(src, component, 1)
end
end
end
end
end
elseif ESX then
local xPlayer = ESX.GetPlayerFromId(src)
if not xPlayer then
DebugPrint("ERROR: Could not find xPlayer with ID: "..src)
return
end
if weaponData then
for k, v in pairs(weaponData) do
if xPlayer.hasWeapon(v.weaponName) then
xPlayer.removeWeapon(v.weaponName)
xPlayer.addWeapon(v.weaponName, v.ammoCount)
xPlayer.addWeaponAmmo(v.weaponName, v.ammoCount)
else
xPlayer.addWeapon(v.weaponName, v.ammoCount)
end
-- Add weapon attachments (components)
for _, component in pairs(v.componentList) do
if xPlayer.hasWeaponComponent(v.weaponName, component) then
xPlayer.removeWeaponComponent(v.weaponName, component)
xPlayer.addWeaponComponent(v.weaponName, component)
else
xPlayer.addWeaponComponent(v.weaponName, component)
end
end
end
end
else
-- Default GTA V Weapons
for k, v in pairs(weaponData) do
local playerPed = GetPlayerPed(src)
GiveWeaponToPed(playerPed, GetHashKey(v.weaponName), v.ammoCount, false, true)
if #v.componentList > 0 then
for i, component in pairs(v.componentList) do
GiveWeaponComponentToPed(playerPed, GetHashKey(v.weaponName), GetHashKey(component))
DebugPrint("[^4DEBUG ^7] Set component "..component.." to given weapon "..v.weaponName)
end
end
end
end
-- Add other or more logic here...
end)
-- ============================================
-- SERVICE PERMISSION MANAGEMENT
-- ============================================
-- Checks if a player is authorized to access a specific service.
-- Supports multiple permission systems: Discord API, Ace, ESX, QBCore.
-- @param source number The player's source ID.
-- @param rolesOrGroups table Array of roles or groups to check against.
-- @return boolean True if player has permission, false otherwise.
function CheckIsPlayerAllowedToPlayServiceByRoleOrGroups(source, rolesOrGroups)
local permission = false
-- If this is enabled, everyone can play any service at any time.
if Config.EveryoneHasPermission then
return true
end
-- Discord API Permissions
if Config.Enable_Night_DiscordApi_Permissions then
local isPermitted = exports.night_discordapi:IsMemberPartOfAnyOfTheseRoles(source, rolesOrGroups)
if isPermitted then
permission = true
end
end
-- Ace Permissions
if Config.Enable_Ace_Permissions then
for _, roleOrGroup in pairs(rolesOrGroups) do
if IsPlayerAceAllowed(source, roleOrGroup) then
permission = true
break
end
end
end
-- ESX Job Permissions
if Config.Enable_ESX_Permissions.Check_By_Job then
if ESX == nil then return print("You've enabled ESX job permissions, but the ESX framework has not been found...") end
local xPlayer = ESX.GetPlayerFromId(source)
if xPlayer then
for _, job in pairs(rolesOrGroups) do
if xPlayer.job.name == job then
permission = true
break
end
end
end
end
-- ESX Permission Based
if Config.Enable_ESX_Permissions.Check_By_Permissions then
if ESX == nil then return print("You've enabled ESX group permissions, but the ESX framework has not been found...") end
local xPlayer = ESX.GetPlayerFromId(source)
if xPlayer then
for _, group in pairs(rolesOrGroups) do
if xPlayer.getGroup() == group then
permission = true
break
end
end
end
end
-- QBCore Job Based
if Config.Enable_QBCore_Permissions.Check_By_Job then
if QBCore == nil then return print("You've enabled QBCore job permissions, but the QBCore framework has not been found...") end
local Player = QBCore.Functions.GetPlayer(source)
if Player then
for _, job in pairs(rolesOrGroups) do
if Player.PlayerData.job.name == job then
permission = true
break
end
end
end
end
-- QBCore Permission based
if Config.Enable_QBCore_Permissions.Check_By_Permissions then
if QBCore == nil then return print("You've enabled QBCore group permissions, but the QBCore framework has not been found...") end
local Player = QBCore.Functions.GetPlayer(source)
if Player then
for _, perm in pairs(rolesOrGroups) do
if QBCore.Functions.HasPermission(source, perm) then
permission = true
end
end
end
end
return permission
end
-- ============================================
-- GEAR LOADOUT PERMISSION MANAGEMENT
-- ============================================
-- Checks if a player is authorized to select specific gear loadouts.
-- Supports multiple permission systems: Discord API, Ace, ESX, QBCore.
-- @param source number The player's source ID.
-- @param rolesOrGroups table Array of roles or groups to check against.
-- @return boolean True if player has permission, false otherwise.
function CheckIsPlayerAllowedToSelectGearByRoleOrGroups(source, rolesOrGroups)
local permission = false
-- If this is enabled, everyone can select any gear loadout/uniform.
if not Config.GearData.Enable_Gear_Permissions then
return true
end
-- Discord API Permissions
if Config.GearData.Enable_Night_DiscordApi_Permissions then
local isPermitted = exports.night_discordapi:IsMemberPartOfAnyOfTheseRoles(source, rolesOrGroups)
if isPermitted then
permission = true
end
end
-- Ace Permissions
if Config.GearData.Enable_Ace_Permissions then
for _, roleOrGroup in pairs(rolesOrGroups) do
if IsPlayerAceAllowed(source, roleOrGroup) then
permission = true
break
end
end
end
-- ESX Job Permissions
if Config.GearData.Enable_ESX_Permissions.Check_By_Job then
if ESX == nil then return print("You've enabled ESX job permissions, but the ESX framework has not been found...") end
local xPlayer = ESX.GetPlayerFromId(source)
if xPlayer then
for _, job in pairs(rolesOrGroups) do
if xPlayer.job.name == job then
permission = true
break
end
end
end
end
-- ESX Permission Based
if Config.GearData.Enable_ESX_Permissions.Check_By_Permissions then
if ESX == nil then return print("You've enabled ESX group permissions, but the ESX framework has not been found...") end
local xPlayer = ESX.GetPlayerFromId(source)
if xPlayer then
for _, group in pairs(rolesOrGroups) do
if xPlayer.getGroup() == group then
permission = true
break
end
end
end
end
-- QBCore Job Based
if Config.GearData.Enable_QBCore_Permissions.Check_By_Job then
if QBCore == nil then return print("You've enabled QBCore job permissions, but the QBCore framework has not been found...") end
local Player = QBCore.Functions.GetPlayer(source)
if Player then
for _, job in pairs(rolesOrGroups) do
if Player.PlayerData.job.name == job then
permission = true
break
end
end
end
end
-- QBCore Permission based
if Config.GearData.Enable_QBCore_Permissions.Check_By_Permissions then
if QBCore == nil then return print("You've enabled QBCore group permissions, but the QBCore framework has not been found...") end
local Player = QBCore.Functions.GetPlayer(source)
if Player then
for _, perm in pairs(rolesOrGroups) do
if QBCore.Functions.HasPermission(source, perm) then
permission = true
end
end
end
end
return permission
end
-- ============================================
-- UTILITY FUNCTIONS
-- ============================================
-- Retrieves custom player name for shift management integration.
-- Integrates with night_shifts MDT system if enabled.
-- @param id number|string The player's source ID.
-- @return string The player's callsign or display name.
function GetCustomPlayerName(id)
local src = tonumber(id)
local srcName = GetPlayerName(src)
if Config.Enable_Night_Shifts.ManageShiftsByMDT then
local shiftDataResults = exports['night_shifts']:GetUserShiftData(src) or nil
-- print(json.encode(shiftDataResults))
if shiftDataResults and #shiftDataResults > 0 then
for k, v in pairs(shiftDataResults) do
srcName = v.last_callsign or v.userCallsign
-- if type(v) == "table"then
-- print("Key: "..k.." | Value: "..json.encode(v))
-- elseif type(v) == "boolean" then
-- if v then
-- print("Key: "..k.." | Value: true")
-- else
-- print("Key: "..k.." | Value: false")
-- end
-- else
-- print("Key: "..k.." | Value: "..v)
-- end
end
end
end
return srcName
-- Or write your own stuff here.
end
--- Capitalizes the first letter of a string.
-- @param str string The input string.
-- @return string String with first letter capitalized.
function firstToUpper(str)
return (str:gsub("^%l", string.upper))
end
--- Converts entire string to uppercase.
-- @param str string The input string.
-- @return string Fully uppercase string.
function allToUpper(str)
return (string.upper(str))
end
-- ============================================
-- RANDOM DATA GENERATION FUNCTIONS
-- ============================================
--- Generates a random license status result based on configured probabilities.
-- @return string, boolean, string, string | Status, validity, color, icon.
function GenerateRandomLicenseResult()
-- Calculate total weight
local totalWeight = 0
for _, result in pairs(Config.RandomLicenseResults) do
totalWeight = totalWeight + result.Chance
end
-- Generate random number based on total weight
local chance = math.random(1, totalWeight)
local runningTotal = 0
-- Check each result against its proportional range
for _, result in pairs(Config.RandomLicenseResults) do
runningTotal = runningTotal + result.Chance
if chance <= runningTotal then
return result.Status, result.IsStatusValid, result.Colour, result.Icon
end
end
-- If no license result is found (shouldn't happen), return nil
return nil, nil, nil, nil
end
--- Generates random criminal flags and markers for NPCs.
-- Uses configured probability to determine if NPC has criminal history.
-- @return table Table containing all flag states and descriptions.
function GenerateRandomFlagsOrMarkers()
local FlagsOrMarkers = {}
local FLAG_OR_MARKER_CHANCE = Config.ChanceToHaveRecords
-- Initial check if person should have any flags or markers at all
if math.random(1, 100) > FLAG_OR_MARKER_CHANCE then
return {
armed_and_dangerous = false,
assault = false,
burglary = false,
drug_related = false,
gang_affiliation = false,
homicide = false,
kidnapping = false,
mental_health_issues = false,
sex_offense = false,
terrorism = false,
theft = false,
traffic_violation = false,
wanted_person = false,
other = false,
active_warrant = false,
}
end
-- Helper function to determine if warrant should be active
local function shouldHaveFlagOrMarker()
return math.random(1, 100) <= FLAG_OR_MARKER_CHANCE
end
-- Generate a suitable flag_description based off the flag or marker which is true
local function GenerateFlagDescription(flagsOrMarkers)
local descriptions = {}
for flag, isTrue in pairs(flagsOrMarkers) do
if isTrue and Config.RandomFlagsOrMarkersDescriptions[flag] then
local descriptionsList = Config.RandomFlagsOrMarkersDescriptions[flag]
local randomIndex = math.random(#descriptionsList)
table.insert(descriptions, descriptionsList[randomIndex])
end
end
if #descriptions == 0 then
return "None"
else
return table.concat(descriptions, ", ")
end
end
FlagsOrMarkers = {
armed_and_dangerous = shouldHaveFlagOrMarker(),
assault = shouldHaveFlagOrMarker(),
burglary = shouldHaveFlagOrMarker(),
drug_related = shouldHaveFlagOrMarker(),
gang_affiliation = shouldHaveFlagOrMarker(),
homicide = shouldHaveFlagOrMarker(),
kidnapping = shouldHaveFlagOrMarker(),
mental_health_issues = shouldHaveFlagOrMarker(),
sex_offense = shouldHaveFlagOrMarker(),
terrorism = shouldHaveFlagOrMarker(),
theft = shouldHaveFlagOrMarker(),
traffic_violation = shouldHaveFlagOrMarker(),
wanted_person = shouldHaveFlagOrMarker(),
other = shouldHaveFlagOrMarker(),
active_warrant = shouldHaveFlagOrMarker(),
}
local flag_description = GenerateFlagDescription(FlagsOrMarkers)
FlagsOrMarkers.flag_description = flag_description
return FlagsOrMarkers
end
--- Selects a random home address from the configured address list.
-- @return string A randomly selected home address.
function GenerateRandomHomeAddress()
local library = Config.RandomHomeAddressList
local randomIndex = math.random(#library)
return library[randomIndex]
end
--- Generates a random date of birth within realistic age ranges (1970-2005).
-- Handles leap years and month-specific day limits.
-- @return string Formatted date string based on Config.DOBFormat.
function GenerateRandomDOB()
-- Define the range of years
local minYear = 1970
local maxYear = 2005
-- Generate a random year within the range
local year = math.random(minYear, maxYear)
-- Generate a random month (1-12)
local month = math.random(1, 12)
-- Generate a random day within the month
local maxDay
if month == 2 then
-- Check for February
if year % 4 == 0 and (year % 100 ~= 0 or year % 400 == 0) then
-- Leap year
maxDay = 29
else
maxDay = 28
end
elseif month == 4 or month == 6 or month == 9 or month == 11 then
-- Months with 30 days
maxDay = 30
else
-- Months with 31 days
maxDay = 31
end
-- Generate a random day
local day = math.random(1, maxDay)
-- Determine the format and return the generated date of birth
if Config.DOBFormat == "en" then
return string.format("%02d-%02d-%04d", day, month, year)
elseif Config.DOBFormat == "us" then
return string.format("%02d-%02d-%04d", month, day, year)
else
return "Unknown"
end
end
--- Selects a random nationality from the configured nationality list.
-- @return string A randomly selected nationality.
function GenerateRandomNationality()
local randomIndex = math.random(1, #Config.RandomNationalities)
local nationality = Config.RandomNationalities[randomIndex]
return nationality
end
--- Generates a random phone number in UK format.
-- Format can be changed by modifying the function logic.
-- @return string A randomly generated phone number.
function GenerateRandomPhoneNumber()
-- UK format
local phoneNumber = "07" .. math.random(10000000, 99999999)
return phoneNumber
-- -- US format
-- local phoneNumber = "1" .. math.random(2000000000, 9999999999)
-- return phoneNumber
-- -- NL format
-- local phoneNumber = "06" .. math.random(10000000, 99999999)
-- return phoneNumber
end
--- Generates complete address information including country, state, city, postal code, and address.
-- Handles error checking for empty configuration tables.
-- @return string, string, string, string, string, string Country, state, city, postal code, address, property type.
function GenerateRandomStateCityPostalCodeRangeAndAddress()
-- Error handling for empty/nil tables
if not Config.RandomStates or #Config.RandomStates == 0 then
DebugPrint("^1ERROR ^7Config.RandomStates is empty or nil")
return nil, nil, nil, nil, nil, nil
end
local country = Config.RandomStates[math.random(#Config.RandomStates)]
if not country or not country.States or #country.States == 0 then
DebugPrint("^1ERROR ^7Country or States table is empty or nil")
return nil, nil, nil, nil, nil, nil
end
local randomState = country.States[math.random(#country.States)]
if not randomState or not randomState.Cities or #randomState.Cities == 0 then
DebugPrint("^1ERROR ^7RandomState or Cities table is empty or nil")
return nil, nil, nil, nil, nil, nil
end
local randomCity = randomState.Cities[math.random(#randomState.Cities)]
if not randomCity or not randomCity.Addresses or #randomCity.Addresses == 0 then
DebugPrint("^1ERROR ^7RandomCity or Addresses table is empty or nil")
return nil, nil, nil, nil, nil, nil
end
-- Generate postal code with proper formatting
local randomPostalCode = math.random(randomCity.PostalCodeRange[1], randomCity.PostalCodeRange[2])
local randomAddress = randomCity.Addresses[math.random(#randomCity.Addresses)]
local randomAddressType = GenerateRandomAdressType()
-- Remove debug prints or make them conditional
if Config.Debug then
print(country.Country or "not found")
print(randomState.State or "not found")
print(randomCity.City or "not found")
print(randomPostalCode or "not found")
print(randomAddress.Address or "not found")
print(randomAddressType or "not found")
end
-- Make sure we're returning the actual country name and address type in the correct order
return country.Country or "USA",
randomState.State or "San Andreas",
randomCity.City or "Los Santos",
tostring(randomPostalCode) or "12345",
randomAddress.Address or "123 Night St",
randomAddressType -- This should be the property type, not the country
end
--- Selects a random property type from the configured property types list.
-- @return string A randomly selected property type (e.g., "Apartment", "House").
function GenerateRandomAdressType()
return Config.RandomPropertyTypes[math.random(#Config.RandomPropertyTypes)]
end
--- Generates a fictitious email address using provided names.
-- @param first_name string The first name for the email.
-- @param last_name string The last name for the email.
-- @return string A generated email address using a random domain.
function GenerateRandomEmail(first_name, last_name)
local randomDomain = Config.FictiveEmailDomains[math.random(#Config.FictiveEmailDomains)]
return first_name .. "." .. last_name .. randomDomain
end
--- Generates a random inventory list for NPCs.
-- Shuffles available items and selects up to 8 random items.
-- @return table Array of inventory items with name and illegal status.
function GenerateListOfRandomInventoryItems()
local inventory = {}
local itemsPool = Config.NPCInventory
-- Generate a random number of items (between 0 and the total number of items).
local numItems = math.random(#itemsPool)
-- Shuffle the items pool.
for i = #itemsPool, 2, -1 do
local j = math.random(i)
itemsPool[i], itemsPool[j] = itemsPool[j], itemsPool[i]
end
-- Add random items to the inventory, limited to a maximum of 8 items.
for i = 1, math.min(#itemsPool, 8) do
local item = itemsPool[i]
table.insert(inventory, {name = item.name, illegal = item.illegal})
end
return inventory
end
-- ============================================
-- DISCORD WEBHOOK INTEGRATION
-- ============================================
--- Server event handler for Discord webhook messages.
RegisterServerEvent(Config.EventPrefix..':sendDiscordEmbedMessage')
AddEventHandler(Config.EventPrefix..':sendDiscordEmbedMessage', function(data)
local src = source
SendDiscordEmbedMessage(src, data)
end)
--- Sends formatted embed messages to Discord webhooks.
-- Supports different webhook types and rich embed formatting.
-- Available as export: exports['night_ers']:SendDiscordEmbedMessage(source, messageData).
-- @param src number The source ID of the player.
-- @param data table Table containing embed data (title, description, color, etc.).
function SendDiscordEmbedMessage(src, data)
if Config.Enable_Discord_Webhooks then
local webhookURL = "https://discord.com/api/webhooks/964210045220958251/2qzKEBdUceFxJ1Wt3OhmL6WbqP9AUJqrJjbtd0U31MFV8l9uOODvn7mfmsm5I3wxzE1d"
local webhooKType = data.discordwebhookurltype
if webhooKType == nil then webhooKType = '' end
if webhooKType == 'dispatch' then
webhookURL = "https://discord.com/api/webhooks/964210045220958251/2qzKEBdUceFxJ1Wt3OhmL6WbqP9AUJqrJjbtd0U31MFV8l9uOODvn7mfmsm5I3wxzE1d"
else
webhookURL = "https://discord.com/api/webhooks/964210045220958251/2qzKEBdUceFxJ1Wt3OhmL6WbqP9AUJqrJjbtd0U31MFV8l9uOODvn7mfmsm5I3wxzE1d"
end
local discordId = getDiscordIdFromSource(src) or 0
local embed = {
{
["title"] = "**"..(data.title or "DISPATCH MESSAGE SYSTEM").."**" ,
["description"] = data.description or "",
["color"] = data.color or 11876095,
["author"] = {
["name"] = data.authorname or "",
-- ["icon_url"] = data.authoravatarurl or ""
},
["fields"] = {
{
["name"] = data.sender or "",
["value"] = "<@"..(discordId or "Unknown")..">",
["inline"] = true
},
{
["name"] = "Get the Emergency Response Simulator",
["value"] = "[Nights Software](https://store.nights-software.com)",
["inline"] = false
},
{
["name"] = "Go for the full experience and expand your options",
["value"] = "[London Studios](https://londonstudios.net)",
["inline"] = false
}
},
["footer"] = {
["text"] = data.footer or "".." "..os.date("%Y").." | "..os.date("%d-%m-%Y at %H:%M:%S"),
["icon_url"] = data.footericon or "https://assets.ea-rp.com/img/ERS_Logo.png"
},
["thumbnail"] = {
["url"] = data.thumbnail or "https://assets.ea-rp.com/img/ERS_Logo_Sq.png",
},
["image"] = {
["url"] = data.image or "" -- No url is no image, which is fine as well
},
["timestamp"] = os.date("!%Y-%m-%dT%H:%M:%SZ") -- UTC timestamp
}
}
PerformHttpRequest(webhookURL, function(err, text, headers) end, 'POST', json.encode({data.systemname or "NS - ERS System", embeds = embed}), { ['Content-Type'] = 'application/json' })
end
end
exports('SendDiscordEmbedMessage', SendDiscordEmbedMessage)
-- ============================================
-- DEBUG UTILITIES
-- ============================================
--- Conditional debug print function.
-- Only prints messages when Config.Debug is enabled.
-- @param msg string The debug message to print.
function DebugPrint(msg)
if Config.Debug then
if msg ~= nil then
print("["..GetCurrentResourceName().."] "..msg)
end
end
end