828 lines
33 KiB
Lua
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 |