0.0.1
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
if Config.CustomInventory ~= "ox" then return end
|
||||
|
||||
MySQL.ready(function()
|
||||
TriggerEvent("__cfx_export_ox_inventory_Items", function(ref)
|
||||
if ref then
|
||||
ESX.Items = ref()
|
||||
end
|
||||
end)
|
||||
|
||||
AddEventHandler("ox_inventory:itemList", function(items)
|
||||
ESX.Items = items
|
||||
end)
|
||||
end)
|
||||
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
ESX.GetItemLabel = function(item)
|
||||
item = exports.ox_inventory:Items(item)
|
||||
if item then
|
||||
return item.label
|
||||
end
|
||||
end
|
||||
|
||||
function setPlayerInventory(playerId, xPlayer, inventory, isNew)
|
||||
exports.ox_inventory:setPlayerInventory(xPlayer, inventory)
|
||||
if isNew then
|
||||
local shared = json.decode(GetConvar("inventory:accounts", '["money"]'))
|
||||
|
||||
for i = 1, #shared do
|
||||
local name = shared[i]
|
||||
local account = Config.StartingAccountMoney[name]
|
||||
if account then
|
||||
exports.ox_inventory:AddItem(playerId, name, account)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,219 @@
|
||||
local Inventory
|
||||
|
||||
if Config.CustomInventory ~= "ox" then return end
|
||||
|
||||
AddEventHandler("ox_inventory:loadInventory", function(module)
|
||||
Inventory = module
|
||||
end)
|
||||
|
||||
Core.PlayerFunctionOverrides.OxInventory = {
|
||||
getInventory = function(self)
|
||||
return function(minimal)
|
||||
if not minimal then
|
||||
return self.inventory
|
||||
end
|
||||
|
||||
local minimalInventory = {}
|
||||
|
||||
for k, v in pairs(self.inventory) do
|
||||
if v.count and v.count > 0 then
|
||||
local metadata = v.metadata
|
||||
|
||||
if v.metadata and next(v.metadata) == nil then
|
||||
metadata = nil
|
||||
end
|
||||
|
||||
minimalInventory[#minimalInventory + 1] = {
|
||||
name = v.name,
|
||||
count = v.count,
|
||||
slot = k,
|
||||
metadata = metadata,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return minimalInventory
|
||||
end
|
||||
end,
|
||||
|
||||
getLoadout = function()
|
||||
return function()
|
||||
return {}
|
||||
end
|
||||
end,
|
||||
|
||||
setAccountMoney = function(self)
|
||||
return function(accountName, money, reason)
|
||||
reason = reason or "unknown"
|
||||
if money < 0 then return end
|
||||
local account = self.getAccount(accountName)
|
||||
|
||||
if not account then return end
|
||||
|
||||
money = account.round and ESX.Math.Round(money) or money
|
||||
self.accounts[account.index].money = money
|
||||
|
||||
self.triggerEvent("esx:setAccountMoney", account)
|
||||
TriggerEvent("esx:setAccountMoney", self.source, accountName, money, reason)
|
||||
if Inventory.accounts[accountName] then
|
||||
Inventory.SetItem(self.source, accountName, money)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
addAccountMoney = function(self)
|
||||
return function(accountName, money, reason)
|
||||
reason = reason or "unknown"
|
||||
if money < 1 then return end
|
||||
|
||||
local account = self.getAccount(accountName)
|
||||
if not account then return end
|
||||
|
||||
money = account.round and ESX.Math.Round(money) or money
|
||||
self.accounts[account.index].money = self.accounts[account.index].money + money
|
||||
self.triggerEvent("esx:setAccountMoney", account)
|
||||
TriggerEvent("esx:addAccountMoney", self.source, accountName, money, reason)
|
||||
if Inventory.accounts[accountName] then
|
||||
Inventory.AddItem(self.source, accountName, money)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
removeAccountMoney = function(self)
|
||||
return function(accountName, money, reason)
|
||||
reason = reason or "unknown"
|
||||
if money < 1 then return end
|
||||
|
||||
local account = self.getAccount(accountName)
|
||||
if not account then return end
|
||||
|
||||
money = account.round and ESX.Math.Round(money) or money
|
||||
self.accounts[account.index].money = self.accounts[account.index].money - money
|
||||
self.triggerEvent("esx:setAccountMoney", account)
|
||||
TriggerEvent("esx:removeAccountMoney", self.source, accountName, money, reason)
|
||||
if Inventory.accounts[accountName] then
|
||||
Inventory.RemoveItem(self.source, accountName, money)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
getInventoryItem = function(self)
|
||||
return function(name, metadata)
|
||||
return Inventory.GetItem(self.source, name, metadata)
|
||||
end
|
||||
end,
|
||||
|
||||
addInventoryItem = function(self)
|
||||
return function(name, count, metadata, slot)
|
||||
return Inventory.AddItem(self.source, name, count or 1, metadata, slot)
|
||||
end
|
||||
end,
|
||||
|
||||
removeInventoryItem = function(self)
|
||||
return function(name, count, metadata, slot)
|
||||
return Inventory.RemoveItem(self.source, name, count or 1, metadata, slot)
|
||||
end
|
||||
end,
|
||||
|
||||
setInventoryItem = function(self)
|
||||
return function(name, count, metadata)
|
||||
return Inventory.SetItem(self.source, name, count, metadata)
|
||||
end
|
||||
end,
|
||||
|
||||
canCarryItem = function(self)
|
||||
return function(name, count, metadata)
|
||||
return Inventory.CanCarryItem(self.source, name, count, metadata)
|
||||
end
|
||||
end,
|
||||
|
||||
canSwapItem = function(self)
|
||||
return function(firstItem, firstItemCount, testItem, testItemCount)
|
||||
return Inventory.CanSwapItem(self.source, firstItem, firstItemCount, testItem, testItemCount)
|
||||
end
|
||||
end,
|
||||
|
||||
setMaxWeight = function(self)
|
||||
return function(newWeight)
|
||||
self.maxWeight = newWeight
|
||||
self.triggerEvent("esx:setMaxWeight", self.maxWeight)
|
||||
return Inventory.SetMaxWeight(self.source, newWeight)
|
||||
end
|
||||
end,
|
||||
|
||||
addWeapon = function()
|
||||
return function() end
|
||||
end,
|
||||
|
||||
addWeaponComponent = function()
|
||||
return function() end
|
||||
end,
|
||||
|
||||
addWeaponAmmo = function()
|
||||
return function() end
|
||||
end,
|
||||
|
||||
updateWeaponAmmo = function()
|
||||
return function() end
|
||||
end,
|
||||
|
||||
setWeaponTint = function()
|
||||
return function() end
|
||||
end,
|
||||
|
||||
getWeaponTint = function()
|
||||
return function() end
|
||||
end,
|
||||
|
||||
removeWeapon = function()
|
||||
return function() end
|
||||
end,
|
||||
|
||||
removeWeaponComponent = function()
|
||||
return function() end
|
||||
end,
|
||||
|
||||
removeWeaponAmmo = function()
|
||||
return function() end
|
||||
end,
|
||||
|
||||
hasWeaponComponent = function()
|
||||
return function()
|
||||
return false
|
||||
end
|
||||
end,
|
||||
|
||||
hasWeapon = function()
|
||||
return function()
|
||||
return false
|
||||
end
|
||||
end,
|
||||
|
||||
hasItem = function(self)
|
||||
return function(name, metadata)
|
||||
return Inventory.GetItem(self.source, name, metadata)
|
||||
end
|
||||
end,
|
||||
|
||||
getWeapon = function()
|
||||
return function() end
|
||||
end,
|
||||
|
||||
syncInventory = function(self)
|
||||
return function(weight, maxWeight, items, money)
|
||||
self.weight, self.maxWeight = weight, maxWeight
|
||||
self.inventory = items
|
||||
|
||||
if not money then return end
|
||||
for accountName, amount in pairs(money) do
|
||||
local account = self.getAccount(accountName)
|
||||
|
||||
if account and ESX.Math.Round(account.money) ~= amount then
|
||||
account.money = amount
|
||||
self.triggerEvent("esx:setAccountMoney", account)
|
||||
TriggerEvent("esx:setAccountMoney", self.source, accountName, amount, "Sync account with item")
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
}
|
||||
969
resources/[core]/es_extended/server/classes/player.lua
Normal file
969
resources/[core]/es_extended/server/classes/player.lua
Normal file
@@ -0,0 +1,969 @@
|
||||
---@class ESXAccount
|
||||
---@field name string # Account name (e.g., "bank", "money").
|
||||
---@field money number # Current balance in this account.
|
||||
---@field label string # Human-readable label for the account.
|
||||
---@field round boolean # Whether amounts are rounded for display.
|
||||
---@field index number # Index of the account in the player's accounts list.
|
||||
|
||||
---@class ESXItem
|
||||
---@field name string # Item identifier (internal name).
|
||||
---@field label string # Display name of the item.
|
||||
---@field weight number # Weight of a single unit of the item.
|
||||
---@field usable boolean # Whether the item can be used.
|
||||
---@field rare boolean # Whether the item is rare.
|
||||
---@field canRemove boolean # Whether the item can be removed from inventory.
|
||||
|
||||
---@class ESXInventoryItem:ESXItem
|
||||
---@field count number # Number of this item in the player's inventory.
|
||||
|
||||
---@class ESXJob
|
||||
---@field id number # Job ID.
|
||||
---@field name string # Job internal name.
|
||||
---@field label string # Job display label.
|
||||
---@field grade number # Current grade/rank number.
|
||||
---@field grade_name string # Name of the current grade.
|
||||
---@field grade_label string # Label of the current grade.
|
||||
---@field grade_salary number # Salary for the current grade.
|
||||
---@field skin_male table # Skin configuration for male characters.
|
||||
---@field skin_female table # Skin configuration for female characters.
|
||||
---@field onDuty boolean? # Whether the player is currently on duty.
|
||||
|
||||
---@class ESXWeapon
|
||||
---@field name string # Weapon identifier (internal name).
|
||||
---@field label string # Weapon display name.
|
||||
|
||||
---@class ESXInventoryWeapon:ESXWeapon
|
||||
---@field ammo number # Amount of ammo in the weapon.
|
||||
---@field components string[] # List of components attached to the weapon.
|
||||
---@field tintIndex number # Current weapon tint index.
|
||||
|
||||
---@class ESXWeaponComponent
|
||||
---@field name string # Component identifier (internal name).
|
||||
---@field label string # Component display name.
|
||||
---@field hash string|number # Component hash or identifier.
|
||||
|
||||
---@class StaticPlayer
|
||||
---@field src number # Player's server ID.
|
||||
--- Money Functions
|
||||
---@field setMoney fun(money: number) # Set player's cash balance.
|
||||
---@field getMoney fun(): number # Get player's current cash balance.
|
||||
---@field addMoney fun(money: number, reason: string) # Add money to the player's cash balance.
|
||||
---@field removeMoney fun(money: number, reason: string) # Remove money from the player's cash balance.
|
||||
---@field setAccountMoney fun(accountName: string, money: number, reason?: string) # Set specific account balance.
|
||||
---@field addAccountMoney fun(accountName: string, money: number, reason?: string) # Add money to an account.
|
||||
---@field removeAccountMoney fun(accountName: string, money: number, reason?: string) # Remove money from an account.
|
||||
---@field getAccount fun(account: string): ESXAccount? # Get account data by name.
|
||||
---@field getAccounts fun(minimal?: boolean): ESXAccount[]|table<string,number> # Get all accounts, optionally minimal.
|
||||
--- Inventory Functions
|
||||
---@field getInventory fun(minimal?: boolean): ESXInventoryItem[]|table<string,number> # Get inventory, optionally minimal.
|
||||
---@field getInventoryItem fun(itemName: string): ESXInventoryItem? # Get a specific item from inventory.
|
||||
---@field addInventoryItem fun(itemName: string, count: number) # Add items to inventory.
|
||||
---@field removeInventoryItem fun(itemName: string, count: number) # Remove items from inventory.
|
||||
---@field setInventoryItem fun(itemName: string, count: number) # Set item count in inventory.
|
||||
---@field getWeight fun(): number # Get current carried weight.
|
||||
---@field getMaxWeight fun(): number # Get maximum carry weight.
|
||||
---@field setMaxWeight fun(newWeight: number) # Set maximum carry weight.
|
||||
---@field canCarryItem fun(itemName: string, count: number): boolean # Check if player can carry more of an item.
|
||||
---@field canSwapItem fun(firstItem: string, firstItemCount: number, testItem: string, testItemCount: number): boolean # Check if items can be swapped.
|
||||
---@field hasItem fun(item: string): ESXInventoryItem|false, number? # Check if player has an item.
|
||||
---@field getLoadout fun(minimal?: boolean): ESXInventoryWeapon[]|table<string, {ammo:number, tintIndex?:number, components?:string[]}> # Get player's weapon loadout.
|
||||
--- Job Functions
|
||||
---@field getJob fun(): ESXJob # Get player's current job.
|
||||
---@field setJob fun(newJob: string, grade: string, onDuty?: boolean) # Set player's job and grade.
|
||||
---@field setGroup fun(newGroup: string) # Set player's permission group.
|
||||
---@field getGroup fun(): string # Get player's permission group.
|
||||
--- Weapon Functions
|
||||
---@field addWeapon fun(weaponName: string, ammo: number) # Give player a weapon.
|
||||
---@field removeWeapon fun(weaponName: string) # Remove weapon from player.
|
||||
---@field hasWeapon fun(weaponName: string): boolean # Check if player has a weapon.
|
||||
---@field getWeapon fun(weaponName: string): number?, table? # Get weapon ammo & components.
|
||||
---@field addWeaponAmmo fun(weaponName: string, ammoCount: number) # Add ammo to a weapon.
|
||||
---@field removeWeaponAmmo fun(weaponName: string, ammoCount: number) # Remove ammo from a weapon.
|
||||
---@field updateWeaponAmmo fun(weaponName: string, ammoCount: number) # Update ammo count for a weapon.
|
||||
---@field addWeaponComponent fun(weaponName: string, weaponComponent: string) # Add component to weapon.
|
||||
---@field removeWeaponComponent fun(weaponName: string, weaponComponent: string) # Remove component from weapon.
|
||||
---@field hasWeaponComponent fun(weaponName: string, weaponComponent: string): boolean # Check if weapon has component.
|
||||
---@field setWeaponTint fun(weaponName: string, weaponTintIndex: number) # Set weapon tint.
|
||||
---@field getWeaponTint fun(weaponName: string): number # Get weapon tint.
|
||||
--- Player State Functions
|
||||
---@field getIdentifier fun(): string # Get player's unique identifier.
|
||||
---@field getSSN fun(): string # Get player's social security number.
|
||||
---@field getSource fun(): number # Get player source/server ID.
|
||||
---@field getPlayerId fun(): number # Alias for getSource.
|
||||
---@field getName fun(): string # Get player's name.
|
||||
---@field setName fun(newName: string) # Set player's name.
|
||||
---@field setCoords fun(coordinates: vector4|vector3|table) # Teleport player to coordinates.
|
||||
---@field getCoords fun(vector?: boolean, heading?: boolean): vector3|vector4|table # Get player's coordinates.
|
||||
---@field isAdmin fun(): boolean # Check if player is admin.
|
||||
---@field kick fun(reason: string) # Kick player from server.
|
||||
---@field getPlayTime fun(): number # Get total playtime in seconds.
|
||||
---@field set fun(k: string, v: any) # Set custom variable.
|
||||
---@field get fun(k: string): any # Get custom variable.
|
||||
--- Metadata Functions
|
||||
---@field getMeta fun(index?: string, subIndex?: string|table): any # Get metadata value(s).
|
||||
---@field setMeta fun(index: string, value: any, subValue?: any) # Set metadata value(s).
|
||||
---@field clearMeta fun(index: string, subValues?: string|table) # Clear metadata value(s).
|
||||
--- Notification Functions
|
||||
---@field showNotification fun(msg: string, notifyType?: string, length?: number, title?: string, position?: string) # Show a simple notification.
|
||||
---@field showAdvancedNotification fun(sender: string, subject: string, msg: string, textureDict: string, iconType: string, flash: boolean, saveToBrief: boolean, hudColorIndex: number) # Show advanced notification.
|
||||
---@field showHelpNotification fun(msg: string, thisFrame?: boolean, beep?: boolean, duration?: number) # Show help notification.
|
||||
--- Misc Functions
|
||||
---@field togglePaycheck fun(toggle: boolean) # Enable/disable paycheck.
|
||||
---@field isPaycheckEnabled fun(): boolean # Check if paycheck is enabled.
|
||||
---@field executeCommand fun(command: string) # Execute a server command.
|
||||
---@field triggerEvent fun(eventName: string, ...) # Trigger client event for this player.
|
||||
|
||||
|
||||
---@class xPlayer:StaticPlayer
|
||||
--- Properties
|
||||
---@field accounts ESXAccount[] # Array of the player's accounts.
|
||||
---@field coords table # Player's coordinates {x, y, z, heading}.
|
||||
---@field group string # Player permission group.
|
||||
---@field identifier string # Unique identifier (usually Steam or license).
|
||||
---@field license string # Player license string.
|
||||
---@field inventory ESXInventoryItem[] # Player's inventory items.
|
||||
---@field job ESXJob # Player's current job.
|
||||
---@field loadout ESXInventoryWeapon[] # Player's current weapons.
|
||||
---@field name string # Player's display name.
|
||||
---@field playerId number # Player's ID (server ID).
|
||||
---@field source number # Player's source (alias for playerId).
|
||||
---@field variables table # Custom player variables.
|
||||
---@field weight number # Current carried weight.
|
||||
---@field maxWeight number # Maximum carry weight.
|
||||
---@field metadata table # Custom metadata table.
|
||||
---@field lastPlaytime number # Last recorded playtime in seconds.
|
||||
---@field paycheckEnabled boolean # Whether paycheck is enabled.
|
||||
---@field admin boolean # Whether the player is an admin.
|
||||
|
||||
---@param playerId number
|
||||
---@param identifier string
|
||||
---@param ssn string
|
||||
---@param group string
|
||||
---@param accounts ESXAccount[]
|
||||
---@param inventory table
|
||||
---@param weight number
|
||||
---@param job ESXJob
|
||||
---@param loadout ESXInventoryWeapon[]
|
||||
---@param name string
|
||||
---@param coords vector4|{x: number, y: number, z: number, heading: number}
|
||||
---@param metadata table
|
||||
---@return xPlayer
|
||||
function CreateExtendedPlayer(playerId, identifier, ssn, group, accounts, inventory, weight, job, loadout, name, coords, metadata)
|
||||
---@diagnostic disable-next-line: missing-fields
|
||||
local self = {} ---@type xPlayer
|
||||
|
||||
self.accounts = accounts
|
||||
self.coords = coords
|
||||
self.group = group
|
||||
self.identifier = identifier
|
||||
self.ssn = ssn
|
||||
self.inventory = inventory
|
||||
self.job = job
|
||||
self.loadout = loadout
|
||||
self.name = name
|
||||
self.playerId = playerId
|
||||
self.source = playerId
|
||||
self.variables = {}
|
||||
self.weight = weight
|
||||
self.maxWeight = Config.MaxWeight
|
||||
self.metadata = metadata
|
||||
self.lastPlaytime = self.metadata.lastPlaytime or 0
|
||||
self.paycheckEnabled = true
|
||||
self.admin = Core.IsPlayerAdmin(playerId)
|
||||
if Config.Multichar then
|
||||
local startIndex = identifier:find(":", 1)
|
||||
if startIndex then
|
||||
self.license = ("%s%s"):format(Config.Identifier, identifier:sub(startIndex, identifier:len()))
|
||||
end
|
||||
else
|
||||
self.license = ("%s:%s"):format(Config.Identifier, identifier)
|
||||
end
|
||||
|
||||
if type(self.metadata.jobDuty) ~= "boolean" then
|
||||
self.metadata.jobDuty = self.job.name ~= "unemployed" and Config.DefaultJobDuty or false
|
||||
end
|
||||
job.onDuty = self.metadata.jobDuty
|
||||
|
||||
ExecuteCommand(("add_principal identifier.%s group.%s"):format(self.license, self.group))
|
||||
|
||||
local stateBag = Player(self.source).state
|
||||
stateBag:set("identifier", self.identifier, false)
|
||||
stateBag:set("license", self.license, false)
|
||||
stateBag:set("job", self.job, true)
|
||||
stateBag:set("group", self.group, true)
|
||||
stateBag:set("name", self.name, true)
|
||||
|
||||
function self.triggerEvent(eventName, ...)
|
||||
assert(type(eventName) == "string", "eventName should be string!")
|
||||
TriggerClientEvent(eventName, self.source, ...)
|
||||
end
|
||||
|
||||
function self.togglePaycheck(toggle)
|
||||
self.paycheckEnabled = toggle
|
||||
end
|
||||
|
||||
function self.isPaycheckEnabled()
|
||||
return self.paycheckEnabled
|
||||
end
|
||||
|
||||
function self.isAdmin()
|
||||
return Core.IsPlayerAdmin(self.source)
|
||||
end
|
||||
|
||||
function self.setCoords(coordinates)
|
||||
local ped <const> = GetPlayerPed(self.source)
|
||||
|
||||
SetEntityCoords(ped, coordinates.x, coordinates.y, coordinates.z, false, false, false, false)
|
||||
SetEntityHeading(ped, coordinates.w or coordinates.heading or 0.0)
|
||||
end
|
||||
|
||||
function self.getCoords(vector, heading)
|
||||
local ped <const> = GetPlayerPed(self.source)
|
||||
local entityCoords <const> = GetEntityCoords(ped)
|
||||
local entityHeading <const> = GetEntityHeading(ped)
|
||||
|
||||
local coordinates = { x = entityCoords.x, y = entityCoords.y, z = entityCoords.z }
|
||||
|
||||
if vector then
|
||||
coordinates = (heading and vector4(entityCoords.x, entityCoords.y, entityCoords.z, entityHeading) or entityCoords)
|
||||
else
|
||||
if heading then
|
||||
coordinates.heading = entityHeading
|
||||
end
|
||||
end
|
||||
|
||||
return coordinates
|
||||
end
|
||||
|
||||
function self.kick(reason)
|
||||
DropPlayer(self.source --[[@as string]], reason)
|
||||
end
|
||||
|
||||
function self.getPlayTime()
|
||||
-- luacheck: ignore
|
||||
return self.lastPlaytime + GetPlayerTimeOnline(self.source --[[@as string]])
|
||||
end
|
||||
|
||||
function self.setMoney(money)
|
||||
assert(type(money) == "number", "money should be number!")
|
||||
money = ESX.Math.Round(money)
|
||||
self.setAccountMoney("money", money)
|
||||
end
|
||||
|
||||
function self.getMoney()
|
||||
return self.getAccount("money").money
|
||||
end
|
||||
|
||||
function self.addMoney(money, reason)
|
||||
money = ESX.Math.Round(money)
|
||||
self.addAccountMoney("money", money, reason)
|
||||
end
|
||||
|
||||
function self.removeMoney(money, reason)
|
||||
money = ESX.Math.Round(money)
|
||||
self.removeAccountMoney("money", money, reason)
|
||||
end
|
||||
|
||||
function self.getIdentifier()
|
||||
return self.identifier
|
||||
end
|
||||
|
||||
function self.getSSN()
|
||||
return self.ssn
|
||||
end
|
||||
|
||||
function self.setGroup(newGroup)
|
||||
local lastGroup = self.group
|
||||
|
||||
ExecuteCommand(("remove_principal identifier.%s group.%s"):format(self.license, self.group))
|
||||
|
||||
self.group = newGroup
|
||||
|
||||
TriggerEvent("esx:setGroup", self.source, self.group, lastGroup)
|
||||
self.triggerEvent("esx:setGroup", self.group, lastGroup)
|
||||
Player(self.source).state:set("group", self.group, true)
|
||||
|
||||
ExecuteCommand(("add_principal identifier.%s group.%s"):format(self.license, self.group))
|
||||
end
|
||||
|
||||
function self.getGroup()
|
||||
return self.group
|
||||
end
|
||||
|
||||
function self.set(k, v)
|
||||
self.variables[k] = v
|
||||
|
||||
self.triggerEvent('esx:updatePlayerData', 'variables', self.variables)
|
||||
end
|
||||
|
||||
function self.get(k)
|
||||
return self.variables[k]
|
||||
end
|
||||
|
||||
function self.getAccounts(minimal)
|
||||
if not minimal then
|
||||
return self.accounts
|
||||
end
|
||||
|
||||
local minimalAccounts = {}
|
||||
|
||||
for i = 1, #self.accounts do
|
||||
minimalAccounts[self.accounts[i].name] = self.accounts[i].money
|
||||
end
|
||||
|
||||
return minimalAccounts
|
||||
end
|
||||
|
||||
function self.getAccount(account)
|
||||
account = string.lower(account)
|
||||
for i = 1, #self.accounts do
|
||||
local accountName = string.lower(self.accounts[i].name)
|
||||
if accountName == account then
|
||||
return self.accounts[i]
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function self.getInventory(minimal)
|
||||
if minimal then
|
||||
local minimalInventory = {}
|
||||
|
||||
for _, v in ipairs(self.inventory) do
|
||||
if v.count > 0 then
|
||||
minimalInventory[v.name] = v.count
|
||||
end
|
||||
end
|
||||
|
||||
return minimalInventory
|
||||
end
|
||||
|
||||
return self.inventory
|
||||
end
|
||||
|
||||
function self.getJob()
|
||||
return self.job
|
||||
end
|
||||
|
||||
function self.getLoadout(minimal)
|
||||
if not minimal then
|
||||
return self.loadout
|
||||
end
|
||||
local minimalLoadout = {}
|
||||
|
||||
for _, v in ipairs(self.loadout) do
|
||||
minimalLoadout[v.name] = { ammo = v.ammo }
|
||||
if v.tintIndex > 0 then
|
||||
minimalLoadout[v.name].tintIndex = v.tintIndex
|
||||
end
|
||||
|
||||
if #v.components > 0 then
|
||||
local components = {}
|
||||
|
||||
for _, component in ipairs(v.components) do
|
||||
if component ~= "clip_default" then
|
||||
components[#components + 1] = component
|
||||
end
|
||||
end
|
||||
|
||||
if #components > 0 then
|
||||
minimalLoadout[v.name].components = components
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return minimalLoadout
|
||||
end
|
||||
|
||||
function self.getName()
|
||||
return self.name
|
||||
end
|
||||
|
||||
function self.setName(newName)
|
||||
self.name = newName
|
||||
Player(self.source).state:set("name", self.name, true)
|
||||
end
|
||||
|
||||
function self.setAccountMoney(accountName, money, reason)
|
||||
reason = reason or "unknown"
|
||||
if not tonumber(money) then
|
||||
error(("Tried To Set Account ^5%s^1 For Player ^5%s^1 To An Invalid Number -> ^5%s^1"):format(accountName, self.playerId, money))
|
||||
return
|
||||
end
|
||||
if money >= 0 then
|
||||
local account = self.getAccount(accountName)
|
||||
|
||||
if account then
|
||||
money = account.round and ESX.Math.Round(money) or money
|
||||
self.accounts[account.index].money = money
|
||||
|
||||
self.triggerEvent("esx:setAccountMoney", account)
|
||||
TriggerEvent("esx:setAccountMoney", self.source, accountName, money, reason)
|
||||
else
|
||||
error(("Tried To Set Invalid Account ^5%s^1 For Player ^5%s^1!"):format(accountName, self.playerId))
|
||||
end
|
||||
else
|
||||
error(("Tried To Set Account ^5%s^1 For Player ^5%s^1 To An Invalid Number -> ^5%s^1"):format(accountName, self.playerId, money))
|
||||
end
|
||||
end
|
||||
|
||||
function self.addAccountMoney(accountName, money, reason)
|
||||
reason = reason or "Unknown"
|
||||
if not tonumber(money) then
|
||||
error(("Tried To Set Account ^5%s^1 For Player ^5%s^1 To An Invalid Number -> ^5%s^1"):format(accountName, self.playerId, money))
|
||||
return
|
||||
end
|
||||
if money > 0 then
|
||||
local account = self.getAccount(accountName)
|
||||
if account then
|
||||
money = account.round and ESX.Math.Round(money) or money
|
||||
self.accounts[account.index].money = self.accounts[account.index].money + money
|
||||
|
||||
self.triggerEvent("esx:setAccountMoney", account)
|
||||
TriggerEvent("esx:addAccountMoney", self.source, accountName, money, reason)
|
||||
else
|
||||
error(("Tried To Set Add To Invalid Account ^5%s^1 For Player ^5%s^1!"):format(accountName, self.playerId))
|
||||
end
|
||||
else
|
||||
error(("Tried To Set Account ^5%s^1 For Player ^5%s^1 To An Invalid Number -> ^5%s^1"):format(accountName, self.playerId, money))
|
||||
end
|
||||
end
|
||||
|
||||
function self.removeAccountMoney(accountName, money, reason)
|
||||
reason = reason or "Unknown"
|
||||
if not tonumber(money) then
|
||||
error(("Tried To Set Account ^5%s^1 For Player ^5%s^1 To An Invalid Number -> ^5%s^1"):format(accountName, self.playerId, money))
|
||||
return
|
||||
end
|
||||
if money > 0 then
|
||||
local account = self.getAccount(accountName)
|
||||
|
||||
if account then
|
||||
money = account.round and ESX.Math.Round(money) or money
|
||||
if self.accounts[account.index].money - money > self.accounts[account.index].money then
|
||||
error(("Tried To Underflow Account ^5%s^1 For Player ^5%s^1!"):format(accountName, self.playerId))
|
||||
return
|
||||
end
|
||||
self.accounts[account.index].money = self.accounts[account.index].money - money
|
||||
|
||||
self.triggerEvent("esx:setAccountMoney", account)
|
||||
TriggerEvent("esx:removeAccountMoney", self.source, accountName, money, reason)
|
||||
else
|
||||
error(("Tried To Set Add To Invalid Account ^5%s^1 For Player ^5%s^1!"):format(accountName, self.playerId))
|
||||
end
|
||||
else
|
||||
error(("Tried To Set Account ^5%s^1 For Player ^5%s^1 To An Invalid Number -> ^5%s^1"):format(accountName, self.playerId, money))
|
||||
end
|
||||
end
|
||||
|
||||
function self.getInventoryItem(itemName)
|
||||
for _, v in ipairs(self.inventory) do
|
||||
if v.name == itemName then
|
||||
return v
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function self.addInventoryItem(itemName, count)
|
||||
local item = self.getInventoryItem(itemName)
|
||||
|
||||
if item then
|
||||
count = ESX.Math.Round(count)
|
||||
item.count = item.count + count
|
||||
self.weight = self.weight + (item.weight * count)
|
||||
|
||||
TriggerEvent("esx:onAddInventoryItem", self.source, item.name, item.count)
|
||||
self.triggerEvent("esx:addInventoryItem", item.name, item.count)
|
||||
end
|
||||
end
|
||||
|
||||
function self.removeInventoryItem(itemName, count)
|
||||
local item = self.getInventoryItem(itemName)
|
||||
|
||||
if item then
|
||||
count = ESX.Math.Round(count)
|
||||
if count > 0 then
|
||||
local newCount = item.count - count
|
||||
|
||||
if newCount >= 0 then
|
||||
item.count = newCount
|
||||
self.weight = self.weight - (item.weight * count)
|
||||
|
||||
TriggerEvent("esx:onRemoveInventoryItem", self.source, item.name, item.count)
|
||||
self.triggerEvent("esx:removeInventoryItem", item.name, item.count)
|
||||
end
|
||||
else
|
||||
error(("Player ID:^5%s Tried remove a Invalid count -> %s of %s"):format(self.playerId, count, itemName))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function self.setInventoryItem(itemName, count)
|
||||
local item = self.getInventoryItem(itemName)
|
||||
|
||||
if item and count >= 0 then
|
||||
count = ESX.Math.Round(count)
|
||||
|
||||
if count > item.count then
|
||||
self.addInventoryItem(item.name, count - item.count)
|
||||
else
|
||||
self.removeInventoryItem(item.name, item.count - count)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function self.getWeight()
|
||||
return self.weight
|
||||
end
|
||||
|
||||
function self.getSource()
|
||||
return self.source
|
||||
end
|
||||
self.getPlayerId = self.getSource
|
||||
|
||||
function self.getMaxWeight()
|
||||
return self.maxWeight
|
||||
end
|
||||
|
||||
function self.canCarryItem(itemName, count)
|
||||
if ESX.Items[itemName] then
|
||||
local currentWeight, itemWeight = self.weight, ESX.Items[itemName].weight
|
||||
local newWeight = currentWeight + (itemWeight * count)
|
||||
|
||||
return newWeight <= self.maxWeight
|
||||
else
|
||||
print(('[^3WARNING^7] Item ^5"%s"^7 was used but does not exist!'):format(itemName))
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function self.canSwapItem(firstItem, firstItemCount, testItem, testItemCount)
|
||||
local firstItemObject = self.getInventoryItem(firstItem)
|
||||
if not firstItemObject then
|
||||
return false
|
||||
end
|
||||
local testItemObject = self.getInventoryItem(testItem)
|
||||
if not testItemObject then
|
||||
return false
|
||||
end
|
||||
|
||||
if firstItemObject.count >= firstItemCount then
|
||||
local weightWithoutFirstItem = ESX.Math.Round(self.weight - (firstItemObject.weight * firstItemCount))
|
||||
local weightWithTestItem = ESX.Math.Round(weightWithoutFirstItem + (testItemObject.weight * testItemCount))
|
||||
|
||||
return weightWithTestItem <= self.maxWeight
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function self.setMaxWeight(newWeight)
|
||||
self.maxWeight = newWeight
|
||||
self.triggerEvent("esx:setMaxWeight", self.maxWeight)
|
||||
end
|
||||
|
||||
function self.setJob(newJob, grade, onDuty)
|
||||
grade = tostring(grade)
|
||||
local lastJob = self.job
|
||||
|
||||
if not ESX.DoesJobExist(newJob, grade) then
|
||||
return print(("[ESX] [^3WARNING^7] Ignoring invalid ^5.setJob()^7 usage for ID: ^5%s^7, Job: ^5%s^7"):format(self.source, newJob))
|
||||
end
|
||||
|
||||
if newJob == "unemployed" then
|
||||
onDuty = false
|
||||
end
|
||||
|
||||
if type(onDuty) ~= "boolean" then
|
||||
onDuty = Config.DefaultJobDuty
|
||||
end
|
||||
|
||||
local jobObject, gradeObject = ESX.Jobs[newJob], ESX.Jobs[newJob].grades[grade]
|
||||
|
||||
self.job = {
|
||||
id = jobObject.id,
|
||||
name = jobObject.name,
|
||||
label = jobObject.label,
|
||||
type = jobObject.type,
|
||||
onDuty = onDuty,
|
||||
|
||||
grade = tonumber(grade) or 0,
|
||||
grade_name = gradeObject.name,
|
||||
grade_label = gradeObject.label,
|
||||
grade_salary = gradeObject.salary,
|
||||
|
||||
skin_male = gradeObject.skin_male and json.decode(gradeObject.skin_male) or {},
|
||||
skin_female = gradeObject.skin_female and json.decode(gradeObject.skin_female) or {},
|
||||
}
|
||||
|
||||
self.metadata.jobDuty = onDuty
|
||||
TriggerEvent("esx:setJob", self.source, self.job, lastJob)
|
||||
self.triggerEvent("esx:setJob", self.job, lastJob)
|
||||
Player(self.source).state:set("job", self.job, true)
|
||||
end
|
||||
|
||||
function self.addWeapon(weaponName, ammo)
|
||||
if not self.hasWeapon(weaponName) then
|
||||
local weaponLabel <const> = ESX.GetWeaponLabel(weaponName)
|
||||
|
||||
table.insert(self.loadout, {
|
||||
name = weaponName,
|
||||
ammo = ammo,
|
||||
label = weaponLabel,
|
||||
components = {},
|
||||
tintIndex = 0,
|
||||
})
|
||||
|
||||
GiveWeaponToPed(GetPlayerPed(self.source), joaat(weaponName), ammo, false, false)
|
||||
self.triggerEvent("esx:addInventoryItem", weaponLabel, false, true)
|
||||
self.triggerEvent("esx:addLoadoutItem", weaponName, weaponLabel, ammo)
|
||||
end
|
||||
end
|
||||
|
||||
function self.addWeaponComponent(weaponName, weaponComponent)
|
||||
local loadoutNum <const>, weapon <const> = self.getWeapon(weaponName)
|
||||
|
||||
if weapon then
|
||||
local component = ESX.GetWeaponComponent(weaponName, weaponComponent)
|
||||
|
||||
if component then
|
||||
if not self.hasWeaponComponent(weaponName, weaponComponent) then
|
||||
self.loadout[loadoutNum].components[#self.loadout[loadoutNum].components + 1] = weaponComponent
|
||||
local componentHash = ESX.GetWeaponComponent(weaponName, weaponComponent).hash
|
||||
GiveWeaponComponentToPed(GetPlayerPed(self.source), joaat(weaponName), componentHash)
|
||||
self.triggerEvent("esx:addInventoryItem", component.label, false, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function self.addWeaponAmmo(weaponName, ammoCount)
|
||||
local _, weapon = self.getWeapon(weaponName)
|
||||
|
||||
if weapon then
|
||||
weapon.ammo = weapon.ammo + ammoCount
|
||||
SetPedAmmo(GetPlayerPed(self.source), joaat(weaponName), weapon.ammo)
|
||||
end
|
||||
end
|
||||
|
||||
function self.updateWeaponAmmo(weaponName, ammoCount)
|
||||
local _, weapon = self.getWeapon(weaponName)
|
||||
|
||||
if not weapon then
|
||||
return
|
||||
end
|
||||
|
||||
weapon.ammo = ammoCount
|
||||
|
||||
if weapon.ammo <= 0 then
|
||||
local _, weaponConfig = ESX.GetWeapon(weaponName)
|
||||
if weaponConfig.throwable then
|
||||
self.removeWeapon(weaponName)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function self.setWeaponTint(weaponName, weaponTintIndex)
|
||||
local loadoutNum <const>, weapon <const> = self.getWeapon(weaponName)
|
||||
|
||||
if weapon then
|
||||
local _, weaponObject <const> = ESX.GetWeapon(weaponName)
|
||||
|
||||
if weaponObject.tints and weaponObject.tints[weaponTintIndex] then
|
||||
self.loadout[loadoutNum].tintIndex = weaponTintIndex
|
||||
self.triggerEvent("esx:setWeaponTint", weaponName, weaponTintIndex)
|
||||
self.triggerEvent("esx:addInventoryItem", weaponObject.tints[weaponTintIndex], false, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function self.getWeaponTint(weaponName)
|
||||
local _, weapon <const> = self.getWeapon(weaponName)
|
||||
|
||||
if weapon then
|
||||
return weapon.tintIndex
|
||||
end
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
function self.removeWeapon(weaponName)
|
||||
local weaponLabel, playerPed <const> = nil, GetPlayerPed(self.source)
|
||||
|
||||
if not playerPed then
|
||||
return error("xPlayer.removeWeapon ^5invalid^1 player ped!")
|
||||
end
|
||||
|
||||
for k, v in ipairs(self.loadout) do
|
||||
if v.name == weaponName then
|
||||
weaponLabel = v.label
|
||||
|
||||
for _, v2 in ipairs(v.components) do
|
||||
self.removeWeaponComponent(weaponName, v2)
|
||||
end
|
||||
|
||||
local weaponHash = joaat(v.name)
|
||||
RemoveWeaponFromPed(playerPed, weaponHash)
|
||||
SetPedAmmo(playerPed, weaponHash, 0)
|
||||
|
||||
table.remove(self.loadout, k)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if weaponLabel then
|
||||
self.triggerEvent("esx:removeInventoryItem", weaponLabel, false, true)
|
||||
self.triggerEvent("esx:removeLoadoutItem", weaponName, weaponLabel)
|
||||
end
|
||||
end
|
||||
|
||||
function self.removeWeaponComponent(weaponName, weaponComponent)
|
||||
local loadoutNum <const>, weapon <const> = self.getWeapon(weaponName)
|
||||
|
||||
if weapon then
|
||||
local component <const> = ESX.GetWeaponComponent(weaponName, weaponComponent)
|
||||
|
||||
if component then
|
||||
if self.hasWeaponComponent(weaponName, weaponComponent) then
|
||||
for k, v in ipairs(self.loadout[loadoutNum].components) do
|
||||
if v == weaponComponent then
|
||||
table.remove(self.loadout[loadoutNum].components, k)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
self.triggerEvent("esx:removeWeaponComponent", weaponName, weaponComponent)
|
||||
self.triggerEvent("esx:removeInventoryItem", component.label, false, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function self.removeWeaponAmmo(weaponName, ammoCount)
|
||||
local _, weapon = self.getWeapon(weaponName)
|
||||
|
||||
if weapon then
|
||||
weapon.ammo = weapon.ammo - ammoCount
|
||||
SetPedAmmo(GetPlayerPed(self.source), joaat(weaponName), weapon.ammo)
|
||||
end
|
||||
end
|
||||
|
||||
function self.hasWeaponComponent(weaponName, weaponComponent)
|
||||
local _, weapon <const> = self.getWeapon(weaponName)
|
||||
|
||||
if weapon then
|
||||
for _, v in ipairs(weapon.components) do
|
||||
if v == weaponComponent then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function self.hasWeapon(weaponName)
|
||||
for _, v in ipairs(self.loadout) do
|
||||
if v.name == weaponName then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function self.hasItem(item)
|
||||
for _, v in ipairs(self.inventory) do
|
||||
if v.name == item and v.count >= 1 then
|
||||
return v, v.count
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function self.getWeapon(weaponName)
|
||||
for k, v in ipairs(self.loadout) do
|
||||
if v.name == weaponName then
|
||||
return k, v
|
||||
end
|
||||
end
|
||||
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
function self.showNotification(msg, notifyType, length, title, position)
|
||||
self.triggerEvent("esx:showNotification", msg, notifyType, length, title, position)
|
||||
end
|
||||
|
||||
function self.showAdvancedNotification(sender, subject, msg, textureDict, iconType, flash, saveToBrief, hudColorIndex)
|
||||
self.triggerEvent("esx:showAdvancedNotification", sender, subject, msg, textureDict, iconType, flash, saveToBrief, hudColorIndex)
|
||||
end
|
||||
|
||||
function self.showHelpNotification(msg, thisFrame, beep, duration)
|
||||
self.triggerEvent("esx:showHelpNotification", msg, thisFrame, beep, duration)
|
||||
end
|
||||
|
||||
function self.getMeta(index, subIndex)
|
||||
if not index then
|
||||
return self.metadata
|
||||
end
|
||||
|
||||
if type(index) ~= "string" then
|
||||
error("xPlayer.getMeta ^5index^1 should be ^5string^1!")
|
||||
return
|
||||
end
|
||||
|
||||
local metaData = self.metadata[index]
|
||||
if metaData == nil then
|
||||
return Config.EnableDebug and error(("xPlayer.getMeta ^5%s^1 not exist!"):format(index)) or nil
|
||||
end
|
||||
|
||||
if subIndex and type(metaData) == "table" then
|
||||
local _type = type(subIndex)
|
||||
|
||||
if _type == "string" then
|
||||
local value = metaData[subIndex]
|
||||
return value
|
||||
end
|
||||
|
||||
if _type == "table" then
|
||||
local returnValues = {}
|
||||
|
||||
for i = 1, #subIndex do
|
||||
local key = subIndex[i]
|
||||
if type(key) == "string" then
|
||||
returnValues[key] = self.getMeta(index, key)
|
||||
else
|
||||
error(("xPlayer.getMeta subIndex should be ^5string^1 or ^5table^1! that contains ^5string^1, received ^5%s^1!, skipping..."):format(type(key)))
|
||||
end
|
||||
end
|
||||
|
||||
return returnValues
|
||||
end
|
||||
|
||||
error(("xPlayer.getMeta subIndex should be ^5string^1 or ^5table^1!, received ^5%s^1!"):format(_type))
|
||||
return
|
||||
end
|
||||
|
||||
return metaData
|
||||
end
|
||||
|
||||
function self.setMeta(index, value, subValue)
|
||||
if not index then
|
||||
return error("xPlayer.setMeta ^5index^1 is Missing!")
|
||||
end
|
||||
|
||||
if type(index) ~= "string" then
|
||||
return error("xPlayer.setMeta ^5index^1 should be ^5string^1!")
|
||||
end
|
||||
|
||||
if value == nil then
|
||||
return error("xPlayer.setMeta value is missing!")
|
||||
end
|
||||
|
||||
local _type = type(value)
|
||||
|
||||
if not subValue then
|
||||
if _type ~= "number" and _type ~= "string" and _type ~= "table" then
|
||||
return error(("xPlayer.setMeta ^5%s^1 should be ^5number^1 or ^5string^1 or ^5table^1!"):format(value))
|
||||
end
|
||||
|
||||
self.metadata[index] = value
|
||||
else
|
||||
if _type ~= "string" then
|
||||
return error(("xPlayer.setMeta ^5value^1 should be ^5string^1 as a subIndex!"):format(value))
|
||||
end
|
||||
|
||||
if not self.metadata[index] or type(self.metadata[index]) ~= "table" then
|
||||
self.metadata[index] = {}
|
||||
end
|
||||
|
||||
self.metadata[index] = type(self.metadata[index]) == "table" and self.metadata[index] or {}
|
||||
self.metadata[index][value] = subValue
|
||||
end
|
||||
self.triggerEvent('esx:updatePlayerData', 'metadata', self.metadata)
|
||||
end
|
||||
|
||||
function self.clearMeta(index, subValues)
|
||||
if not index then
|
||||
return error("xPlayer.clearMeta ^5index^1 is Missing!")
|
||||
end
|
||||
|
||||
if type(index) ~= "string" then
|
||||
return error("xPlayer.clearMeta ^5index^1 should be ^5string^1!")
|
||||
end
|
||||
|
||||
local metaData = self.metadata[index]
|
||||
if metaData == nil then
|
||||
if Config.EnableDebug then
|
||||
error(("xPlayer.clearMeta ^5%s^1 does not exist!"):format(index))
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if not subValues then
|
||||
-- If no subValues is provided, we will clear the entire value in the metaData table
|
||||
self.metadata[index] = nil
|
||||
elseif type(subValues) == "string" then
|
||||
-- If subValues is a string, we will clear the specific subValue within the table
|
||||
if type(metaData) == "table" then
|
||||
metaData[subValues] = nil
|
||||
else
|
||||
return error(("xPlayer.clearMeta ^5%s^1 is not a table! Cannot clear subValue ^5%s^1."):format(index, subValues))
|
||||
end
|
||||
elseif type(subValues) == "table" then
|
||||
-- If subValues is a table, we will clear multiple subValues within the table
|
||||
for i = 1, #subValues do
|
||||
local subValue = subValues[i]
|
||||
if type(subValue) == "string" then
|
||||
if type(metaData) == "table" then
|
||||
metaData[subValue] = nil
|
||||
else
|
||||
error(("xPlayer.clearMeta ^5%s^1 is not a table! Cannot clear subValue ^5%s^1."):format(index, subValue))
|
||||
end
|
||||
else
|
||||
error(("xPlayer.clearMeta subValues should contain ^5string^1, received ^5%s^1, skipping..."):format(type(subValue)))
|
||||
end
|
||||
end
|
||||
else
|
||||
return error(("xPlayer.clearMeta ^5subValues^1 should be ^5string^1 or ^5table^1, received ^5%s^1!"):format(type(subValues)))
|
||||
end
|
||||
self.triggerEvent('esx:updatePlayerData', 'metadata', self.metadata)
|
||||
end
|
||||
|
||||
function self.executeCommand(command)
|
||||
if type(command) ~= "string" then
|
||||
error("xPlayer.executeCommand must be of type string!")
|
||||
return
|
||||
end
|
||||
|
||||
self.triggerEvent("esx:executeCommand", command)
|
||||
end
|
||||
|
||||
for _, funcs in pairs(Core.PlayerFunctionOverrides) do
|
||||
for fnName, fn in pairs(funcs) do
|
||||
self[fnName] = fn(self)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
local function runStaticPlayerMethod(src, method, ...)
|
||||
local xPlayer = ESX.Players[src]
|
||||
if not xPlayer then
|
||||
return
|
||||
end
|
||||
|
||||
if not ESX.IsFunctionReference(xPlayer[method]) then
|
||||
error(("Attempted to call invalid method on playerId %s: %s"):format(src, method))
|
||||
end
|
||||
|
||||
return xPlayer[method](...)
|
||||
end
|
||||
exports("RunStaticPlayerMethod", runStaticPlayerMethod)
|
||||
237
resources/[core]/es_extended/server/classes/vehicle.lua
Normal file
237
resources/[core]/es_extended/server/classes/vehicle.lua
Normal file
@@ -0,0 +1,237 @@
|
||||
---@class CVehicleData
|
||||
---@field plate string
|
||||
---@field netId number
|
||||
---@field entity number
|
||||
---@field modelHash number
|
||||
---@field owner string
|
||||
|
||||
---@class CExtendedVehicle
|
||||
---@field plate string
|
||||
---@field isValid fun(self:CExtendedVehicle):boolean
|
||||
---@field new fun(owner:string, plate:string, coords:vector4): CExtendedVehicle?
|
||||
---@field getFromPlate fun(plate:string):CExtendedVehicle?
|
||||
---@field getPlate fun(self:CExtendedVehicle):string?
|
||||
---@field getNetId fun(self:CExtendedVehicle):number?
|
||||
---@field getEntity fun(self:CExtendedVehicle):number?
|
||||
---@field getModelHash fun(self:CExtendedVehicle):number?
|
||||
---@field getOwner fun(self:CExtendedVehicle):string?
|
||||
---@field setPlate fun(self:CExtendedVehicle, newPlate:string):boolean
|
||||
---@field setProps fun(self:CExtendedVehicle, newProps:table):boolean
|
||||
---@field setOwner fun(self:CExtendedVehicle, newOwner:string):boolean
|
||||
---@field delete fun(self:CExtendedVehicle, garageName:string?, isImpound:boolean?):nil
|
||||
Core.vehicleClass = {
|
||||
plate = "",
|
||||
new = function(owner, plate, coords)
|
||||
assert(type(owner) == "string", "Expected 'owner' to be a string")
|
||||
assert(type(plate) == "string", "Expected 'plate' to be a string")
|
||||
assert(type(coords) == "vector4", "Expected 'coords' to be a vector4")
|
||||
|
||||
local xVehicle = Core.vehicleClass.getFromPlate(plate)
|
||||
if xVehicle then
|
||||
return xVehicle
|
||||
end
|
||||
|
||||
local vehicleProps = MySQL.scalar.await("SELECT `vehicle` FROM `owned_vehicles` WHERE `stored` = true AND `owner` = ? AND `plate` = ? LIMIT 1", { owner, plate })
|
||||
if not vehicleProps then
|
||||
return
|
||||
end
|
||||
vehicleProps = json.decode(vehicleProps)
|
||||
|
||||
if type(vehicleProps.model) ~= "number" then
|
||||
vehicleProps.model = joaat(vehicleProps.model)
|
||||
end
|
||||
|
||||
local netId = ESX.OneSync.SpawnVehicle(vehicleProps.model, coords.xyz, coords.w, vehicleProps)
|
||||
if not netId then
|
||||
return
|
||||
end
|
||||
|
||||
local entity = NetworkGetEntityFromNetworkId(netId)
|
||||
if entity <= 0 then
|
||||
return
|
||||
end
|
||||
Entity(entity).state:set("owner", owner, false)
|
||||
Entity(entity).state:set("plate", plate, false)
|
||||
|
||||
---@type CVehicleData
|
||||
local vehicleData = {
|
||||
plate = plate,
|
||||
entity = entity,
|
||||
netId = netId,
|
||||
modelHash = vehicleProps.model,
|
||||
owner = owner,
|
||||
}
|
||||
Core.vehicles[plate] = vehicleData
|
||||
|
||||
MySQL.update.await("UPDATE `owned_vehicles` SET `stored` = false WHERE `owner` = ? AND `plate` = ?", { owner, plate })
|
||||
|
||||
local obj = table.clone(Core.vehicleClass)
|
||||
obj.plate = plate
|
||||
TriggerEvent("esx:createdExtendedVehicle", obj)
|
||||
|
||||
return obj
|
||||
end,
|
||||
getFromPlate = function(plate)
|
||||
assert(type(plate) == "string", "Expected 'plate' to be a string")
|
||||
|
||||
if Core.vehicles[plate] then
|
||||
local obj = table.clone(Core.vehicleClass)
|
||||
obj.plate = plate
|
||||
|
||||
if obj:isValid() then
|
||||
return obj
|
||||
end
|
||||
end
|
||||
end,
|
||||
isValid = function(self)
|
||||
local vehicleData = Core.vehicles[self.plate]
|
||||
if not vehicleData then
|
||||
return false
|
||||
end
|
||||
|
||||
local entity = NetworkGetEntityFromNetworkId(vehicleData.netId)
|
||||
if entity <= 0 or Entity(entity).state.owner ~= vehicleData.owner or Entity(entity).state.plate ~= vehicleData.plate then
|
||||
self:delete()
|
||||
return false
|
||||
end
|
||||
|
||||
vehicleData.entity = entity
|
||||
|
||||
return true
|
||||
end,
|
||||
getNetId = function(self)
|
||||
if not self:isValid() then
|
||||
return
|
||||
end
|
||||
|
||||
return Core.vehicles[self.plate].netId
|
||||
end,
|
||||
getEntity = function(self)
|
||||
if not self:isValid() then
|
||||
return
|
||||
end
|
||||
|
||||
return Core.vehicles[self.plate].entity
|
||||
end,
|
||||
getPlate = function(self)
|
||||
if not self:isValid() then
|
||||
return
|
||||
end
|
||||
|
||||
return Core.vehicles[self.plate].plate
|
||||
end,
|
||||
getModelHash = function(self)
|
||||
if not self:isValid() then
|
||||
return
|
||||
end
|
||||
|
||||
return Core.vehicles[self.plate].modelHash
|
||||
end,
|
||||
getOwner = function(self)
|
||||
if not self:isValid() then
|
||||
return
|
||||
end
|
||||
|
||||
return Core.vehicles[self.plate].owner
|
||||
end,
|
||||
setPlate = function(self, newPlate)
|
||||
if not self:isValid() then
|
||||
return false
|
||||
end
|
||||
assert(type(newPlate) == "string", "Expected 'plate' to be a string")
|
||||
|
||||
local vehicleData = Core.vehicles[self.plate]
|
||||
local affectedRows = MySQL.update.await("UPDATE `owned_vehicles` SET `plate` = ? WHERE `plate` = ? AND `owner` = ?", { newPlate, vehicleData.plate, vehicleData.owner })
|
||||
if affectedRows <= 0 then
|
||||
self:delete()
|
||||
return false
|
||||
end
|
||||
|
||||
Entity(vehicleData.entity).state:set("plate", newPlate, false)
|
||||
SetVehicleNumberPlateText(vehicleData.entity, newPlate)
|
||||
|
||||
local oldPlate = vehicleData.plate
|
||||
vehicleData.plate = newPlate
|
||||
Core.vehicles[newPlate] = table.clone(vehicleData)
|
||||
Core.vehicles[oldPlate] = nil
|
||||
|
||||
TriggerEvent("esx:changedExtendedVehiclePlate", vehicleData.plate, oldPlate)
|
||||
Wait(0)
|
||||
|
||||
return true
|
||||
end,
|
||||
setProps = function(self, newProps)
|
||||
if not self:isValid() then
|
||||
return false
|
||||
end
|
||||
assert(type(newProps) == "table", "Expected 'props' to be a table")
|
||||
|
||||
local vehicleData = Core.vehicles[self.plate]
|
||||
local affectedRows = MySQL.update.await("UPDATE `owned_vehicles` SET `vehicle` = ? WHERE `plate` = ? AND `owner` = ?", json.encode(newProps), vehicleData.plate, vehicleData.owner)
|
||||
if affectedRows <= 0 then
|
||||
self:delete()
|
||||
return false
|
||||
end
|
||||
|
||||
Entity(vehicleData.entity).state:set("VehicleProperties", newProps, true)
|
||||
|
||||
return true
|
||||
end,
|
||||
setOwner = function(self, newOwner)
|
||||
if not self:isValid() then
|
||||
return false
|
||||
end
|
||||
assert(type(newOwner) == "string", "Expected 'owner' to be a string")
|
||||
|
||||
local vehicleData = Core.vehicles[self.plate]
|
||||
if vehicleData.owner == newOwner then
|
||||
return true
|
||||
end
|
||||
|
||||
local affectedRows = MySQL.update.await("UPDATE `owned_vehicles` SET `owner` = ? WHERE owner = ? AND `plate` = ?", { newOwner, vehicleData.owner, vehicleData.plate })
|
||||
if affectedRows <= 0 then
|
||||
self:delete()
|
||||
return false
|
||||
end
|
||||
|
||||
Entity(vehicleData.entity).state:set("owner", newOwner, false)
|
||||
vehicleData.owner = newOwner
|
||||
|
||||
return true
|
||||
end,
|
||||
delete = function(self, garageName, isImpound)
|
||||
if type(garageName) ~= "string" then
|
||||
garageName = nil
|
||||
end
|
||||
if type(isImpound) ~= "boolean" then
|
||||
isImpound = false
|
||||
end
|
||||
|
||||
local vehicleData = Core.vehicles[self.plate]
|
||||
if not vehicleData then
|
||||
return
|
||||
end
|
||||
|
||||
local entity = NetworkGetEntityFromNetworkId(vehicleData.netId)
|
||||
if entity >= 0 and Entity(entity).state.owner == vehicleData.owner then
|
||||
DeleteEntity(vehicleData.entity)
|
||||
end
|
||||
|
||||
local query = "UPDATE `owned_vehicles` SET `stored` = true WHERE `plate` = ? AND `owner` = ?"
|
||||
local queryParams = { vehicleData.plate, vehicleData.owner }
|
||||
if garageName then
|
||||
if isImpound then
|
||||
query = "UPDATE `owned_vehicles` SET `stored` = true, `parking` = NULL, `pound` = ? WHERE `plate` = ? AND `owner` = ?"
|
||||
else
|
||||
query = "UPDATE `owned_vehicles` SET `stored` = true, `pound` = NULL, `parking` = ? WHERE `plate` = ? AND `owner` = ?"
|
||||
end
|
||||
|
||||
queryParams = { garageName, vehicleData.plate, vehicleData.owner }
|
||||
end
|
||||
|
||||
MySQL.update.await(query, queryParams)
|
||||
TriggerEvent("esx:deletedExtendedVehicle", self)
|
||||
|
||||
Core.vehicles[self.plate] = nil
|
||||
end,
|
||||
}
|
||||
54
resources/[core]/es_extended/server/common.lua
Normal file
54
resources/[core]/es_extended/server/common.lua
Normal file
@@ -0,0 +1,54 @@
|
||||
ESX.Players = {}
|
||||
ESX.Jobs = {}
|
||||
ESX.Items = {}
|
||||
|
||||
RegisterNetEvent("esx:onPlayerSpawn", function()
|
||||
ESX.Players[source].spawned = true
|
||||
end)
|
||||
|
||||
if Config.CustomInventory then
|
||||
SetConvarReplicated("inventory:framework", "esx")
|
||||
SetConvarReplicated("inventory:weight", tostring(Config.MaxWeight * 1000))
|
||||
end
|
||||
|
||||
local function StartDBSync()
|
||||
CreateThread(function()
|
||||
local interval <const> = 10 * 60 * 1000
|
||||
while true do
|
||||
Wait(interval)
|
||||
Core.SavePlayers()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
MySQL.ready(function()
|
||||
Core.DatabaseConnected = true
|
||||
|
||||
if not Config.CustomInventory then
|
||||
ESX.RefreshItems()
|
||||
end
|
||||
|
||||
ESX.RefreshJobs()
|
||||
|
||||
print(("[^2INFO^7] ESX ^5Legacy %s^0 initialized!"):format(GetResourceMetadata(GetCurrentResourceName(), "version", 0)))
|
||||
|
||||
StartDBSync()
|
||||
if Config.EnablePaycheck then
|
||||
StartPayCheck()
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx:clientLog", function(msg)
|
||||
if Config.EnableDebug then
|
||||
print(("[^2TRACE^7] %s^7"):format(msg))
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx:ReturnVehicleType", function(Type, Request)
|
||||
if Core.ClientCallbacks[Request] then
|
||||
Core.ClientCallbacks[Request](Type)
|
||||
Core.ClientCallbacks[Request] = nil
|
||||
end
|
||||
end)
|
||||
|
||||
GlobalState.playerCount = 0
|
||||
14
resources/[core]/es_extended/server/core.lua
Normal file
14
resources/[core]/es_extended/server/core.lua
Normal file
@@ -0,0 +1,14 @@
|
||||
Core = {}
|
||||
Core.JobsPlayerCount = {}
|
||||
Core.UsableItemsCallbacks = {}
|
||||
Core.RegisteredCommands = {}
|
||||
Core.Pickups = {}
|
||||
Core.PickupId = 0
|
||||
Core.PlayerFunctionOverrides = {}
|
||||
Core.DatabaseConnected = false
|
||||
Core.playersByIdentifier = {}
|
||||
Core.JobsLoaded = false
|
||||
|
||||
---@type table<string, CVehicleData>
|
||||
Core.vehicles = {}
|
||||
Core.vehicleTypesByModel = {}
|
||||
909
resources/[core]/es_extended/server/functions.lua
Normal file
909
resources/[core]/es_extended/server/functions.lua
Normal file
@@ -0,0 +1,909 @@
|
||||
---@param msg string
|
||||
---@return nil
|
||||
function ESX.Trace(msg)
|
||||
if Config.EnableDebug then
|
||||
print(("[^2TRACE^7] %s^7"):format(msg))
|
||||
end
|
||||
end
|
||||
|
||||
--- Triggers an event for one or more clients.
|
||||
---@param eventName string The name of the event to trigger.
|
||||
---@param playerIds table|number If a number, represents a single player ID. If a table, represents an array of player IDs.
|
||||
---@param ... any Additional arguments to pass to the event handler.
|
||||
function ESX.TriggerClientEvent(eventName, playerIds, ...)
|
||||
if type(playerIds) == "number" then
|
||||
TriggerClientEvent(eventName, playerIds, ...)
|
||||
return
|
||||
end
|
||||
|
||||
local payload = msgpack.pack_args(...)
|
||||
local payloadLength = #payload
|
||||
|
||||
for i = 1, #playerIds do
|
||||
TriggerClientEventInternal(eventName, playerIds[i], payload, payloadLength)
|
||||
end
|
||||
end
|
||||
|
||||
---@param name string | table
|
||||
---@param group string | table
|
||||
---@param cb function
|
||||
---@param allowConsole? boolean
|
||||
---@param suggestion? table
|
||||
function ESX.RegisterCommand(name, group, cb, allowConsole, suggestion)
|
||||
if type(name) == "table" then
|
||||
for _, v in ipairs(name) do
|
||||
ESX.RegisterCommand(v, group, cb, allowConsole, suggestion)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if Core.RegisteredCommands[name] then
|
||||
print(('[^3WARNING^7] Command ^5"%s" ^7already registered, overriding command'):format(name))
|
||||
|
||||
if Core.RegisteredCommands[name].suggestion then
|
||||
TriggerClientEvent("chat:removeSuggestion", -1, ("/%s"):format(name))
|
||||
end
|
||||
end
|
||||
|
||||
if suggestion then
|
||||
if not suggestion.arguments then
|
||||
suggestion.arguments = {}
|
||||
end
|
||||
if not suggestion.help then
|
||||
suggestion.help = ""
|
||||
end
|
||||
|
||||
TriggerClientEvent("chat:addSuggestion", -1, ("/%s"):format(name), suggestion.help, suggestion.arguments)
|
||||
end
|
||||
|
||||
Core.RegisteredCommands[name] = { group = group, cb = cb, allowConsole = allowConsole, suggestion = suggestion }
|
||||
|
||||
RegisterCommand(name, function(playerId, args)
|
||||
local command = Core.RegisteredCommands[name]
|
||||
|
||||
if not command.allowConsole and playerId == 0 then
|
||||
print(("[^3WARNING^7] ^5%s^0"):format(TranslateCap("commanderror_console")))
|
||||
else
|
||||
local xPlayer, err = ESX.Players[playerId], nil
|
||||
|
||||
if command.suggestion then
|
||||
if command.suggestion.validate then
|
||||
if #args ~= #command.suggestion.arguments then
|
||||
err = TranslateCap("commanderror_argumentmismatch", #args, #command.suggestion.arguments)
|
||||
end
|
||||
end
|
||||
|
||||
if not err and command.suggestion.arguments then
|
||||
local newArgs = {}
|
||||
|
||||
for k, v in ipairs(command.suggestion.arguments) do
|
||||
if v.type then
|
||||
if v.type == "number" then
|
||||
local newArg = tonumber(args[k])
|
||||
|
||||
if newArg then
|
||||
newArgs[v.name] = newArg
|
||||
else
|
||||
err = TranslateCap("commanderror_argumentmismatch_number", k)
|
||||
end
|
||||
elseif v.type == "player" or v.type == "playerId" then
|
||||
local targetPlayer = tonumber(args[k])
|
||||
|
||||
if args[k] == "me" then
|
||||
targetPlayer = playerId
|
||||
end
|
||||
|
||||
if targetPlayer then
|
||||
local xTargetPlayer = ESX.GetPlayerFromId(targetPlayer)
|
||||
|
||||
if xTargetPlayer then
|
||||
if v.type == "player" then
|
||||
newArgs[v.name] = xTargetPlayer
|
||||
else
|
||||
newArgs[v.name] = targetPlayer
|
||||
end
|
||||
else
|
||||
err = TranslateCap("commanderror_invalidplayerid")
|
||||
end
|
||||
else
|
||||
err = TranslateCap("commanderror_argumentmismatch_number", k)
|
||||
end
|
||||
elseif v.type == "string" then
|
||||
local newArg = tonumber(args[k])
|
||||
if not newArg then
|
||||
newArgs[v.name] = args[k]
|
||||
else
|
||||
err = TranslateCap("commanderror_argumentmismatch_string", k)
|
||||
end
|
||||
elseif v.type == "item" then
|
||||
if ESX.Items[args[k]] then
|
||||
newArgs[v.name] = args[k]
|
||||
else
|
||||
err = TranslateCap("commanderror_invaliditem")
|
||||
end
|
||||
elseif v.type == "weapon" then
|
||||
if ESX.GetWeapon(args[k]) then
|
||||
newArgs[v.name] = string.upper(args[k])
|
||||
else
|
||||
err = TranslateCap("commanderror_invalidweapon")
|
||||
end
|
||||
elseif v.type == "any" then
|
||||
newArgs[v.name] = args[k]
|
||||
elseif v.type == "merge" then
|
||||
local length = 0
|
||||
for i = 1, k - 1 do
|
||||
length = length + string.len(args[i]) + 1
|
||||
end
|
||||
local merge = table.concat(args, " ")
|
||||
|
||||
newArgs[v.name] = string.sub(merge, length)
|
||||
elseif v.type == "coordinate" then
|
||||
local coord = tonumber(args[k]:match("(-?%d+%.?%d*)"))
|
||||
if not coord then
|
||||
err = TranslateCap("commanderror_argumentmismatch_number", k)
|
||||
else
|
||||
newArgs[v.name] = coord
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if ESX.IsFunctionReference(v.Validator?.validate) and not err then
|
||||
local candidate = newArgs[v.name]
|
||||
local ok, res = pcall(v.Validator.validate, candidate)
|
||||
if not ok or res ~= true then
|
||||
err = v.Validator.err or TranslateCap("commanderror_argumentmismatch")
|
||||
end
|
||||
end
|
||||
|
||||
--backwards compatibility
|
||||
if v.validate ~= nil and not v.validate then
|
||||
err = nil
|
||||
end
|
||||
|
||||
if err then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
args = newArgs
|
||||
end
|
||||
end
|
||||
|
||||
if err then
|
||||
if playerId == 0 then
|
||||
print(("[^3WARNING^7] %s^7"):format(err))
|
||||
else
|
||||
xPlayer.showNotification(err)
|
||||
end
|
||||
else
|
||||
cb(xPlayer or false, args, function(msg)
|
||||
if playerId == 0 then
|
||||
print(("[^3WARNING^7] %s^7"):format(msg))
|
||||
else
|
||||
xPlayer.showNotification(msg)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
end, true)
|
||||
|
||||
if type(group) == "table" then
|
||||
for _, v in ipairs(group) do
|
||||
ExecuteCommand(("add_ace group.%s command.%s allow"):format(v, name))
|
||||
end
|
||||
else
|
||||
ExecuteCommand(("add_ace group.%s command.%s allow"):format(group, name))
|
||||
end
|
||||
end
|
||||
|
||||
local function updateHealthAndArmorInMetadata(xPlayer)
|
||||
local ped = GetPlayerPed(xPlayer.source)
|
||||
xPlayer.setMeta("health", GetEntityHealth(ped))
|
||||
xPlayer.setMeta("armor", GetPedArmour(ped))
|
||||
xPlayer.setMeta("lastPlaytime", xPlayer.getPlayTime())
|
||||
end
|
||||
|
||||
---@param xPlayer table
|
||||
---@param cb? function
|
||||
---@return nil
|
||||
function Core.SavePlayer(xPlayer, cb)
|
||||
if not xPlayer.spawned then
|
||||
return cb and cb()
|
||||
end
|
||||
|
||||
updateHealthAndArmorInMetadata(xPlayer)
|
||||
local parameters <const> = {
|
||||
json.encode(xPlayer.getAccounts(true)),
|
||||
xPlayer.job.name,
|
||||
xPlayer.job.grade,
|
||||
xPlayer.group,
|
||||
json.encode(xPlayer.getCoords(false, true)),
|
||||
json.encode(xPlayer.getInventory(true)),
|
||||
json.encode(xPlayer.getLoadout(true)),
|
||||
json.encode(xPlayer.getMeta()),
|
||||
xPlayer.identifier,
|
||||
}
|
||||
|
||||
MySQL.prepare(
|
||||
"UPDATE `users` SET `accounts` = ?, `job` = ?, `job_grade` = ?, `group` = ?, `position` = ?, `inventory` = ?, `loadout` = ?, `metadata` = ? WHERE `identifier` = ?",
|
||||
parameters,
|
||||
function(affectedRows)
|
||||
if affectedRows == 1 then
|
||||
print(('[^2INFO^7] Saved player ^5"%s^7"'):format(xPlayer.name))
|
||||
TriggerEvent("esx:playerSaved", xPlayer.playerId, xPlayer)
|
||||
end
|
||||
if cb then
|
||||
cb()
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
---@param cb? function
|
||||
---@return nil
|
||||
function Core.SavePlayers(cb)
|
||||
local xPlayers <const> = ESX.Players
|
||||
if not next(xPlayers) then
|
||||
return
|
||||
end
|
||||
|
||||
local startTime <const> = os.time()
|
||||
local parameters = {}
|
||||
|
||||
for _, xPlayer in pairs(ESX.Players) do
|
||||
updateHealthAndArmorInMetadata(xPlayer)
|
||||
parameters[#parameters + 1] = {
|
||||
json.encode(xPlayer.getAccounts(true)),
|
||||
xPlayer.job.name,
|
||||
xPlayer.job.grade,
|
||||
xPlayer.group,
|
||||
json.encode(xPlayer.getCoords(false, true)),
|
||||
json.encode(xPlayer.getInventory(true)),
|
||||
json.encode(xPlayer.getLoadout(true)),
|
||||
json.encode(xPlayer.getMeta()),
|
||||
xPlayer.identifier,
|
||||
}
|
||||
end
|
||||
|
||||
MySQL.prepare(
|
||||
"UPDATE `users` SET `accounts` = ?, `job` = ?, `job_grade` = ?, `group` = ?, `position` = ?, `inventory` = ?, `loadout` = ?, `metadata` = ? WHERE `identifier` = ?",
|
||||
parameters,
|
||||
function(results)
|
||||
if not results then
|
||||
return
|
||||
end
|
||||
|
||||
if type(cb) == "function" then
|
||||
return cb()
|
||||
end
|
||||
|
||||
print(("[^2INFO^7] Saved ^5%s^7 %s over ^5%s^7 ms"):format(#parameters,
|
||||
#parameters > 1 and "players" or "player", ESX.Math.Round((os.time() - startTime) / 1000000, 2)))
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
ESX.GetPlayers = GetPlayers
|
||||
|
||||
local function checkTable(key, val, xPlayer, xPlayers, minimal)
|
||||
for valIndex = 1, #val do
|
||||
local value = val[valIndex]
|
||||
if not xPlayers[value] then
|
||||
xPlayers[value] = {}
|
||||
end
|
||||
|
||||
if (key == "job" and xPlayer.job.name == value) or xPlayer[key] == value then
|
||||
xPlayers[value][#xPlayers[value] + 1] = (minimal and xPlayer.source or xPlayer)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param key? string
|
||||
---@param val? string|table
|
||||
---@param minimal? boolean
|
||||
---@return xPlayer[]|number[]|table<any, xPlayer[]>|table<any, number[]>
|
||||
function ESX.GetExtendedPlayers(key, val, minimal)
|
||||
if not key then
|
||||
if not minimal then
|
||||
return ESX.Table.ToArray(ESX.Players)
|
||||
end
|
||||
|
||||
local xPlayers = {}
|
||||
local index = 1
|
||||
for src, _ in pairs(ESX.Players) do
|
||||
xPlayers[index] = src
|
||||
index += 1
|
||||
end
|
||||
|
||||
return xPlayers
|
||||
end
|
||||
|
||||
local xPlayers = {}
|
||||
if type(val) == "table" then
|
||||
for _, xPlayer in pairs(ESX.Players) do
|
||||
checkTable(key, val, xPlayer, xPlayers, minimal)
|
||||
end
|
||||
|
||||
return xPlayers
|
||||
end
|
||||
|
||||
for _, xPlayer in pairs(ESX.Players) do
|
||||
if (key == "job" and xPlayer.job.name == val) or xPlayer[key] == val then
|
||||
xPlayers[#xPlayers + 1] = (minimal and xPlayer.source or xPlayer)
|
||||
end
|
||||
end
|
||||
|
||||
return xPlayers
|
||||
end
|
||||
|
||||
---@param key? string
|
||||
---@param val? string|table
|
||||
---@return number | table
|
||||
function ESX.GetNumPlayers(key, val)
|
||||
if not key then
|
||||
return #GetPlayers()
|
||||
end
|
||||
|
||||
if type(val) == "table" then
|
||||
local numPlayers = {}
|
||||
if key == "job" then
|
||||
for _, v in ipairs(val) do
|
||||
numPlayers[v] = (Core.JobsPlayerCount[v] or 0)
|
||||
end
|
||||
return numPlayers
|
||||
end
|
||||
|
||||
local filteredPlayers = ESX.GetExtendedPlayers(key, val)
|
||||
for i, v in pairs(filteredPlayers) do
|
||||
numPlayers[i] = (#v or 0)
|
||||
end
|
||||
return numPlayers
|
||||
end
|
||||
|
||||
if key == "job" then
|
||||
return (Core.JobsPlayerCount[val] or 0)
|
||||
end
|
||||
|
||||
return #ESX.GetExtendedPlayers(key, val)
|
||||
end
|
||||
|
||||
---@param source number
|
||||
---@return xPlayer?
|
||||
function ESX.GetPlayerFromId(source)
|
||||
return ESX.Players[tonumber(source)]
|
||||
end
|
||||
|
||||
---@param identifier string
|
||||
---@return xPlayer?
|
||||
function ESX.GetPlayerFromIdentifier(identifier)
|
||||
return Core.playersByIdentifier[identifier]
|
||||
end
|
||||
|
||||
---@param identifier string
|
||||
---@return number playerId
|
||||
function ESX.GetPlayerIdFromIdentifier(identifier)
|
||||
return Core.playersByIdentifier[identifier]?.source
|
||||
end
|
||||
|
||||
---@param source number
|
||||
---@return boolean
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function ESX.IsPlayerLoaded(source)
|
||||
return ESX.Players[source] ~= nil
|
||||
end
|
||||
|
||||
---@param playerId number | string
|
||||
---@return string, number
|
||||
function ESX.GetIdentifier(playerId)
|
||||
local fxDk = GetConvarInt("sv_fxdkMode", 0)
|
||||
if fxDk == 1 then
|
||||
return "ESX-DEBUG-LICENCE", 0
|
||||
end
|
||||
|
||||
playerId = tostring(playerId)
|
||||
|
||||
local identifierType = Config.Identifier
|
||||
local identifier = GetPlayerIdentifierByType(playerId, identifierType)
|
||||
|
||||
assert(identifier, ("[ESX] GetIdentifier failed: no identifier found for playerId %s with type '%s'"):format(playerId, identifierType))
|
||||
|
||||
return identifier:gsub(("%s:"):format(identifierType), "")
|
||||
end
|
||||
|
||||
---@param model string|number
|
||||
---@param player number
|
||||
---@param cb function?
|
||||
---@return string?
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function ESX.GetVehicleType(model, player, cb)
|
||||
if cb and not ESX.IsFunctionReference(cb) then
|
||||
error("Invalid callback function")
|
||||
end
|
||||
|
||||
local promise = not cb and promise.new()
|
||||
local function resolve(result)
|
||||
if promise then
|
||||
promise:resolve(result)
|
||||
elseif cb then
|
||||
cb(result)
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
model = type(model) == "string" and joaat(model) or model
|
||||
|
||||
if Core.vehicleTypesByModel[model] then
|
||||
return resolve(Core.vehicleTypesByModel[model])
|
||||
end
|
||||
|
||||
ESX.TriggerClientCallback(player, "esx:GetVehicleType", function(vehicleType)
|
||||
Core.vehicleTypesByModel[model] = vehicleType
|
||||
resolve(vehicleType)
|
||||
end, model)
|
||||
|
||||
if promise then
|
||||
return Citizen.Await(promise)
|
||||
end
|
||||
end
|
||||
|
||||
---@param name string
|
||||
---@param title string
|
||||
---@param color string
|
||||
---@param message string
|
||||
---@return nil
|
||||
function ESX.DiscordLog(name, title, color, message)
|
||||
local webHook = Config.DiscordLogs.Webhooks[name] or Config.DiscordLogs.Webhooks.default
|
||||
local embedData = {
|
||||
{
|
||||
["title"] = title,
|
||||
["color"] = Config.DiscordLogs.Colors[color] or Config.DiscordLogs.Colors.default,
|
||||
["footer"] = {
|
||||
["text"] = "| ESX Logs | " .. os.date(),
|
||||
["icon_url"] =
|
||||
"https://cdn.discordapp.com/attachments/944789399852417096/1020099828266586193/blanc-800x800.png",
|
||||
},
|
||||
["description"] = message,
|
||||
["author"] = {
|
||||
["name"] = "ESX Framework",
|
||||
["icon_url"] = "https://cdn.discordapp.com/emojis/939245183621558362.webp?size=128&quality=lossless",
|
||||
},
|
||||
},
|
||||
}
|
||||
PerformHttpRequest(
|
||||
webHook,
|
||||
function()
|
||||
return
|
||||
end,
|
||||
"POST",
|
||||
json.encode({
|
||||
username = "Logs",
|
||||
embeds = embedData,
|
||||
}),
|
||||
{
|
||||
["Content-Type"] = "application/json",
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
---@param name string
|
||||
---@param title string
|
||||
---@param color string
|
||||
---@param fields table
|
||||
---@return nil
|
||||
function ESX.DiscordLogFields(name, title, color, fields)
|
||||
for i = 1, #fields do
|
||||
local field = fields[i]
|
||||
field.value = tostring(field.value)
|
||||
end
|
||||
|
||||
local webHook = Config.DiscordLogs.Webhooks[name] or Config.DiscordLogs.Webhooks.default
|
||||
local embedData = {
|
||||
{
|
||||
["title"] = title,
|
||||
["color"] = Config.DiscordLogs.Colors[color] or Config.DiscordLogs.Colors.default,
|
||||
["footer"] = {
|
||||
["text"] = "| ESX Logs | " .. os.date(),
|
||||
["icon_url"] =
|
||||
"https://cdn.discordapp.com/attachments/944789399852417096/1020099828266586193/blanc-800x800.png",
|
||||
},
|
||||
["fields"] = fields,
|
||||
["description"] = "",
|
||||
["author"] = {
|
||||
["name"] = "ESX Framework",
|
||||
["icon_url"] = "https://cdn.discordapp.com/emojis/939245183621558362.webp?size=128&quality=lossless",
|
||||
},
|
||||
},
|
||||
}
|
||||
PerformHttpRequest(
|
||||
webHook,
|
||||
function()
|
||||
return
|
||||
end,
|
||||
"POST",
|
||||
json.encode({
|
||||
username = "Logs",
|
||||
embeds = embedData,
|
||||
}),
|
||||
{
|
||||
["Content-Type"] = "application/json",
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
---@return nil
|
||||
function ESX.RefreshJobs()
|
||||
Core.JobsLoaded = false
|
||||
|
||||
local Jobs = {}
|
||||
local jobs = MySQL.query.await("SELECT * FROM jobs")
|
||||
|
||||
for _, v in ipairs(jobs) do
|
||||
Jobs[v.name] = v
|
||||
Jobs[v.name].grades = {}
|
||||
end
|
||||
|
||||
local jobGrades = MySQL.query.await("SELECT * FROM job_grades")
|
||||
|
||||
for _, v in ipairs(jobGrades) do
|
||||
if Jobs[v.job_name] then
|
||||
Jobs[v.job_name].grades[tostring(v.grade)] = v
|
||||
else
|
||||
print(('[^3WARNING^7] Ignoring job grades for ^5"%s"^0 due to missing job'):format(v.job_name))
|
||||
end
|
||||
end
|
||||
|
||||
for _, v in pairs(Jobs) do
|
||||
if ESX.Table.SizeOf(v.grades) == 0 then
|
||||
Jobs[v.name] = nil
|
||||
print(('[^3WARNING^7] Ignoring job ^5"%s"^0 due to no job grades found'):format(v.name))
|
||||
end
|
||||
end
|
||||
|
||||
if not Jobs then
|
||||
-- Fallback data, if no jobs exist
|
||||
ESX.Jobs["unemployed"] = { name = "unemployed", label = "Unemployed", type = "civ", whitelisted = false, grades = { ["0"] = { grade = 0, name = "unemployed", label = "Unemployed", salary = 200, skin_male = {}, skin_female = {} } } }
|
||||
else
|
||||
ESX.Jobs = Jobs
|
||||
end
|
||||
|
||||
TriggerEvent("esx:jobsRefreshed")
|
||||
Core.JobsLoaded = true
|
||||
end
|
||||
|
||||
---@param item string
|
||||
---@param cb function
|
||||
---@return nil
|
||||
function ESX.RegisterUsableItem(item, cb)
|
||||
Core.UsableItemsCallbacks[item] = cb
|
||||
end
|
||||
|
||||
---@param source number
|
||||
---@param item string
|
||||
---@param ... any
|
||||
---@return nil
|
||||
function ESX.UseItem(source, item, ...)
|
||||
if ESX.Items[item] then
|
||||
local itemCallback = Core.UsableItemsCallbacks[item]
|
||||
|
||||
if itemCallback then
|
||||
local success, result = pcall(itemCallback, source, item, ...)
|
||||
|
||||
if not success then
|
||||
return result and print(result) or
|
||||
print(('[^3WARNING^7] An error occured when using item ^5"%s"^7! This was not caused by ESX.'):format(
|
||||
item))
|
||||
end
|
||||
end
|
||||
else
|
||||
print(('[^3WARNING^7] Item ^5"%s"^7 was used but does not exist!'):format(item))
|
||||
end
|
||||
end
|
||||
|
||||
---@param index string
|
||||
---@param overrides table
|
||||
---@return nil
|
||||
function ESX.RegisterPlayerFunctionOverrides(index, overrides)
|
||||
Core.PlayerFunctionOverrides[index] = overrides
|
||||
end
|
||||
|
||||
---@param index string
|
||||
---@return nil
|
||||
function ESX.SetPlayerFunctionOverride(index)
|
||||
if not index or not Core.PlayerFunctionOverrides[index] then
|
||||
return print("[^3WARNING^7] No valid index provided.")
|
||||
end
|
||||
|
||||
Config.PlayerFunctionOverride = index
|
||||
end
|
||||
|
||||
---@param item string
|
||||
---@return string?
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function ESX.GetItemLabel(item)
|
||||
if ESX.Items[item] then
|
||||
return ESX.Items[item].label
|
||||
else
|
||||
print(("[^3WARNING^7] Attemting to get invalid Item -> ^5%s^7"):format(item))
|
||||
end
|
||||
end
|
||||
|
||||
---@param jobType string|string[]?
|
||||
---@return table
|
||||
function ESX.GetJobs(jobType)
|
||||
while not Core.JobsLoaded do
|
||||
Citizen.Wait(200)
|
||||
end
|
||||
|
||||
if not jobType then
|
||||
return ESX.Jobs
|
||||
end
|
||||
|
||||
jobType = type(jobType) == "string" and { jobType } or jobType
|
||||
|
||||
local jobTypeLookup = {}
|
||||
for i = 1, #jobType do
|
||||
jobTypeLookup[jobType[i]] = true
|
||||
end
|
||||
|
||||
local filteredJobs = {}
|
||||
for jobName, jobObject in pairs(ESX.Jobs) do
|
||||
if jobTypeLookup[jobObject.type] then
|
||||
filteredJobs[jobName] = jobObject
|
||||
end
|
||||
end
|
||||
|
||||
return filteredJobs
|
||||
end
|
||||
|
||||
---@return table
|
||||
function ESX.GetItems()
|
||||
return ESX.Items
|
||||
end
|
||||
|
||||
---@return table
|
||||
function ESX.GetUsableItems()
|
||||
local Usables = {}
|
||||
for k in pairs(Core.UsableItemsCallbacks) do
|
||||
Usables[k] = true
|
||||
end
|
||||
return Usables
|
||||
end
|
||||
|
||||
if not Config.CustomInventory then
|
||||
---@param itemType string
|
||||
---@param name string
|
||||
---@param count integer
|
||||
---@param label string
|
||||
---@param playerId number
|
||||
---@param components? string | table
|
||||
---@param tintIndex? integer
|
||||
---@param coords? table | vector3
|
||||
---@return nil
|
||||
function ESX.CreatePickup(itemType, name, count, label, playerId, components, tintIndex, coords)
|
||||
local pickupId = (Core.PickupId == 65635 and 0 or Core.PickupId + 1)
|
||||
local xPlayer = ESX.Players[playerId]
|
||||
coords = ((type(coords) == "vector3" or type(coords) == "vector4") and coords.xyz or xPlayer.getCoords(true))
|
||||
|
||||
Core.Pickups[pickupId] = { type = itemType, name = name, count = count, label = label, coords = coords }
|
||||
|
||||
if itemType == "item_weapon" then
|
||||
Core.Pickups[pickupId].components = components
|
||||
Core.Pickups[pickupId].tintIndex = tintIndex
|
||||
end
|
||||
|
||||
TriggerClientEvent("esx:createPickup", -1, pickupId, label, coords, itemType, name, components, tintIndex)
|
||||
Core.PickupId = pickupId
|
||||
end
|
||||
|
||||
local function refreshPlayerInventories()
|
||||
local xPlayers = ESX.GetExtendedPlayers()
|
||||
for i = 1, #xPlayers do
|
||||
local xPlayer = xPlayers[i]
|
||||
local minimalInv = xPlayer.getInventory(true)
|
||||
|
||||
for itemName, _ in pairs(minimalInv) do
|
||||
if not ESX.Items[itemName] then
|
||||
xPlayer.setInventoryItem(itemName, 0)
|
||||
minimalInv[itemName] = nil
|
||||
end
|
||||
end
|
||||
|
||||
xPlayer.inventory = {}
|
||||
local playerInvIndex = 1
|
||||
for itemName, itemData in pairs(ESX.Items) do
|
||||
xPlayer.inventory[playerInvIndex] = {
|
||||
name = itemName,
|
||||
count = minimalInv[itemName] or 0,
|
||||
label = itemData.label,
|
||||
weight = itemData.weight,
|
||||
usable = Core.UsableItemsCallbacks[itemName] ~= nil,
|
||||
rare = itemData.rare,
|
||||
canRemove = itemData.canRemove,
|
||||
}
|
||||
playerInvIndex += 1
|
||||
end
|
||||
|
||||
TriggerClientEvent("esx:setInventory", xPlayer.source, xPlayer.inventory)
|
||||
end
|
||||
end
|
||||
|
||||
---@return number newItemCount
|
||||
function ESX.RefreshItems()
|
||||
ESX.Items = {}
|
||||
|
||||
local items = MySQL.query.await("SELECT * FROM items")
|
||||
local itemCount = #items
|
||||
for i = 1, itemCount do
|
||||
local item = items[i]
|
||||
ESX.Items[item.name] = { label = item.label, weight = item.weight, rare = item.rare, canRemove = item
|
||||
.can_remove }
|
||||
end
|
||||
refreshPlayerInventories()
|
||||
|
||||
return itemCount
|
||||
end
|
||||
|
||||
---@param items { name: string, label: string, weight?: number, rare?: boolean, canRemove?: boolean }[]
|
||||
function ESX.AddItems(items)
|
||||
local toInsert = {}
|
||||
local toInsertIndex = 1
|
||||
|
||||
for i = 1, #items do
|
||||
local item = items[i]
|
||||
local name = item.name
|
||||
local label = item.label
|
||||
local weight = item.weight or 1
|
||||
local rare = item.rare or false
|
||||
local canRemove = item.canRemove ~= false
|
||||
|
||||
if type(name) ~= "string" then
|
||||
print(("^1[AddItems]^0 Invalid item name: %s"):format(name))
|
||||
goto continue
|
||||
end
|
||||
|
||||
if ESX.Items[name] then
|
||||
goto continue
|
||||
end
|
||||
|
||||
if type(label) ~= "string" then
|
||||
print(("^1[AddItems]^0 Invalid label for item '%s'"):format(name))
|
||||
goto continue
|
||||
end
|
||||
|
||||
if type(weight) ~= "number" then
|
||||
print(("^1[AddItems]^0 Invalid weight for item '%s'"):format(name))
|
||||
goto continue
|
||||
end
|
||||
|
||||
if type(rare) ~= "boolean" then
|
||||
print(("^1[AddItems]^0 Invalid rare flag for item '%s'"):format(name))
|
||||
goto continue
|
||||
end
|
||||
|
||||
if type(canRemove) ~= "boolean" then
|
||||
print(("^1[AddItems]^0 Invalid canRemove flag for item '%s'"):format(name))
|
||||
goto continue
|
||||
end
|
||||
|
||||
toInsert[toInsertIndex] = {
|
||||
name = name,
|
||||
label = label,
|
||||
weight = weight,
|
||||
rare = rare,
|
||||
canRemove = canRemove,
|
||||
}
|
||||
toInsertIndex += 1
|
||||
|
||||
::continue::
|
||||
end
|
||||
|
||||
if #toInsert > 0 then
|
||||
MySQL.prepare.await(
|
||||
"INSERT IGNORE INTO `items` (`name`, `label`, `weight`, `rare`, `can_remove`) VALUES (?, ?, ?, ?, ?)",
|
||||
toInsert)
|
||||
|
||||
for i = 1, #toInsert do
|
||||
local row = toInsert[i]
|
||||
ESX.Items[row.name] = {
|
||||
label = row.label,
|
||||
weight = row.weight,
|
||||
rare = row.rare,
|
||||
canRemove = row.canRemove,
|
||||
}
|
||||
end
|
||||
|
||||
refreshPlayerInventories()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param job string
|
||||
---@param grade string
|
||||
---@return boolean
|
||||
function ESX.DoesJobExist(job, grade)
|
||||
while not Core.JobsLoaded do
|
||||
Citizen.Wait(200)
|
||||
end
|
||||
|
||||
return (ESX.Jobs[job] and ESX.Jobs[job].grades[tostring(grade)] ~= nil) or false
|
||||
end
|
||||
|
||||
---@param playerSrc number
|
||||
---@return boolean
|
||||
function Core.IsPlayerAdmin(playerSrc)
|
||||
if type(playerSrc) ~= "number" then
|
||||
return false
|
||||
end
|
||||
|
||||
if IsPlayerAceAllowed(playerSrc --[[@as string]], "command") or GetConvar("sv_lan", "") == "true" then
|
||||
return true
|
||||
end
|
||||
|
||||
local xPlayer = ESX.GetPlayerFromId(playerSrc)
|
||||
return xPlayer and Config.AdminGroups[xPlayer.getGroup()] or false
|
||||
end
|
||||
|
||||
-- Generates a unique 9-digit SSN in dashed format (XXX-XX-XXXX).
|
||||
---@param skipUniqueCheck boolean?
|
||||
---@return string
|
||||
function Core.generateSSN(skipUniqueCheck)
|
||||
local reservedSSNs = {
|
||||
["078-05-1120"] = true,
|
||||
["219-09-9999"] = true,
|
||||
["123-45-6789"] = true
|
||||
}
|
||||
|
||||
while true do
|
||||
-- Generate the first part (area number)
|
||||
local area = math.random(1, 899)
|
||||
|
||||
-- 666 is never assigned
|
||||
if area == 666 then
|
||||
goto continue
|
||||
end
|
||||
|
||||
-- Generate the second part (group number)
|
||||
local group = math.random(1, 99)
|
||||
|
||||
-- Generate the last part (serial number)
|
||||
local serial = math.random(1, 9999)
|
||||
|
||||
-- Skip reserved SSN range (987-65-4320..4329)
|
||||
if area == 987 and group == 65 and serial >= 4320 and serial <= 4329 then
|
||||
goto continue
|
||||
end
|
||||
|
||||
local candidate = string.format("%03d-%02d-%04d", area, group, serial)
|
||||
|
||||
if reservedSSNs[candidate] then
|
||||
goto continue
|
||||
end
|
||||
|
||||
if skipUniqueCheck then
|
||||
return candidate
|
||||
end
|
||||
|
||||
local exists = MySQL.scalar.await("SELECT 1 FROM `users` WHERE `ssn` = ? LIMIT 1", { candidate })
|
||||
|
||||
if not exists then
|
||||
return candidate
|
||||
end
|
||||
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
|
||||
---@param owner string
|
||||
---@param plate string
|
||||
---@param coords vector4
|
||||
---@return CExtendedVehicle?
|
||||
function ESX.CreateExtendedVehicle(owner, plate, coords)
|
||||
return Core.vehicleClass.new(owner, plate, coords)
|
||||
end
|
||||
|
||||
---@param plate string
|
||||
---@return CExtendedVehicle?
|
||||
function ESX.GetExtendedVehicleFromPlate(plate)
|
||||
return Core.vehicleClass.getFromPlate(plate)
|
||||
end
|
||||
792
resources/[core]/es_extended/server/main.lua
Normal file
792
resources/[core]/es_extended/server/main.lua
Normal file
@@ -0,0 +1,792 @@
|
||||
SetMapName("San Andreas")
|
||||
SetGameType("ESX Legacy")
|
||||
|
||||
local oneSyncState = GetConvar("onesync", "off")
|
||||
local newPlayer = "INSERT INTO `users` SET `accounts` = ?, `identifier` = ?, `ssn` = ?, `group` = ?"
|
||||
local loadPlayer = "SELECT `accounts`, `ssn`, `job`, `job_grade`, `group`, `position`, `inventory`, `skin`, `loadout`, `metadata`"
|
||||
|
||||
if Config.Multichar then
|
||||
newPlayer = newPlayer .. ", `firstname` = ?, `lastname` = ?, `dateofbirth` = ?, `sex` = ?, `height` = ?"
|
||||
end
|
||||
|
||||
if Config.StartingInventoryItems then
|
||||
newPlayer = newPlayer .. ", `inventory` = ?"
|
||||
end
|
||||
|
||||
if Config.Multichar or Config.Identity then
|
||||
loadPlayer = loadPlayer .. ", `firstname`, `lastname`, `dateofbirth`, `sex`, `height`"
|
||||
end
|
||||
|
||||
loadPlayer = loadPlayer .. " FROM `users` WHERE identifier = ?"
|
||||
|
||||
local function createESXPlayer(identifier, playerId, data)
|
||||
local accounts = {}
|
||||
|
||||
for account, money in pairs(Config.StartingAccountMoney) do
|
||||
accounts[account] = money
|
||||
end
|
||||
|
||||
local defaultGroup = "user"
|
||||
if Core.IsPlayerAdmin(playerId) then
|
||||
print(("[^2INFO^0] Player ^5%s^0 Has been granted admin permissions via ^5Ace Perms^7."):format(playerId))
|
||||
defaultGroup = "admin"
|
||||
end
|
||||
local parameters = Config.Multichar and
|
||||
{ json.encode(accounts), identifier, Core.generateSSN(), defaultGroup, data.firstname, data.lastname, data.dateofbirth, data.sex, data.height }
|
||||
or { json.encode(accounts), identifier, Core.generateSSN(), defaultGroup }
|
||||
|
||||
if Config.StartingInventoryItems then
|
||||
table.insert(parameters, json.encode(Config.StartingInventoryItems))
|
||||
end
|
||||
|
||||
MySQL.prepare(newPlayer, parameters, function()
|
||||
loadESXPlayer(identifier, playerId, true)
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
local function onPlayerJoined(playerId)
|
||||
local identifier = ESX.GetIdentifier(playerId)
|
||||
if not identifier then
|
||||
return DropPlayer(playerId, "there was an error loading your character!\nError code: identifier-missing-ingame\n\nThe cause of this error is not known, your identifier could not be found. Please come back later or report this problem to the server administration team.")
|
||||
end
|
||||
|
||||
if ESX.GetPlayerFromIdentifier(identifier) then
|
||||
DropPlayer(
|
||||
playerId,
|
||||
("there was an error loading your character!\nError code: identifier-active-ingame\n\nThis error is caused by a player on this server who has the same identifier as you have. Make sure you are not playing on the same Rockstar account.\n\nYour Rockstar identifier: %s"):format(
|
||||
identifier
|
||||
)
|
||||
)
|
||||
else
|
||||
local result = MySQL.scalar.await("SELECT 1 FROM users WHERE identifier = ?", { identifier })
|
||||
if result then
|
||||
loadESXPlayer(identifier, playerId, false)
|
||||
else
|
||||
createESXPlayer(identifier, playerId)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param playerId number
|
||||
---@param reason string
|
||||
---@param cb function?
|
||||
local function onPlayerDropped(playerId, reason, cb)
|
||||
local p = not cb and promise:new()
|
||||
local function resolve()
|
||||
if cb then
|
||||
return cb()
|
||||
elseif(p) then
|
||||
return p:resolve()
|
||||
end
|
||||
end
|
||||
|
||||
local xPlayer = ESX.GetPlayerFromId(playerId)
|
||||
if not xPlayer then
|
||||
return resolve()
|
||||
end
|
||||
|
||||
TriggerEvent("esx:playerDropped", playerId, reason)
|
||||
local job = xPlayer.getJob().name
|
||||
local currentJob = Core.JobsPlayerCount[job]
|
||||
Core.JobsPlayerCount[job] = ((currentJob and currentJob > 0) and currentJob or 1) - 1
|
||||
|
||||
GlobalState[("%s:count"):format(job)] = Core.JobsPlayerCount[job]
|
||||
|
||||
Core.SavePlayer(xPlayer, function()
|
||||
GlobalState["playerCount"] = GlobalState["playerCount"] - 1
|
||||
ESX.Players[playerId] = nil
|
||||
Core.playersByIdentifier[xPlayer.identifier] = nil
|
||||
|
||||
resolve()
|
||||
end)
|
||||
|
||||
if p then
|
||||
return Citizen.Await(p)
|
||||
end
|
||||
end
|
||||
AddEventHandler("esx:onPlayerDropped", onPlayerDropped)
|
||||
|
||||
|
||||
if Config.Multichar then
|
||||
AddEventHandler("esx:onPlayerJoined", function(src, char, data)
|
||||
while not next(ESX.Jobs) do
|
||||
Wait(50)
|
||||
end
|
||||
|
||||
if not ESX.Players[src] then
|
||||
local identifier = char .. ":" .. ESX.GetIdentifier(src)
|
||||
if data then
|
||||
createESXPlayer(identifier, src, data)
|
||||
else
|
||||
loadESXPlayer(identifier, src, false)
|
||||
end
|
||||
end
|
||||
end)
|
||||
else
|
||||
RegisterNetEvent("esx:onPlayerJoined", function()
|
||||
local _source = source
|
||||
while not next(ESX.Jobs) do
|
||||
Wait(50)
|
||||
end
|
||||
|
||||
if not ESX.Players[_source] then
|
||||
onPlayerJoined(_source)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
if not Config.Multichar then
|
||||
AddEventHandler("playerConnecting", function(_, _, deferrals)
|
||||
local playerId = source
|
||||
deferrals.defer()
|
||||
Wait(0) -- Required
|
||||
local identifier
|
||||
local correctLicense, _ = pcall(function ()
|
||||
identifier = ESX.GetIdentifier(playerId)
|
||||
end)
|
||||
|
||||
-- luacheck: ignore
|
||||
if not SetEntityOrphanMode then
|
||||
return deferrals.done(("[ESX] ESX Requires a minimum Artifact version of 10188, Please update your server."))
|
||||
end
|
||||
|
||||
if oneSyncState == "off" or oneSyncState == "legacy" then
|
||||
return deferrals.done(("[ESX] ESX Requires Onesync Infinity to work. This server currently has Onesync set to: %s"):format(oneSyncState))
|
||||
end
|
||||
|
||||
if not Core.DatabaseConnected then
|
||||
return deferrals.done("[ESX] OxMySQL Was Unable To Connect to your database. Please make sure it is turned on and correctly configured in your server.cfg")
|
||||
end
|
||||
|
||||
if not identifier or not correctLicense then
|
||||
if GetResourceState("esx_identity") ~= "started" then
|
||||
return deferrals.done("[ESX] There was an error loading your character!\nError code: identifier-missing\n\nThe cause of this error is not known, your identifier could not be found. Please come back later or report this problem to the server administration team.")
|
||||
end
|
||||
end
|
||||
|
||||
local xPlayer = ESX.GetPlayerFromIdentifier(identifier)
|
||||
|
||||
if not xPlayer then
|
||||
return deferrals.done()
|
||||
end
|
||||
|
||||
if GetPlayerPing(xPlayer.source --[[@as string]]) > 0 then
|
||||
return deferrals.done(
|
||||
("[ESX] There was an error loading your character!\nError code: identifier-active\n\nThis error is caused by a player on this server who has the same identifier as you have. Make sure you are not playing on the same account.\n\nYour identifier: %s"):format(identifier)
|
||||
)
|
||||
end
|
||||
|
||||
deferrals.update(("[ESX] Cleaning stale player entry..."):format(identifier))
|
||||
onPlayerDropped(xPlayer.source, "esx_stale_player_obj")
|
||||
deferrals.done()
|
||||
end)
|
||||
end
|
||||
|
||||
function loadESXPlayer(identifier, playerId, isNew)
|
||||
local userData = {
|
||||
accounts = {},
|
||||
inventory = {},
|
||||
loadout = {},
|
||||
weight = 0,
|
||||
name = GetPlayerName(playerId),
|
||||
identifier = identifier,
|
||||
firstName = "John",
|
||||
lastName = "Doe",
|
||||
dateofbirth = "01/01/2000",
|
||||
height = 120,
|
||||
dead = false,
|
||||
}
|
||||
|
||||
local result = MySQL.prepare.await(loadPlayer, { identifier })
|
||||
|
||||
-- Accounts
|
||||
local accounts = result.accounts
|
||||
accounts = (accounts and accounts ~= "") and json.decode(accounts) or {}
|
||||
|
||||
for account, data in pairs(Config.Accounts) do
|
||||
data.round = data.round or data.round == nil
|
||||
|
||||
local index = #userData.accounts + 1
|
||||
userData.accounts[index] = {
|
||||
name = account,
|
||||
money = accounts[account] or Config.StartingAccountMoney[account] or 0,
|
||||
label = data.label,
|
||||
round = data.round,
|
||||
index = index,
|
||||
}
|
||||
end
|
||||
|
||||
-- SSN
|
||||
userData.ssn = result.ssn
|
||||
|
||||
-- Job
|
||||
local job, grade = result.job, tostring(result.job_grade)
|
||||
|
||||
if not ESX.DoesJobExist(job, grade) then
|
||||
print(("[^3WARNING^7] Ignoring invalid job for ^5%s^7 [job: ^5%s^7, grade: ^5%s^7]"):format(identifier, job, grade))
|
||||
job, grade = "unemployed", "0"
|
||||
end
|
||||
|
||||
local jobObject, gradeObject = ESX.Jobs[job], ESX.Jobs[job].grades[grade]
|
||||
|
||||
userData.job = {
|
||||
id = jobObject.id,
|
||||
name = jobObject.name,
|
||||
label = jobObject.label,
|
||||
type = jobObject.type,
|
||||
|
||||
grade = tonumber(grade),
|
||||
grade_name = gradeObject.name,
|
||||
grade_label = gradeObject.label,
|
||||
grade_salary = gradeObject.salary,
|
||||
|
||||
skin_male = gradeObject.skin_male and json.decode(gradeObject.skin_male) or {},
|
||||
skin_female = gradeObject.skin_female and json.decode(gradeObject.skin_female) or {},
|
||||
}
|
||||
|
||||
-- Inventory
|
||||
if not Config.CustomInventory then
|
||||
local inventory = (result.inventory and result.inventory ~= "") and json.decode(result.inventory) or {}
|
||||
|
||||
for name, item in pairs(ESX.Items) do
|
||||
local count = inventory[name] or 0
|
||||
userData.weight += (count * item.weight)
|
||||
|
||||
userData.inventory[#userData.inventory + 1] = {
|
||||
name = name,
|
||||
count = count,
|
||||
label = item.label,
|
||||
weight = item.weight,
|
||||
usable = Core.UsableItemsCallbacks[name] ~= nil,
|
||||
rare = item.rare,
|
||||
canRemove = item.canRemove,
|
||||
}
|
||||
end
|
||||
table.sort(userData.inventory, function(a, b)
|
||||
return a.label < b.label
|
||||
end)
|
||||
elseif result.inventory and result.inventory ~= "" then
|
||||
userData.inventory = json.decode(result.inventory)
|
||||
end
|
||||
|
||||
-- Group
|
||||
if result.group then
|
||||
if result.group == "superadmin" then
|
||||
userData.group = "admin"
|
||||
print("[^3WARNING^7] ^5Superadmin^7 detected, setting group to ^5admin^7")
|
||||
else
|
||||
userData.group = result.group
|
||||
end
|
||||
else
|
||||
userData.group = "user"
|
||||
end
|
||||
|
||||
-- Loadout
|
||||
if not Config.CustomInventory then
|
||||
if result.loadout and result.loadout ~= "" then
|
||||
|
||||
local loadout = json.decode(result.loadout)
|
||||
for name, weapon in pairs(loadout) do
|
||||
local label = ESX.GetWeaponLabel(name)
|
||||
|
||||
if label then
|
||||
userData.loadout[#userData.loadout + 1] = {
|
||||
name = name,
|
||||
ammo = weapon.ammo,
|
||||
label = label,
|
||||
components = weapon.components or {},
|
||||
tintIndex = weapon.tintIndex or 0,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Position
|
||||
userData.coords = json.decode(result.position) or Config.DefaultSpawns[ESX.Math.Random(1,#Config.DefaultSpawns)]
|
||||
|
||||
-- Skin
|
||||
userData.skin = (result.skin and result.skin ~= "") and json.decode(result.skin) or { sex = userData.sex == "f" and 1 or 0 }
|
||||
|
||||
-- Metadata
|
||||
userData.metadata = (result.metadata and result.metadata ~= "") and json.decode(result.metadata) or {}
|
||||
|
||||
-- xPlayer Creation
|
||||
local xPlayer = CreateExtendedPlayer(playerId, identifier, userData.ssn, userData.group, userData.accounts, userData.inventory, userData.weight, userData.job, userData.loadout, GetPlayerName(playerId), userData.coords, userData.metadata)
|
||||
|
||||
GlobalState["playerCount"] = GlobalState["playerCount"] + 1
|
||||
ESX.Players[playerId] = xPlayer
|
||||
Core.playersByIdentifier[identifier] = xPlayer
|
||||
|
||||
-- Identity
|
||||
if result.firstname and result.firstname ~= "" then
|
||||
userData.firstName = result.firstname
|
||||
userData.lastName = result.lastname
|
||||
|
||||
local name = ("%s %s"):format(result.firstname, result.lastname)
|
||||
userData.name = name
|
||||
|
||||
xPlayer.set("firstName", result.firstname)
|
||||
xPlayer.set("lastName", result.lastname)
|
||||
xPlayer.setName(name)
|
||||
|
||||
if result.dateofbirth then
|
||||
userData.dateofbirth = result.dateofbirth
|
||||
xPlayer.set("dateofbirth", result.dateofbirth)
|
||||
end
|
||||
if result.sex then
|
||||
userData.sex = result.sex
|
||||
xPlayer.set("sex", result.sex)
|
||||
end
|
||||
if result.height then
|
||||
userData.height = result.height
|
||||
xPlayer.set("height", result.height)
|
||||
end
|
||||
end
|
||||
|
||||
TriggerEvent("esx:playerLoaded", playerId, xPlayer, isNew)
|
||||
userData.money = xPlayer.getMoney()
|
||||
userData.maxWeight = xPlayer.getMaxWeight()
|
||||
userData.variables = xPlayer.variables or {}
|
||||
xPlayer.triggerEvent("esx:playerLoaded", userData, isNew, userData.skin)
|
||||
|
||||
if not Config.CustomInventory then
|
||||
xPlayer.triggerEvent("esx:createMissingPickups", Core.Pickups)
|
||||
elseif setPlayerInventory then
|
||||
setPlayerInventory(playerId, xPlayer, userData.inventory, isNew)
|
||||
end
|
||||
|
||||
xPlayer.triggerEvent("esx:registerSuggestions", Core.RegisteredCommands)
|
||||
print(('[^2INFO^0] Player ^5"%s"^0 has connected to the server. ID: ^5%s^7'):format(xPlayer.getName(), playerId))
|
||||
end
|
||||
|
||||
AddEventHandler("chatMessage", function(playerId, _, message)
|
||||
local xPlayer = ESX.GetPlayerFromId(playerId)
|
||||
if xPlayer and message:sub(1, 1) == "/" and playerId > 0 then
|
||||
CancelEvent()
|
||||
local commandName = message:sub(1):gmatch("%w+")()
|
||||
xPlayer.showNotification(TranslateCap("commanderror_invalidcommand", commandName))
|
||||
end
|
||||
end)
|
||||
|
||||
---@param reason string
|
||||
AddEventHandler("playerDropped", function(reason)
|
||||
onPlayerDropped(source --[[@as number]], reason)
|
||||
end)
|
||||
|
||||
AddEventHandler("esx:playerLoaded", function(_, xPlayer, isNew)
|
||||
local job = xPlayer.getJob().name
|
||||
local jobKey = ("%s:count"):format(job)
|
||||
|
||||
Core.JobsPlayerCount[job] = (Core.JobsPlayerCount[job] or 0) + 1
|
||||
GlobalState[jobKey] = Core.JobsPlayerCount[job]
|
||||
if isNew then
|
||||
Player(xPlayer.source).state:set('isNew', true, false)
|
||||
end
|
||||
end)
|
||||
|
||||
AddEventHandler("esx:setJob", function(_, job, lastJob)
|
||||
local lastJobKey = ("%s:count"):format(lastJob.name)
|
||||
local jobKey = ("%s:count"):format(job.name)
|
||||
local currentLastJob = Core.JobsPlayerCount[lastJob.name]
|
||||
|
||||
Core.JobsPlayerCount[lastJob.name] = ((currentLastJob and currentLastJob > 0) and currentLastJob or 1) - 1
|
||||
Core.JobsPlayerCount[job.name] = (Core.JobsPlayerCount[job.name] or 0) + 1
|
||||
|
||||
GlobalState[lastJobKey] = Core.JobsPlayerCount[lastJob.name]
|
||||
GlobalState[jobKey] = Core.JobsPlayerCount[job.name]
|
||||
end)
|
||||
|
||||
AddEventHandler("esx:playerLogout", function(playerId, cb)
|
||||
onPlayerDropped(playerId, "esx_player_logout", cb)
|
||||
TriggerClientEvent("esx:onPlayerLogout", playerId)
|
||||
end)
|
||||
|
||||
if not Config.CustomInventory then
|
||||
RegisterNetEvent("esx:updateWeaponAmmo", function(weaponName, ammoCount)
|
||||
local xPlayer = ESX.GetPlayerFromId(source)
|
||||
|
||||
if xPlayer then
|
||||
xPlayer.updateWeaponAmmo(weaponName, ammoCount)
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx:giveInventoryItem", function(target, itemType, itemName, itemCount)
|
||||
local playerId = source
|
||||
local sourceXPlayer = ESX.GetPlayerFromId(playerId)
|
||||
local targetXPlayer = ESX.GetPlayerFromId(target)
|
||||
local distance = #(GetEntityCoords(GetPlayerPed(playerId)) - GetEntityCoords(GetPlayerPed(target)))
|
||||
if not sourceXPlayer or not targetXPlayer or distance > Config.DistanceGive then
|
||||
print(("[^3WARNING^7] Player Detected Cheating: ^5%s^7"):format(GetPlayerName(playerId)))
|
||||
return
|
||||
end
|
||||
|
||||
if itemType == "item_standard" then
|
||||
local sourceItem = sourceXPlayer.getInventoryItem(itemName)
|
||||
|
||||
if not sourceItem then
|
||||
return
|
||||
end
|
||||
|
||||
if itemCount < 1 or sourceItem.count < itemCount then
|
||||
return sourceXPlayer.showNotification(TranslateCap("imp_invalid_quantity"))
|
||||
end
|
||||
|
||||
if not targetXPlayer.canCarryItem(itemName, itemCount) then
|
||||
return sourceXPlayer.showNotification(TranslateCap("ex_inv_lim", targetXPlayer.name))
|
||||
end
|
||||
|
||||
sourceXPlayer.removeInventoryItem(itemName, itemCount)
|
||||
targetXPlayer.addInventoryItem(itemName, itemCount)
|
||||
|
||||
sourceXPlayer.showNotification(TranslateCap("gave_item", itemCount, sourceItem.label, targetXPlayer.name))
|
||||
targetXPlayer.showNotification(TranslateCap("received_item", itemCount, sourceItem.label, sourceXPlayer.name))
|
||||
elseif itemType == "item_account" then
|
||||
if itemCount < 1 or sourceXPlayer.getAccount(itemName).money < itemCount then
|
||||
return sourceXPlayer.showNotification(TranslateCap("imp_invalid_amount"))
|
||||
end
|
||||
|
||||
sourceXPlayer.removeAccountMoney(itemName, itemCount, "Gave to " .. targetXPlayer.name)
|
||||
targetXPlayer.addAccountMoney(itemName, itemCount, "Received from " .. sourceXPlayer.name)
|
||||
|
||||
sourceXPlayer.showNotification(TranslateCap("gave_account_money", ESX.Math.GroupDigits(itemCount), Config.Accounts[itemName].label, targetXPlayer.name))
|
||||
targetXPlayer.showNotification(TranslateCap("received_account_money", ESX.Math.GroupDigits(itemCount), Config.Accounts[itemName].label, sourceXPlayer.name))
|
||||
elseif itemType == "item_weapon" then
|
||||
if not sourceXPlayer.hasWeapon(itemName) then
|
||||
return
|
||||
end
|
||||
|
||||
local weaponLabel = ESX.GetWeaponLabel(itemName)
|
||||
if targetXPlayer.hasWeapon(itemName) then
|
||||
sourceXPlayer.showNotification(TranslateCap("gave_weapon_hasalready", targetXPlayer.name, weaponLabel))
|
||||
targetXPlayer.showNotification(TranslateCap("received_weapon_hasalready", sourceXPlayer.name, weaponLabel))
|
||||
return
|
||||
end
|
||||
|
||||
local _, weapon = sourceXPlayer.getWeapon(itemName)
|
||||
if not weapon then
|
||||
return
|
||||
end
|
||||
|
||||
local _, weaponObject = ESX.GetWeapon(itemName)
|
||||
itemCount = weapon.ammo
|
||||
local weaponComponents = ESX.Table.Clone(weapon.components)
|
||||
local weaponTint = weapon.tintIndex
|
||||
|
||||
if weaponTint then
|
||||
targetXPlayer.setWeaponTint(itemName, weaponTint)
|
||||
end
|
||||
|
||||
if weaponComponents then
|
||||
for _, v in pairs(weaponComponents) do
|
||||
targetXPlayer.addWeaponComponent(itemName, v)
|
||||
end
|
||||
end
|
||||
|
||||
sourceXPlayer.removeWeapon(itemName)
|
||||
targetXPlayer.addWeapon(itemName, itemCount)
|
||||
|
||||
if weaponObject.ammo and itemCount > 0 then
|
||||
local ammoLabel = weaponObject.ammo.label
|
||||
sourceXPlayer.showNotification(TranslateCap("gave_weapon_withammo", weaponLabel, itemCount, ammoLabel, targetXPlayer.name))
|
||||
targetXPlayer.showNotification(TranslateCap("received_weapon_withammo", weaponLabel, itemCount, ammoLabel, sourceXPlayer.name))
|
||||
else
|
||||
sourceXPlayer.showNotification(TranslateCap("gave_weapon", weaponLabel, targetXPlayer.name))
|
||||
targetXPlayer.showNotification(TranslateCap("received_weapon", weaponLabel, sourceXPlayer.name))
|
||||
end
|
||||
elseif itemType == "item_ammo" then
|
||||
if not sourceXPlayer.hasWeapon(itemName) then
|
||||
return
|
||||
end
|
||||
|
||||
local _, weapon = sourceXPlayer.getWeapon(itemName)
|
||||
if not weapon then
|
||||
return
|
||||
end
|
||||
|
||||
if not targetXPlayer.hasWeapon(itemName) then
|
||||
sourceXPlayer.showNotification(TranslateCap("gave_weapon_noweapon", targetXPlayer.name))
|
||||
targetXPlayer.showNotification(TranslateCap("received_weapon_noweapon", sourceXPlayer.name, weapon.label))
|
||||
return
|
||||
end
|
||||
|
||||
local _, weaponObject = ESX.GetWeapon(itemName)
|
||||
|
||||
if not weaponObject.ammo then return end
|
||||
|
||||
local ammoLabel = weaponObject.ammo.label
|
||||
if weapon.ammo >= itemCount then
|
||||
sourceXPlayer.removeWeaponAmmo(itemName, itemCount)
|
||||
targetXPlayer.addWeaponAmmo(itemName, itemCount)
|
||||
|
||||
sourceXPlayer.showNotification(TranslateCap("gave_weapon_ammo", itemCount, ammoLabel, weapon.label, targetXPlayer.name))
|
||||
targetXPlayer.showNotification(TranslateCap("received_weapon_ammo", itemCount, ammoLabel, weapon.label, sourceXPlayer.name))
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx:removeInventoryItem", function(itemType, itemName, itemCount)
|
||||
local playerId = source
|
||||
local xPlayer = ESX.GetPlayerFromId(playerId)
|
||||
|
||||
if not xPlayer then
|
||||
return
|
||||
end
|
||||
|
||||
if itemType == "item_standard" then
|
||||
if not itemCount or itemCount < 1 then
|
||||
return xPlayer.showNotification(TranslateCap("imp_invalid_quantity"))
|
||||
end
|
||||
|
||||
local xItem = xPlayer.getInventoryItem(itemName)
|
||||
if not xItem then
|
||||
return
|
||||
end
|
||||
|
||||
if itemCount > xItem.count or xItem.count < 1 then
|
||||
return xPlayer.showNotification(TranslateCap("imp_invalid_quantity"))
|
||||
end
|
||||
|
||||
xPlayer.removeInventoryItem(itemName, itemCount)
|
||||
local pickupLabel = ("%s [%s]"):format(xItem.label, itemCount)
|
||||
ESX.CreatePickup("item_standard", itemName, itemCount, pickupLabel, playerId)
|
||||
xPlayer.showNotification(TranslateCap("threw_standard", itemCount, xItem.label))
|
||||
elseif itemType == "item_account" then
|
||||
if itemCount == nil or itemCount < 1 then
|
||||
return xPlayer.showNotification(TranslateCap("imp_invalid_amount"))
|
||||
end
|
||||
|
||||
local account = xPlayer.getAccount(itemName)
|
||||
if not account then
|
||||
return
|
||||
end
|
||||
|
||||
if itemCount > account.money or account.money < 1 then
|
||||
return xPlayer.showNotification(TranslateCap("imp_invalid_amount"))
|
||||
end
|
||||
|
||||
xPlayer.removeAccountMoney(itemName, itemCount, "Threw away")
|
||||
local pickupLabel = ("%s [%s]"):format(account.label, TranslateCap("locale_currency", ESX.Math.GroupDigits(itemCount)))
|
||||
ESX.CreatePickup("item_account", itemName, itemCount, pickupLabel, playerId)
|
||||
xPlayer.showNotification(TranslateCap("threw_account", ESX.Math.GroupDigits(itemCount), string.lower(account.label)))
|
||||
elseif itemType == "item_weapon" then
|
||||
itemName = string.upper(itemName)
|
||||
|
||||
if not xPlayer.hasWeapon(itemName) then return end
|
||||
|
||||
local _, weapon = xPlayer.getWeapon(itemName)
|
||||
if not weapon then
|
||||
return
|
||||
end
|
||||
|
||||
local _, weaponObject = ESX.GetWeapon(itemName)
|
||||
-- luacheck: ignore weaponPickupLabel
|
||||
local weaponPickupLabel = ""
|
||||
local components = ESX.Table.Clone(weapon.components)
|
||||
xPlayer.removeWeapon(itemName)
|
||||
|
||||
if weaponObject.ammo and weapon.ammo > 0 then
|
||||
local ammoLabel = weaponObject.ammo.label
|
||||
weaponPickupLabel = ("%s [%s %s]"):format(weapon.label, weapon.ammo, ammoLabel)
|
||||
xPlayer.showNotification(TranslateCap("threw_weapon_ammo", weapon.label, weapon.ammo, ammoLabel))
|
||||
else
|
||||
weaponPickupLabel = ("%s"):format(weapon.label)
|
||||
xPlayer.showNotification(TranslateCap("threw_weapon", weapon.label))
|
||||
end
|
||||
|
||||
ESX.CreatePickup("item_weapon", itemName, weapon.ammo, weaponPickupLabel, playerId, components, weapon.tintIndex)
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx:useItem", function(itemName)
|
||||
local source = source
|
||||
local xPlayer = ESX.GetPlayerFromId(source)
|
||||
|
||||
if not xPlayer then
|
||||
return
|
||||
end
|
||||
|
||||
local count = xPlayer.getInventoryItem(itemName).count
|
||||
|
||||
if count < 1 then
|
||||
return xPlayer.showNotification(TranslateCap("act_imp"))
|
||||
end
|
||||
|
||||
ESX.UseItem(source, itemName)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx:onPickup", function(pickupId)
|
||||
local pickup, xPlayer, success = Core.Pickups[pickupId], ESX.GetPlayerFromId(source)
|
||||
|
||||
if not xPlayer then
|
||||
return
|
||||
end
|
||||
|
||||
if not pickup then return end
|
||||
|
||||
local playerPickupDistance = #(pickup.coords - xPlayer.getCoords(true))
|
||||
if playerPickupDistance > 5.0 then
|
||||
print(("[^3WARNING^7] Player Detected Cheating (Out of range pickup): ^5%s^7"):format(xPlayer.getIdentifier()))
|
||||
return
|
||||
end
|
||||
|
||||
if pickup.type == "item_standard" then
|
||||
if not xPlayer.canCarryItem(pickup.name, pickup.count) then
|
||||
return xPlayer.showNotification(TranslateCap("threw_cannot_pickup"))
|
||||
end
|
||||
|
||||
xPlayer.addInventoryItem(pickup.name, pickup.count)
|
||||
success = true
|
||||
elseif pickup.type == "item_account" then
|
||||
success = true
|
||||
xPlayer.addAccountMoney(pickup.name, pickup.count, "Picked up")
|
||||
elseif pickup.type == "item_weapon" then
|
||||
if xPlayer.hasWeapon(pickup.name) then
|
||||
return xPlayer.showNotification(TranslateCap("threw_weapon_already"))
|
||||
end
|
||||
|
||||
success = true
|
||||
xPlayer.addWeapon(pickup.name, pickup.count)
|
||||
xPlayer.setWeaponTint(pickup.name, pickup.tintIndex)
|
||||
|
||||
for _, v in ipairs(pickup.components) do
|
||||
xPlayer.addWeaponComponent(pickup.name, v)
|
||||
end
|
||||
end
|
||||
|
||||
if success then
|
||||
Core.Pickups[pickupId] = nil
|
||||
TriggerClientEvent("esx:removePickup", -1, pickupId)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
ESX.RegisterServerCallback("esx:getPlayerData", function(source, cb)
|
||||
local xPlayer = ESX.GetPlayerFromId(source)
|
||||
|
||||
if not xPlayer then
|
||||
return
|
||||
end
|
||||
|
||||
cb({
|
||||
identifier = xPlayer.identifier,
|
||||
accounts = xPlayer.getAccounts(),
|
||||
inventory = xPlayer.getInventory(),
|
||||
job = xPlayer.getJob(),
|
||||
loadout = xPlayer.getLoadout(),
|
||||
money = xPlayer.getMoney(),
|
||||
position = xPlayer.getCoords(true),
|
||||
metadata = xPlayer.getMeta(),
|
||||
})
|
||||
end)
|
||||
|
||||
ESX.RegisterServerCallback("esx:isUserAdmin", function(source, cb)
|
||||
cb(Core.IsPlayerAdmin(source))
|
||||
end)
|
||||
|
||||
ESX.RegisterServerCallback("esx:getGameBuild", function(_, cb)
|
||||
cb(tonumber(GetConvar("sv_enforceGameBuild", "1604")))
|
||||
end)
|
||||
|
||||
ESX.RegisterServerCallback("esx:getOtherPlayerData", function(_, cb, target)
|
||||
local xPlayer = ESX.GetPlayerFromId(target)
|
||||
|
||||
if not xPlayer then
|
||||
return
|
||||
end
|
||||
|
||||
cb({
|
||||
identifier = xPlayer.identifier,
|
||||
accounts = xPlayer.getAccounts(),
|
||||
inventory = xPlayer.getInventory(),
|
||||
job = xPlayer.getJob(),
|
||||
loadout = xPlayer.getLoadout(),
|
||||
money = xPlayer.getMoney(),
|
||||
position = xPlayer.getCoords(true),
|
||||
metadata = xPlayer.getMeta(),
|
||||
})
|
||||
end)
|
||||
|
||||
ESX.RegisterServerCallback("esx:getPlayerNames", function(source, cb, players)
|
||||
players[source] = nil
|
||||
|
||||
for playerId, _ in pairs(players) do
|
||||
local xPlayer = ESX.GetPlayerFromId(playerId)
|
||||
|
||||
if xPlayer then
|
||||
players[playerId] = xPlayer.getName()
|
||||
else
|
||||
players[playerId] = nil
|
||||
end
|
||||
end
|
||||
|
||||
cb(players)
|
||||
end)
|
||||
|
||||
ESX.RegisterServerCallback("esx:spawnVehicle", function(source, cb, vehData)
|
||||
local ped = GetPlayerPed(source)
|
||||
ESX.OneSync.SpawnVehicle(vehData.model or `ADDER`, vehData.coords or GetEntityCoords(ped), vehData.coords.w or 0.0, vehData.props or {}, function(id)
|
||||
if vehData.warp then
|
||||
local vehicle = NetworkGetEntityFromNetworkId(id)
|
||||
local timeout = 0
|
||||
while GetVehiclePedIsIn(ped, false) ~= vehicle and timeout <= 15 do
|
||||
Wait(0)
|
||||
TaskWarpPedIntoVehicle(ped, vehicle, -1)
|
||||
timeout += 1
|
||||
end
|
||||
end
|
||||
cb(id)
|
||||
end)
|
||||
end)
|
||||
|
||||
AddEventHandler("txAdmin:events:scheduledRestart", function(eventData)
|
||||
if eventData.secondsRemaining == 60 then
|
||||
CreateThread(function()
|
||||
Wait(50000)
|
||||
Core.SavePlayers()
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
AddEventHandler("txAdmin:events:serverShuttingDown", function()
|
||||
Core.SavePlayers()
|
||||
end)
|
||||
|
||||
local DoNotUse = {
|
||||
["essentialmode"] = true,
|
||||
["es_admin2"] = true,
|
||||
["basic-gamemode"] = true,
|
||||
["mapmanager"] = true,
|
||||
["fivem-map-skater"] = true,
|
||||
["fivem-map-hipster"] = true,
|
||||
["qb-core"] = true,
|
||||
["default_spawnpoint"] = true,
|
||||
}
|
||||
|
||||
AddEventHandler("onResourceStart", function(key)
|
||||
if DoNotUse[string.lower(key)] then
|
||||
while GetResourceState(key) ~= "started" do
|
||||
Wait(0)
|
||||
end
|
||||
|
||||
StopResource(key)
|
||||
error(("WE STOPPED A RESOURCE THAT WILL BREAK ^1ESX^1, PLEASE REMOVE ^5%s^1"):format(key))
|
||||
end
|
||||
-- luacheck: ignore
|
||||
if not SetEntityOrphanMode then
|
||||
CreateThread(function()
|
||||
while true do
|
||||
error("ESX Requires a minimum Artifact version of 10188, Please update your server.")
|
||||
Wait(60 * 1000)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
for key in pairs(DoNotUse) do
|
||||
if GetResourceState(key) == "started" or GetResourceState(key) == "starting" then
|
||||
StopResource(key)
|
||||
error(("WE STOPPED A RESOURCE THAT WILL BREAK ^1ESX^1, PLEASE REMOVE ^5%s^1"):format(key))
|
||||
end
|
||||
end
|
||||
46
resources/[core]/es_extended/server/migration/main.lua
Normal file
46
resources/[core]/es_extended/server/migration/main.lua
Normal file
@@ -0,0 +1,46 @@
|
||||
RegisterCommand("resetmigrations", function(src)
|
||||
if src > 0 then
|
||||
print("^1[ERROR]^7 This command can only be run from the server console.")
|
||||
return
|
||||
end
|
||||
|
||||
for version, _ in pairs(Core.Migrations or {}) do
|
||||
DeleteResourceKvp(("esx_migration:%s"):format(version))
|
||||
end
|
||||
print("^2[SUCCESS]^7 Reset all migrations. This will re-run all migrations on the next server start.")
|
||||
end)
|
||||
|
||||
local migrationsRan = 0
|
||||
local restartRequired = false
|
||||
|
||||
for esxVersion, migrations in pairs(Core.Migrations or {}) do
|
||||
---@cast esxVersion string
|
||||
---@cast migrations table<string, function>
|
||||
|
||||
if ESX.Table.SizeOf(migrations) > 0 then
|
||||
print(("^4[INFO]^7 Running migrations for ESX version %s"):format(esxVersion))
|
||||
|
||||
for migrationName, migration in pairs(migrations) do
|
||||
local success, result = pcall(migration)
|
||||
if not success then
|
||||
local err = result --[[@as string]]
|
||||
error(("^1[ERROR]^7 Failed migration ^4['%s.%s']^7: %s"):format(esxVersion, migrationName, err))
|
||||
end
|
||||
|
||||
local migrationRequiresRestart = result --[[@as boolean]]
|
||||
|
||||
if not restartRequired and migrationRequiresRestart then
|
||||
restartRequired = true
|
||||
end
|
||||
end
|
||||
|
||||
SetResourceKvpInt(("esx_migration:%s"):format(esxVersion), 1)
|
||||
print(("^2[SUCCESS]^7 Successfully completed migrations for ESX version %s"):format(esxVersion))
|
||||
migrationsRan += 1
|
||||
end
|
||||
end
|
||||
|
||||
while restartRequired do
|
||||
print(("^4[INFO]^7 Ran migrations for %d ESX version(s). ^1Server restart required!^7"):format(migrationsRan))
|
||||
Wait(500)
|
||||
end
|
||||
@@ -0,0 +1,70 @@
|
||||
local esxVersion = "v1.13.3"
|
||||
|
||||
Core.Migrations = Core.Migrations or {}
|
||||
Core.Migrations[esxVersion] = Core.Migrations[esxVersion] or {}
|
||||
|
||||
if GetResourceKvpInt(("esx_migration:%s"):format(esxVersion)) == 1 then
|
||||
return
|
||||
end
|
||||
|
||||
---@return boolean restartRequired
|
||||
Core.Migrations[esxVersion].ssn = function()
|
||||
print("^4[esx_migration:v.1.13.3:ssn]^7 Adding SSN column to users table.")
|
||||
local col = MySQL.scalar.await([[
|
||||
SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'users'
|
||||
AND COLUMN_NAME = 'ssn'
|
||||
]])
|
||||
|
||||
local idx = MySQL.scalar.await([[
|
||||
SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'users'
|
||||
AND INDEX_NAME = 'unique_ssn'
|
||||
]])
|
||||
|
||||
if col == 0 and idx == 0 then
|
||||
MySQL.update.await([[
|
||||
ALTER TABLE `users`
|
||||
ADD COLUMN `ssn` VARCHAR(11) NULL DEFAULT NULL AFTER `identifier`,
|
||||
ADD UNIQUE KEY `unique_ssn` (`ssn`)
|
||||
]])
|
||||
elseif col == 0 then
|
||||
MySQL.update.await("ALTER TABLE `users` ADD COLUMN `ssn` VARCHAR(11) NULL DEFAULT NULL AFTER `identifier`")
|
||||
elseif idx == 0 then
|
||||
MySQL.update.await("ALTER TABLE `users` ADD UNIQUE KEY `unique_ssn` (`ssn`)")
|
||||
end
|
||||
|
||||
|
||||
local Result = MySQL.query.await("SELECT `identifier` FROM `users` WHERE `ssn` IS NULL")
|
||||
if #Result == 0 then
|
||||
print("^4[esx_migration:v.1.13.3:ssn]^7 No users found without SSN, migration not needed.")
|
||||
return false
|
||||
end
|
||||
|
||||
print("^4[esx_migration:v.1.13.3:ssn]^7 Generating SSN for existing users.")
|
||||
local GeneratedSSNs = {}
|
||||
local Parameters = {}
|
||||
for i = 1, #Result do
|
||||
local ssn
|
||||
repeat
|
||||
ssn = Core.generateSSN(true)
|
||||
until not GeneratedSSNs[ssn]
|
||||
|
||||
GeneratedSSNs[ssn] = true
|
||||
Parameters[i] = { ssn, Result[i].identifier }
|
||||
end
|
||||
|
||||
print("^4[esx_migration:v.1.13.3:ssn]^7 Updating users with generated SSN. This may take a minute...")
|
||||
MySQL.prepare.await("UPDATE `users` SET `ssn` = ? WHERE `identifier` = ?", Parameters)
|
||||
|
||||
print("^4[esx_migration:v.1.13.3:ssn]^7 Removing SSN default value.")
|
||||
MySQL.update.await("ALTER TABLE `users` MODIFY `ssn` VARCHAR(11) NOT NULL")
|
||||
|
||||
print(("^4[esx_migration:v.1.13.3:ssn]^7 Successfully migrated %d users."):format(#Parameters))
|
||||
|
||||
return true
|
||||
end
|
||||
@@ -0,0 +1,35 @@
|
||||
local esxVersion = "v1.13.5"
|
||||
|
||||
Core.Migrations = Core.Migrations or {}
|
||||
Core.Migrations[esxVersion] = Core.Migrations[esxVersion] or {}
|
||||
|
||||
if GetResourceKvpInt(("esx_migration:%s"):format(esxVersion)) == 1 then
|
||||
return
|
||||
end
|
||||
|
||||
---@return boolean restartRequired
|
||||
Core.Migrations[esxVersion].jobTypes = function()
|
||||
print("^4[esx_migration:v1.13.5:jobTypes]^7 Adding job type column to jobs table.")
|
||||
|
||||
local col = MySQL.scalar.await([[
|
||||
SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'jobs'
|
||||
AND COLUMN_NAME = 'type'
|
||||
]])
|
||||
|
||||
if col == 0 then
|
||||
print("^4[esx_migration:v1.13.5:jobTypes]^7 Column not found, altering jobs table.")
|
||||
MySQL.update.await([[
|
||||
ALTER TABLE `jobs`
|
||||
ADD COLUMN `type` VARCHAR(50) NOT NULL DEFAULT 'civ' AFTER `label`
|
||||
]])
|
||||
else
|
||||
print("^4[esx_migration:v1.13.5:jobTypes]^7 Column already exists, migration not needed.")
|
||||
return false
|
||||
end
|
||||
|
||||
print("^4[esx_migration:v1.13.5:jobTypes]^7 Migration complete.")
|
||||
return true
|
||||
end
|
||||
155
resources/[core]/es_extended/server/modules/callback.lua
Normal file
155
resources/[core]/es_extended/server/modules/callback.lua
Normal file
@@ -0,0 +1,155 @@
|
||||
---@diagnostic disable: duplicate-set-field
|
||||
|
||||
-- =============================================
|
||||
-- MARK: Variables
|
||||
-- =============================================
|
||||
|
||||
Callbacks = {}
|
||||
|
||||
Callbacks.requests = {}
|
||||
Callbacks.storage = {}
|
||||
Callbacks.id = 0
|
||||
|
||||
-- =============================================
|
||||
-- MARK: Internal Functions
|
||||
-- =============================================
|
||||
|
||||
function Callbacks:Register(name, resource, cb)
|
||||
self.storage[name] = {
|
||||
resource = resource,
|
||||
cb = cb
|
||||
}
|
||||
end
|
||||
|
||||
function Callbacks:Execute(cb, ...)
|
||||
local success, errorString = pcall(cb, ...)
|
||||
|
||||
if not success then
|
||||
print(("[^1ERROR^7] Failed to execute Callback with RequestId: ^5%s^7"):format(self.currentId))
|
||||
print("^3Callback Error:^7 " .. tostring(errorString)) -- just log, don't throw
|
||||
self.currentId = nil
|
||||
return
|
||||
end
|
||||
|
||||
self.currentId = nil
|
||||
end
|
||||
|
||||
function Callbacks:Trigger(player, event, cb, invoker, ...)
|
||||
self.requests[self.id] = {
|
||||
await = type(cb) == "boolean",
|
||||
cb = cb or promise:new()
|
||||
}
|
||||
local table = self.requests[self.id]
|
||||
|
||||
TriggerClientEvent("esx:triggerClientCallback", player, event, self.id, invoker, ...)
|
||||
|
||||
self.id += 1
|
||||
|
||||
return table.cb
|
||||
end
|
||||
|
||||
function Callbacks:ServerRecieve(player, event, requestId, invoker, ...)
|
||||
self.currentId = requestId
|
||||
|
||||
if not self.storage[event] then
|
||||
return error(("Server Callback with requestId ^5%s^1 Was Called by ^5%s^1 but does not exist."):format(event, invoker))
|
||||
end
|
||||
|
||||
local returnCb = function(...)
|
||||
TriggerClientEvent("esx:serverCallback", player, requestId, invoker, ...)
|
||||
end
|
||||
local callback = self.storage[event].cb
|
||||
|
||||
self:Execute(callback, player, returnCb, ...)
|
||||
end
|
||||
|
||||
function Callbacks:RecieveClient(requestId, invoker, ...)
|
||||
self.currentId = requestId
|
||||
|
||||
if not self.requests[self.currentId] then
|
||||
return error(("Client Callback with requestId ^5%s^1 Was Called by ^5%s^1 but does not exist."):format(self.currentId, invoker))
|
||||
end
|
||||
|
||||
local callback = self.requests[self.currentId]
|
||||
|
||||
self.requests[requestId] = nil
|
||||
if callback.await then
|
||||
callback.cb:resolve({ ... })
|
||||
else
|
||||
self:Execute(callback.cb, ...)
|
||||
end
|
||||
end
|
||||
|
||||
-- =============================================
|
||||
-- MARK: ESX Functions
|
||||
-- =============================================
|
||||
|
||||
---@param player number playerId
|
||||
---@param eventName string
|
||||
---@param callback function
|
||||
---@param ... any
|
||||
function ESX.TriggerClientCallback(player, eventName, callback, ...)
|
||||
local invokingResource = GetInvokingResource()
|
||||
local invoker = (invokingResource and invokingResource ~= "Unknown") and invokingResource or "es_extended"
|
||||
|
||||
Callbacks:Trigger(player, eventName, callback, invoker, ...)
|
||||
end
|
||||
|
||||
---@param player number playerId
|
||||
---@param eventName string
|
||||
---@param ... any
|
||||
---@return ...
|
||||
function ESX.AwaitClientCallback(player, eventName, ...)
|
||||
local invokingResource = GetInvokingResource()
|
||||
local invoker = (invokingResource and invokingResource ~= "Unknown") and invokingResource or "es_extended"
|
||||
|
||||
local p = Callbacks:Trigger(player, eventName, false, invoker, ...)
|
||||
if not p then return end
|
||||
|
||||
SetTimeout(15000, function()
|
||||
if p.state == "pending" then
|
||||
p:reject("Server Callback Timed Out")
|
||||
end
|
||||
end)
|
||||
|
||||
Citizen.Await(p)
|
||||
|
||||
return table.unpack(p.value)
|
||||
end
|
||||
|
||||
---@param eventName string
|
||||
---@param callback function
|
||||
---@return nil
|
||||
function ESX.RegisterServerCallback(eventName, callback)
|
||||
local invokingResource = GetInvokingResource()
|
||||
local invoker = (invokingResource and invokingResource ~= "Unknown") and invokingResource or "es_extended"
|
||||
|
||||
Callbacks:Register(eventName, invoker, callback)
|
||||
end
|
||||
|
||||
---@param eventName string
|
||||
---@return boolean
|
||||
function ESX.DoesServerCallbackExist(eventName)
|
||||
return Callbacks.storage[eventName] ~= nil
|
||||
end
|
||||
|
||||
-- =============================================
|
||||
-- MARK: Events
|
||||
-- =============================================
|
||||
|
||||
RegisterNetEvent("esx:clientCallback", function(requestId, invoker, ...)
|
||||
Callbacks:RecieveClient(requestId, invoker, ...)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx:triggerServerCallback", function(eventName, requestId, invoker, ...)
|
||||
local source = source
|
||||
Callbacks:ServerRecieve(source, eventName, requestId, invoker, ...)
|
||||
end)
|
||||
|
||||
AddEventHandler("onResourceStop", function(resource)
|
||||
for k, v in pairs(Callbacks.storage) do
|
||||
if v.resource == resource then
|
||||
Callbacks.storage[k] = nil
|
||||
end
|
||||
end
|
||||
end)
|
||||
774
resources/[core]/es_extended/server/modules/commands.lua
Normal file
774
resources/[core]/es_extended/server/modules/commands.lua
Normal file
@@ -0,0 +1,774 @@
|
||||
ESX.RegisterCommand(
|
||||
{ "setcoords", "tp" },
|
||||
"admin",
|
||||
function(xPlayer, args)
|
||||
xPlayer.setCoords({ x = args.x, y = args.y, z = args.z })
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "Set Coordinates /setcoords Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
{ name = "X Coord", value = args.x, inline = true },
|
||||
{ name = "Y Coord", value = args.y, inline = true },
|
||||
{ name = "Z Coord", value = args.z, inline = true },
|
||||
})
|
||||
end
|
||||
end,
|
||||
false,
|
||||
{
|
||||
help = TranslateCap("command_setcoords"),
|
||||
validate = true,
|
||||
arguments = {
|
||||
{ name = "x", help = TranslateCap("command_setcoords_x"), type = "coordinate" },
|
||||
{ name = "y", help = TranslateCap("command_setcoords_y"), type = "coordinate" },
|
||||
{ name = "z", help = TranslateCap("command_setcoords_z"), type = "coordinate" },
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
ESX.RegisterCommand(
|
||||
"setjob",
|
||||
"admin",
|
||||
function(xPlayer, args, showError)
|
||||
if not ESX.DoesJobExist(args.job, args.grade) then
|
||||
return showError(TranslateCap("command_setjob_invalid"))
|
||||
end
|
||||
|
||||
args.playerId.setJob(args.job, args.grade)
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "Set Job /setjob Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
{ name = "Target", value = args.playerId.name, inline = true },
|
||||
{ name = "Job", value = args.job, inline = true },
|
||||
{ name = "Grade", value = args.grade, inline = true },
|
||||
})
|
||||
end
|
||||
end,
|
||||
true,
|
||||
{
|
||||
help = TranslateCap("command_setjob"),
|
||||
validate = true,
|
||||
arguments = {
|
||||
{ name = "playerId", help = TranslateCap("commandgeneric_playerid"), type = "player" },
|
||||
{ name = "job", help = TranslateCap("command_setjob_job"), type = "string" },
|
||||
{ name = "grade", help = TranslateCap("command_setjob_grade"), type = "number" },
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
local upgrades = Config.SpawnVehMaxUpgrades and {
|
||||
plate = "ADMINCAR",
|
||||
modEngine = 3,
|
||||
modBrakes = 2,
|
||||
modTransmission = 2,
|
||||
modSuspension = 3,
|
||||
modArmor = true,
|
||||
windowTint = 1,
|
||||
} or {}
|
||||
|
||||
ESX.RegisterCommand(
|
||||
"car",
|
||||
"admin",
|
||||
function(xPlayer, args, showError)
|
||||
if not xPlayer then
|
||||
return showError("[^1ERROR^7] The xPlayer value is nil")
|
||||
end
|
||||
|
||||
local playerPed = GetPlayerPed(xPlayer.source)
|
||||
local playerCoords = GetEntityCoords(playerPed)
|
||||
local playerHeading = GetEntityHeading(playerPed)
|
||||
local playerVehicle = GetVehiclePedIsIn(playerPed, false)
|
||||
|
||||
if not args.car or type(args.car) ~= "string" then
|
||||
args.car = "adder"
|
||||
end
|
||||
|
||||
if playerVehicle then
|
||||
DeleteEntity(playerVehicle)
|
||||
end
|
||||
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "Spawn Car /car Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
{ name = "Vehicle", value = args.car, inline = true },
|
||||
})
|
||||
end
|
||||
|
||||
local xRoutingBucket = GetPlayerRoutingBucket(xPlayer.source)
|
||||
|
||||
ESX.OneSync.SpawnVehicle(args.car, playerCoords, playerHeading, upgrades, function(networkId)
|
||||
if networkId then
|
||||
local vehicle = NetworkGetEntityFromNetworkId(networkId)
|
||||
|
||||
if xRoutingBucket ~= 0 then
|
||||
SetEntityRoutingBucket(vehicle, xRoutingBucket)
|
||||
end
|
||||
|
||||
for _ = 1, 100 do
|
||||
Wait(0)
|
||||
SetPedIntoVehicle(playerPed, vehicle, -1)
|
||||
|
||||
if GetVehiclePedIsIn(playerPed, false) == vehicle then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if GetVehiclePedIsIn(playerPed, false) ~= vehicle then
|
||||
showError("[^1ERROR^7] The player could not be seated in the vehicle")
|
||||
end
|
||||
end
|
||||
end)
|
||||
end,
|
||||
false,
|
||||
{
|
||||
help = TranslateCap("command_car"),
|
||||
validate = false,
|
||||
arguments = {
|
||||
{ name = "car", validate = false, help = TranslateCap("command_car_car"), type = "string" },
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
ESX.RegisterCommand(
|
||||
{ "cardel", "dv" },
|
||||
"admin",
|
||||
function(xPlayer, args)
|
||||
local ped = GetPlayerPed(xPlayer.source)
|
||||
local pedVehicle = GetVehiclePedIsIn(ped, false)
|
||||
|
||||
if DoesEntityExist(pedVehicle) then
|
||||
DeleteEntity(pedVehicle)
|
||||
end
|
||||
|
||||
local coords = GetEntityCoords(ped)
|
||||
local Vehicles = ESX.OneSync.GetVehiclesInArea(coords, tonumber(args.radius) or 5.0)
|
||||
for i = 1, #Vehicles do
|
||||
local Vehicle = NetworkGetEntityFromNetworkId(Vehicles[i])
|
||||
if DoesEntityExist(Vehicle) then
|
||||
DeleteEntity(Vehicle)
|
||||
end
|
||||
end
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "Delete Vehicle /dv Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
})
|
||||
end
|
||||
end,
|
||||
false,
|
||||
{
|
||||
help = TranslateCap("command_cardel"),
|
||||
validate = false,
|
||||
arguments = {
|
||||
{ name = "radius", validate = false, help = TranslateCap("command_cardel_radius"), type = "number" },
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
ESX.RegisterCommand(
|
||||
{ "fix", "repair" },
|
||||
"admin",
|
||||
function(xPlayer, args, showError)
|
||||
local xTarget = args.playerId
|
||||
local ped = GetPlayerPed(xTarget.source)
|
||||
local pedVehicle = GetVehiclePedIsIn(ped, false)
|
||||
if not pedVehicle or GetPedInVehicleSeat(pedVehicle, -1) ~= ped then
|
||||
showError(TranslateCap("not_in_vehicle"))
|
||||
return
|
||||
end
|
||||
xTarget.triggerEvent("esx:repairPedVehicle")
|
||||
xPlayer.showNotification(TranslateCap("command_repair_success"), true, false, 140)
|
||||
if xPlayer.source ~= xTarget.source then
|
||||
xTarget.showNotification(TranslateCap("command_repair_success_target"), true, false, 140)
|
||||
end
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "Fix Vehicle /fix Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
{ name = "Target", value = xTarget.name, inline = true },
|
||||
})
|
||||
end
|
||||
end,
|
||||
true,
|
||||
{
|
||||
help = TranslateCap("command_repair"),
|
||||
validate = false,
|
||||
arguments = {
|
||||
{ name = "playerId", help = TranslateCap("commandgeneric_playerid"), type = "player" },
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
ESX.RegisterCommand(
|
||||
"setaccountmoney",
|
||||
"admin",
|
||||
function(xPlayer, args, showError)
|
||||
if not args.playerId.getAccount(args.account) then
|
||||
return showError(TranslateCap("command_giveaccountmoney_invalid"))
|
||||
end
|
||||
args.playerId.setAccountMoney(args.account, args.amount, "Government Grant")
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "Set Account Money /setaccountmoney Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
{ name = "Target", value = args.playerId.name, inline = true },
|
||||
{ name = "Account", value = args.account, inline = true },
|
||||
{ name = "Amount", value = args.amount, inline = true },
|
||||
})
|
||||
end
|
||||
end,
|
||||
true,
|
||||
{
|
||||
help = TranslateCap("command_setaccountmoney"),
|
||||
validate = true,
|
||||
arguments = {
|
||||
{ name = "playerId", help = TranslateCap("commandgeneric_playerid"), type = "player" },
|
||||
{ name = "account", help = TranslateCap("command_giveaccountmoney_account"), type = "string" },
|
||||
{ name = "amount", help = TranslateCap("command_setaccountmoney_amount"), type = "number" },
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
ESX.RegisterCommand(
|
||||
"giveaccountmoney",
|
||||
"admin",
|
||||
function(xPlayer, args, showError)
|
||||
if not args.playerId.getAccount(args.account) then
|
||||
return showError(TranslateCap("command_giveaccountmoney_invalid"))
|
||||
end
|
||||
args.playerId.addAccountMoney(args.account, args.amount, "Government Grant")
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "Give Account Money /giveaccountmoney Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
{ name = "Target", value = args.playerId.name, inline = true },
|
||||
{ name = "Account", value = args.account, inline = true },
|
||||
{ name = "Amount", value = args.amount, inline = true },
|
||||
})
|
||||
end
|
||||
end,
|
||||
true,
|
||||
{
|
||||
help = TranslateCap("command_giveaccountmoney"),
|
||||
validate = true,
|
||||
arguments = {
|
||||
{ name = "playerId", help = TranslateCap("commandgeneric_playerid"), type = "player" },
|
||||
{ name = "account", help = TranslateCap("command_giveaccountmoney_account"), type = "string" },
|
||||
{ name = "amount", help = TranslateCap("command_giveaccountmoney_amount"), type = "number" },
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
ESX.RegisterCommand(
|
||||
"removeaccountmoney",
|
||||
"admin",
|
||||
function(xPlayer, args, showError)
|
||||
if not args.playerId.getAccount(args.account) then
|
||||
return showError(TranslateCap("command_removeaccountmoney_invalid"))
|
||||
end
|
||||
args.playerId.removeAccountMoney(args.account, args.amount, "Government Tax")
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "Remove Account Money /removeaccountmoney Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
{ name = "Target", value = args.playerId.name, inline = true },
|
||||
{ name = "Account", value = args.account, inline = true },
|
||||
{ name = "Amount", value = args.amount, inline = true },
|
||||
})
|
||||
end
|
||||
end,
|
||||
true,
|
||||
{
|
||||
help = TranslateCap("command_removeaccountmoney"),
|
||||
validate = true,
|
||||
arguments = {
|
||||
{ name = "playerId", help = TranslateCap("commandgeneric_playerid"), type = "player" },
|
||||
{ name = "account", help = TranslateCap("command_removeaccountmoney_account"), type = "string" },
|
||||
{ name = "amount", help = TranslateCap("command_removeaccountmoney_amount"), type = "number" },
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if not Config.CustomInventory then
|
||||
ESX.RegisterCommand(
|
||||
"giveitem",
|
||||
"admin",
|
||||
function(xPlayer, args)
|
||||
args.playerId.addInventoryItem(args.item, args.count)
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "Give Item /giveitem Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
{ name = "Target", value = args.playerId.name, inline = true },
|
||||
{ name = "Item", value = args.item, inline = true },
|
||||
{ name = "Quantity", value = args.count, inline = true },
|
||||
})
|
||||
end
|
||||
end,
|
||||
true,
|
||||
{
|
||||
help = TranslateCap("command_giveitem"),
|
||||
validate = true,
|
||||
arguments = {
|
||||
{ name = "playerId", help = TranslateCap("commandgeneric_playerid"), type = "player" },
|
||||
{ name = "item", help = TranslateCap("command_giveitem_item"), type = "item" },
|
||||
{ name = "count", help = TranslateCap("command_giveitem_count"), type = "number", Validator = {
|
||||
validate = function(x) return x > 0 end,
|
||||
err = TranslateCap("commanderror_argumentmismatch_positive_number", "count")
|
||||
}},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
ESX.RegisterCommand(
|
||||
"giveweapon",
|
||||
"admin",
|
||||
function(xPlayer, args, showError)
|
||||
if args.playerId.hasWeapon(args.weapon) then
|
||||
return showError(TranslateCap("command_giveweapon_hasalready"))
|
||||
end
|
||||
args.playerId.addWeapon(args.weapon, args.ammo)
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "Give Weapon /giveweapon Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
{ name = "Target", value = args.playerId.name, inline = true },
|
||||
{ name = "Weapon", value = args.weapon, inline = true },
|
||||
{ name = "Ammo", value = args.ammo, inline = true },
|
||||
})
|
||||
end
|
||||
end,
|
||||
true,
|
||||
{
|
||||
help = TranslateCap("command_giveweapon"),
|
||||
validate = true,
|
||||
arguments = {
|
||||
{ name = "playerId", help = TranslateCap("commandgeneric_playerid"), type = "player" },
|
||||
{ name = "weapon", help = TranslateCap("command_giveweapon_weapon"), type = "weapon" },
|
||||
{ name = "ammo", help = TranslateCap("command_giveweapon_ammo"), type = "number" },
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
ESX.RegisterCommand(
|
||||
"giveammo",
|
||||
"admin",
|
||||
function(xPlayer, args, showError)
|
||||
if not args.playerId.hasWeapon(args.weapon) then
|
||||
return showError(TranslateCap("command_giveammo_noweapon_found"))
|
||||
end
|
||||
args.playerId.addWeaponAmmo(args.weapon, args.ammo)
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "Give Ammunition /giveammo Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
{ name = "Target", value = args.playerId.name, inline = true },
|
||||
{ name = "Weapon", value = args.weapon, inline = true },
|
||||
{ name = "Ammo", value = args.ammo, inline = true },
|
||||
})
|
||||
end
|
||||
end,
|
||||
true,
|
||||
{
|
||||
help = TranslateCap("command_giveweapon"),
|
||||
validate = false,
|
||||
arguments = {
|
||||
{ name = "playerId", help = TranslateCap("commandgeneric_playerid"), type = "player" },
|
||||
{ name = "weapon", help = TranslateCap("command_giveammo_weapon"), type = "weapon" },
|
||||
{ name = "ammo", help = TranslateCap("command_giveammo_ammo"), type = "number" },
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
ESX.RegisterCommand(
|
||||
"giveweaponcomponent",
|
||||
"admin",
|
||||
function(xPlayer, args, showError)
|
||||
if args.playerId.hasWeapon(args.weaponName) then
|
||||
local component = ESX.GetWeaponComponent(args.weaponName, args.componentName)
|
||||
|
||||
if component then
|
||||
if args.playerId.hasWeaponComponent(args.weaponName, args.componentName) then
|
||||
showError(TranslateCap("command_giveweaponcomponent_hasalready"))
|
||||
else
|
||||
args.playerId.addWeaponComponent(args.weaponName, args.componentName)
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "Give Weapon Component /giveweaponcomponent Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
{ name = "Target", value = args.playerId.name, inline = true },
|
||||
{ name = "Weapon", value = args.weaponName, inline = true },
|
||||
{ name = "Component", value = args.componentName, inline = true },
|
||||
})
|
||||
end
|
||||
end
|
||||
else
|
||||
showError(TranslateCap("command_giveweaponcomponent_invalid"))
|
||||
end
|
||||
else
|
||||
showError(TranslateCap("command_giveweaponcomponent_missingweapon"))
|
||||
end
|
||||
end,
|
||||
true,
|
||||
{
|
||||
help = TranslateCap("command_giveweaponcomponent"),
|
||||
validate = true,
|
||||
arguments = {
|
||||
{ name = "playerId", help = TranslateCap("commandgeneric_playerid"), type = "player" },
|
||||
{ name = "weaponName", help = TranslateCap("command_giveweapon_weapon"), type = "weapon" },
|
||||
{ name = "componentName", help = TranslateCap("command_giveweaponcomponent_component"), type = "string" },
|
||||
},
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
ESX.RegisterCommand({ "clear", "cls" }, "user", function(xPlayer)
|
||||
xPlayer.triggerEvent("chat:clear")
|
||||
end, false, { help = TranslateCap("command_clear") })
|
||||
|
||||
ESX.RegisterCommand({ "clearall", "clsall" }, "admin", function(xPlayer)
|
||||
TriggerClientEvent("chat:clear", -1)
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "Clear Chat /clearall Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
})
|
||||
end
|
||||
end, true, { help = TranslateCap("command_clearall") })
|
||||
|
||||
ESX.RegisterCommand("refreshjobs", "admin", function()
|
||||
ESX.RefreshJobs()
|
||||
end, true, { help = TranslateCap("command_clearall") })
|
||||
|
||||
if not Config.CustomInventory then
|
||||
ESX.RegisterCommand("refreshitems", "admin", function(xPlayer)
|
||||
local itemCount = ESX.RefreshItems()
|
||||
|
||||
xPlayer.showNotification(Translate("command_refreshitems_success", itemCount), true, false, 140)
|
||||
end, true, { help = TranslateCap("command_refreshitems") })
|
||||
|
||||
ESX.RegisterCommand(
|
||||
"clearinventory",
|
||||
"admin",
|
||||
function(xPlayer, args)
|
||||
for _, v in ipairs(args.playerId.inventory) do
|
||||
if v.count > 0 then
|
||||
args.playerId.setInventoryItem(v.name, 0)
|
||||
end
|
||||
end
|
||||
TriggerEvent("esx:playerInventoryCleared", args.playerId)
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "Clear Inventory /clearinventory Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
{ name = "Target", value = args.playerId.name, inline = true },
|
||||
})
|
||||
end
|
||||
end,
|
||||
true,
|
||||
{
|
||||
help = TranslateCap("command_clearinventory"),
|
||||
validate = true,
|
||||
arguments = {
|
||||
{ name = "playerId", help = TranslateCap("commandgeneric_playerid"), type = "player" },
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
ESX.RegisterCommand(
|
||||
"clearloadout",
|
||||
"admin",
|
||||
function(xPlayer, args)
|
||||
for i = #args.playerId.loadout, 1, -1 do
|
||||
args.playerId.removeWeapon(args.playerId.loadout[i].name)
|
||||
end
|
||||
TriggerEvent("esx:playerLoadoutCleared", args.playerId)
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "/clearloadout Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
{ name = "Target", value = args.playerId.name, inline = true },
|
||||
})
|
||||
end
|
||||
end,
|
||||
true,
|
||||
{
|
||||
help = TranslateCap("command_clearloadout"),
|
||||
validate = true,
|
||||
arguments = {
|
||||
{ name = "playerId", help = TranslateCap("commandgeneric_playerid"), type = "player" },
|
||||
},
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
ESX.RegisterCommand(
|
||||
"setgroup",
|
||||
"admin",
|
||||
function(xPlayer, args)
|
||||
if not args.playerId then
|
||||
args.playerId = xPlayer.source
|
||||
end
|
||||
if args.group == "superadmin" then
|
||||
args.group = "admin"
|
||||
print("[^3WARNING^7] ^5Superadmin^7 detected, setting group to ^5admin^7")
|
||||
end
|
||||
args.playerId.setGroup(args.group)
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "/setgroup Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
{ name = "Target", value = args.playerId.name, inline = true },
|
||||
{ name = "Group", value = args.group, inline = true },
|
||||
})
|
||||
end
|
||||
end,
|
||||
true,
|
||||
{
|
||||
help = TranslateCap("command_setgroup"),
|
||||
validate = true,
|
||||
arguments = {
|
||||
{ name = "playerId", help = TranslateCap("commandgeneric_playerid"), type = "player" },
|
||||
{ name = "group", help = TranslateCap("command_setgroup_group"), type = "string" },
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
ESX.RegisterCommand(
|
||||
"save",
|
||||
"admin",
|
||||
function(_, args)
|
||||
Core.SavePlayer(args.playerId)
|
||||
print(("[^2Info^0] Saved Player - ^5%s^0"):format(args.playerId.source))
|
||||
end,
|
||||
true,
|
||||
{
|
||||
help = TranslateCap("command_save"),
|
||||
validate = true,
|
||||
arguments = {
|
||||
{ name = "playerId", help = TranslateCap("commandgeneric_playerid"), type = "player" },
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
ESX.RegisterCommand("saveall", "admin", function()
|
||||
Core.SavePlayers()
|
||||
end, true, { help = TranslateCap("command_saveall") })
|
||||
|
||||
ESX.RegisterCommand("group", { "user", "admin" }, function(xPlayer, _, _)
|
||||
print(("%s, you are currently: ^5%s^0"):format(xPlayer.getName(), xPlayer.getGroup()))
|
||||
end, true)
|
||||
|
||||
ESX.RegisterCommand("job", { "user", "admin" }, function(xPlayer, _, _)
|
||||
local job = xPlayer.getJob()
|
||||
|
||||
print(("%s, your job is: ^5%s^0 - ^5%s^0 - ^5%s^0"):format(xPlayer.getName(), job.name, job.grade_label, job.onDuty and "On Duty" or "Off Duty"))
|
||||
end, false)
|
||||
|
||||
ESX.RegisterCommand("info", { "user", "admin" }, function(xPlayer)
|
||||
local job = xPlayer.getJob().name
|
||||
print(("^2ID: ^5%s^0 | ^2Name: ^5%s^0 | ^2Group: ^5%s^0 | ^2Job: ^5%s^0"):format(xPlayer.source, xPlayer.getName(), xPlayer.getGroup(), job))
|
||||
end, false)
|
||||
|
||||
ESX.RegisterCommand("playtime", { "user", "admin" }, function(xPlayer)
|
||||
local playtime = xPlayer.getPlayTime()
|
||||
local days = math.floor(playtime / 86400)
|
||||
local hours = math.floor((playtime % 86400) / 3600)
|
||||
local minutes = math.floor((playtime % 3600) / 60)
|
||||
print(("Playtime: ^5%s^0 Days | ^5%s^0 Hours | ^5%s^0 Minutes"):format(days, hours, minutes))
|
||||
end, false)
|
||||
|
||||
ESX.RegisterCommand("coords", "admin", function(xPlayer)
|
||||
local ped = GetPlayerPed(xPlayer.source)
|
||||
local coords = GetEntityCoords(ped, false)
|
||||
local heading = GetEntityHeading(ped)
|
||||
print(("Coords - Vector3: ^5%s^0"):format(vector3(coords.x, coords.y, coords.z)))
|
||||
print(("Coords - Vector4: ^5%s^0"):format(vector4(coords.x, coords.y, coords.z, heading)))
|
||||
end, false)
|
||||
|
||||
ESX.RegisterCommand("tpm", "admin", function(xPlayer)
|
||||
xPlayer.triggerEvent("esx:tpm")
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "Admin Teleport /tpm Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
})
|
||||
end
|
||||
end, false)
|
||||
|
||||
ESX.RegisterCommand(
|
||||
"goto",
|
||||
"admin",
|
||||
function(xPlayer, args)
|
||||
local targetCoords = args.playerId.getCoords()
|
||||
local srcDim = GetPlayerRoutingBucket(xPlayer.source)
|
||||
local targetDim = GetPlayerRoutingBucket(args.playerId.source)
|
||||
|
||||
if srcDim ~= targetDim then
|
||||
SetPlayerRoutingBucket(xPlayer.source, targetDim)
|
||||
end
|
||||
xPlayer.setCoords(targetCoords)
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "Admin Teleport /goto Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
{ name = "Target", value = args.playerId.name, inline = true },
|
||||
{ name = "Target Coords", value = targetCoords, inline = true },
|
||||
})
|
||||
end
|
||||
end,
|
||||
false,
|
||||
{
|
||||
help = TranslateCap("command_goto"),
|
||||
validate = true,
|
||||
arguments = {
|
||||
{ name = "playerId", help = TranslateCap("commandgeneric_playerid"), type = "player" },
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
ESX.RegisterCommand(
|
||||
"bring",
|
||||
"admin",
|
||||
function(xPlayer, args)
|
||||
local targetCoords = args.playerId.getCoords()
|
||||
local playerCoords = xPlayer.getCoords()
|
||||
local targetDim = GetPlayerRoutingBucket(args.playerId.source)
|
||||
local srcDim = GetPlayerRoutingBucket(xPlayer.source)
|
||||
|
||||
if targetDim ~= srcDim then
|
||||
SetPlayerRoutingBucket(args.playerId.source, srcDim)
|
||||
end
|
||||
args.playerId.setCoords(playerCoords)
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "Admin Teleport /bring Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
{ name = "Target", value = args.playerId.name, inline = true },
|
||||
{ name = "Target Coords", value = targetCoords, inline = true },
|
||||
})
|
||||
end
|
||||
end,
|
||||
false,
|
||||
{
|
||||
help = TranslateCap("command_bring"),
|
||||
validate = true,
|
||||
arguments = {
|
||||
{ name = "playerId", help = TranslateCap("commandgeneric_playerid"), type = "player" },
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
ESX.RegisterCommand(
|
||||
"kill",
|
||||
"admin",
|
||||
function(xPlayer, args)
|
||||
args.playerId.triggerEvent("esx:killPlayer")
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "Kill Command /kill Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
{ name = "Target", value = args.playerId.name, inline = true },
|
||||
})
|
||||
end
|
||||
end,
|
||||
true,
|
||||
{
|
||||
help = TranslateCap("command_kill"),
|
||||
validate = true,
|
||||
arguments = {
|
||||
{ name = "playerId", help = TranslateCap("commandgeneric_playerid"), type = "player" },
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
ESX.RegisterCommand(
|
||||
"freeze",
|
||||
"admin",
|
||||
function(xPlayer, args)
|
||||
args.playerId.triggerEvent("esx:freezePlayer", "freeze")
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "Admin Freeze /freeze Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
{ name = "Target", value = args.playerId.name, inline = true },
|
||||
})
|
||||
end
|
||||
end,
|
||||
true,
|
||||
{
|
||||
help = TranslateCap("command_freeze"),
|
||||
validate = true,
|
||||
arguments = {
|
||||
{ name = "playerId", help = TranslateCap("commandgeneric_playerid"), type = "player" },
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
ESX.RegisterCommand(
|
||||
"unfreeze",
|
||||
"admin",
|
||||
function(xPlayer, args)
|
||||
args.playerId.triggerEvent("esx:freezePlayer", "unfreeze")
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "Admin UnFreeze /unfreeze Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
{ name = "Target", value = args.playerId.name, inline = true },
|
||||
})
|
||||
end
|
||||
end,
|
||||
true,
|
||||
{
|
||||
help = TranslateCap("command_unfreeze"),
|
||||
validate = true,
|
||||
arguments = {
|
||||
{ name = "playerId", help = TranslateCap("commandgeneric_playerid"), type = "player" },
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
ESX.RegisterCommand("noclip", "admin", function(xPlayer)
|
||||
xPlayer.triggerEvent("esx:noclip")
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "Admin NoClip /noclip Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
})
|
||||
end
|
||||
end, false)
|
||||
|
||||
ESX.RegisterCommand("players", "admin", function()
|
||||
local xPlayers = ESX.GetExtendedPlayers() -- Returns all xPlayers
|
||||
print(("^5%s^2 online player(s)^0"):format(#xPlayers))
|
||||
for i = 1, #xPlayers do
|
||||
local xPlayer = xPlayers[i]
|
||||
print(("^1[^2ID: ^5%s^0 | ^2Name : ^5%s^0 | ^2Group : ^5%s^0 | ^2Identifier : ^5%s^1]^0\n"):format(xPlayer.source, xPlayer.getName(), xPlayer.getGroup(), xPlayer.identifier))
|
||||
end
|
||||
end, true)
|
||||
|
||||
ESX.RegisterCommand(
|
||||
{"setdim", "setbucket"},
|
||||
"admin",
|
||||
function(xPlayer, args)
|
||||
SetPlayerRoutingBucket(args.playerId.source, args.dimension)
|
||||
if Config.AdminLogging then
|
||||
ESX.DiscordLogFields("UserActions", "Admin Set Dim /setdim Triggered!", "pink", {
|
||||
{ name = "Player", value = xPlayer and xPlayer.name or "Server Console", inline = true },
|
||||
{ name = "ID", value = xPlayer and xPlayer.source or "Unknown ID", inline = true },
|
||||
{ name = "Target", value = args.playerId.name, inline = true },
|
||||
{ name = "Dimension", value = args.dimension, inline = true },
|
||||
})
|
||||
end
|
||||
end,
|
||||
true,
|
||||
{
|
||||
help = TranslateCap("command_setdim"),
|
||||
validate = true,
|
||||
arguments = {
|
||||
{ name = "playerId", help = TranslateCap("commandgeneric_playerid"), type = "player" },
|
||||
{ name = "dimension", help = TranslateCap("commandgeneric_dimension"), type = "number" },
|
||||
},
|
||||
}
|
||||
)
|
||||
101
resources/[core]/es_extended/server/modules/createJob.lua
Normal file
101
resources/[core]/es_extended/server/modules/createJob.lua
Normal file
@@ -0,0 +1,101 @@
|
||||
|
||||
local NOTIFY_TYPES = {
|
||||
INFO = "^5[%s]^7-^6[INFO]^7 %s",
|
||||
SUCCESS = "^5[%s]^7-^2[SUCCESS]^7 %s",
|
||||
ERROR = "^5[%s]^7-^1[ERROR]^7 %s"
|
||||
}
|
||||
|
||||
local function doesJobAndGradesExist(name, grades)
|
||||
if not ESX.Jobs[name] then
|
||||
return false
|
||||
end
|
||||
|
||||
for _, grade in ipairs(grades) do
|
||||
if not ESX.DoesJobExist(name, grade.grade) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function generateNewJobTable(name, label, grades, jobType)
|
||||
local job = { name = name, label = label, type = jobType, grades = {} }
|
||||
for _, v in pairs(grades) do
|
||||
job.grades[tostring(v.grade)] = { job_name = name, grade = v.grade, name = v.name, label = v.label, salary = v.salary, skin_male = v.skin_male or '{}', skin_female = v.skin_female or '{}' }
|
||||
end
|
||||
|
||||
return job
|
||||
end
|
||||
|
||||
local function notify(notifyType,resourceName,message,...)
|
||||
local formattedMessage = string.format(message, ...)
|
||||
|
||||
if not NOTIFY_TYPES[notifyType] then
|
||||
return print(NOTIFY_TYPES.INFO:format(resourceName,formattedMessage))
|
||||
end
|
||||
|
||||
return print(NOTIFY_TYPES[notifyType]:format(resourceName,formattedMessage))
|
||||
end
|
||||
|
||||
--- Create Job at Runtime
|
||||
--- @param name string
|
||||
--- @param label string
|
||||
--- @param grades table
|
||||
--- @param jobType string
|
||||
function ESX.CreateJob(name, label, grades, jobType)
|
||||
local currentResourceName = GetInvokingResource()
|
||||
local success = false
|
||||
|
||||
if not name or name == '' then
|
||||
notify("ERROR",currentResourceName, 'Missing argument `name`')
|
||||
return success
|
||||
end
|
||||
|
||||
if not label or label == '' then
|
||||
notify("ERROR",currentResourceName, 'Missing argument `label`')
|
||||
return success
|
||||
end
|
||||
|
||||
if not grades or not next(grades) then
|
||||
notify("ERROR",currentResourceName, 'Missing argument `grades`')
|
||||
return success
|
||||
end
|
||||
|
||||
if type(jobType) ~= "string" then
|
||||
jobType = "civ"
|
||||
end
|
||||
|
||||
local currentJobExist = doesJobAndGradesExist(name, grades)
|
||||
|
||||
if currentJobExist then
|
||||
notify("ERROR",currentResourceName, 'Job or grades already exists: `%s`', name)
|
||||
return success
|
||||
end
|
||||
|
||||
local queries = {
|
||||
{ query = 'INSERT INTO `jobs` (`name`, `label`, `type`) VALUES (?, ?, ?)', values = { name, label, jobType } }
|
||||
}
|
||||
|
||||
for _, grade in pairs(grades) do
|
||||
queries[#queries + 1] = {
|
||||
query = 'INSERT INTO job_grades (job_name, grade, name, label, salary, skin_male, skin_female) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
values = { name, grade.grade, grade.name, grade.label, grade.salary, grade.skin_male and json.encode(grade.skin_male) or '{}', grade.skin_female and json.encode(grade.skin_female) or '{}' }
|
||||
}
|
||||
end
|
||||
|
||||
success = exports.oxmysql:transaction_async(queries)
|
||||
|
||||
if not success then
|
||||
notify("ERROR", currentResourceName, 'Failed to insert one or more grades for job: `%s`', name)
|
||||
return success
|
||||
end
|
||||
|
||||
ESX.Jobs[name] = generateNewJobTable(name, label, grades, jobType)
|
||||
|
||||
notify("SUCCESS", currentResourceName, 'Job created successfully: `%s`', name)
|
||||
|
||||
TriggerEvent('esx:jobCreated', name, ESX.Jobs[name])
|
||||
|
||||
return success
|
||||
end
|
||||
52
resources/[core]/es_extended/server/modules/npwd.lua
Normal file
52
resources/[core]/es_extended/server/modules/npwd.lua
Normal file
@@ -0,0 +1,52 @@
|
||||
local npwd = GetResourceState("npwd"):find("start") and exports.npwd or nil
|
||||
|
||||
AddEventHandler("onServerResourceStart", function(resource)
|
||||
if resource ~= "npwd" then
|
||||
return
|
||||
end
|
||||
|
||||
npwd = GetResourceState("npwd"):find("start") and exports.npwd or nil
|
||||
|
||||
if not npwd then
|
||||
return
|
||||
end
|
||||
for _, xPlayer in pairs(ESX.Players) do
|
||||
npwd:newPlayer({
|
||||
source = xPlayer.source,
|
||||
identifier = xPlayer.identifier,
|
||||
firstname = xPlayer.get("firstName"),
|
||||
lastname = xPlayer.get("lastName"),
|
||||
})
|
||||
end
|
||||
end)
|
||||
|
||||
AddEventHandler("onServerResourceStop", function(resource)
|
||||
if resource == "npwd" then
|
||||
npwd = nil
|
||||
end
|
||||
end)
|
||||
|
||||
AddEventHandler("esx:playerLoaded", function(playerId, xPlayer)
|
||||
if not npwd then
|
||||
return
|
||||
end
|
||||
|
||||
if not xPlayer then
|
||||
xPlayer = ESX.GetPlayerFromId(playerId)
|
||||
end
|
||||
|
||||
npwd:newPlayer({
|
||||
source = playerId,
|
||||
identifier = xPlayer.identifier,
|
||||
firstname = xPlayer.get("firstName"),
|
||||
lastname = xPlayer.get("lastName"),
|
||||
})
|
||||
end)
|
||||
|
||||
AddEventHandler("esx:playerLogout", function(playerId)
|
||||
if not npwd then
|
||||
return
|
||||
end
|
||||
|
||||
npwd:unloadPlayer(playerId)
|
||||
end)
|
||||
387
resources/[core]/es_extended/server/modules/onesync.lua
Normal file
387
resources/[core]/es_extended/server/modules/onesync.lua
Normal file
@@ -0,0 +1,387 @@
|
||||
ESX.OneSync = {}
|
||||
|
||||
---@param source number|vector3
|
||||
---@param closest boolean
|
||||
---@param distance? number
|
||||
---@param ignore? table
|
||||
---@param routingBucket? number
|
||||
local function getNearbyPlayers(source, closest, distance, ignore, routingBucket)
|
||||
local result = {}
|
||||
local count = 0
|
||||
local playerPed
|
||||
local playerCoords
|
||||
ignore = ignore or {}
|
||||
|
||||
if not distance then
|
||||
distance = 100
|
||||
end
|
||||
|
||||
if type(source) == "number" then
|
||||
playerPed = GetPlayerPed(source)
|
||||
|
||||
if not source then
|
||||
error("Received invalid first argument (source); should be playerId")
|
||||
end
|
||||
|
||||
playerCoords = GetEntityCoords(playerPed)
|
||||
|
||||
if not playerCoords then
|
||||
error("Received nil value (playerCoords); perhaps source is nil at first place?")
|
||||
end
|
||||
end
|
||||
|
||||
if type(source) == "vector3" then
|
||||
playerCoords = source
|
||||
|
||||
if not playerCoords then
|
||||
error("Received nil value (playerCoords); perhaps source is nil at first place?")
|
||||
end
|
||||
end
|
||||
|
||||
for _, xPlayer in pairs(ESX.Players) do
|
||||
if not ignore[xPlayer.source] and (not routingBucket or GetPlayerRoutingBucket(xPlayer.source) == routingBucket) then
|
||||
local entity = GetPlayerPed(xPlayer.source)
|
||||
local coords = GetEntityCoords(entity)
|
||||
|
||||
if not closest then
|
||||
local dist = #(playerCoords - coords)
|
||||
if dist <= distance then
|
||||
count = count + 1
|
||||
result[count] = { id = xPlayer.source, ped = NetworkGetNetworkIdFromEntity(entity), coords = coords, dist = dist }
|
||||
end
|
||||
else
|
||||
if xPlayer.source ~= source then
|
||||
local dist = #(playerCoords - coords)
|
||||
if dist <= (result.dist or distance) then
|
||||
result = { id = xPlayer.source, ped = NetworkGetNetworkIdFromEntity(entity), coords = coords, dist = dist }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
---@param source vector3|number playerId or vector3 coordinates
|
||||
---@param maxDistance number
|
||||
---@param ignore? table playerIds to ignore, where the key is playerId and value is true
|
||||
---@param routingBucket? number
|
||||
function ESX.OneSync.GetPlayersInArea(source, maxDistance, ignore, routingBucket)
|
||||
return getNearbyPlayers(source, false, maxDistance, ignore, routingBucket)
|
||||
end
|
||||
|
||||
---@param source vector3|number playerId or vector3 coordinates
|
||||
---@param maxDistance number
|
||||
---@param ignore? table playerIds to ignore, where the key is playerId and value is true
|
||||
---@param routingBucket? number
|
||||
function ESX.OneSync.GetClosestPlayer(source, maxDistance, ignore, routingBucket)
|
||||
return getNearbyPlayers(source, true, maxDistance, ignore, routingBucket)
|
||||
end
|
||||
|
||||
---@param vehicleModel number|string
|
||||
---@param coords vector3|table
|
||||
---@param heading number
|
||||
---@param vehicleProperties table
|
||||
---@param cb? fun(netId: number)
|
||||
---@param vehicleType string?
|
||||
---@return number? netId
|
||||
function ESX.OneSync.SpawnVehicle(vehicleModel, coords, heading, vehicleProperties, cb, vehicleType)
|
||||
if cb and not ESX.IsFunctionReference(cb) then
|
||||
error("Invalid callback function")
|
||||
end
|
||||
|
||||
vehicleModel = joaat(vehicleModel)
|
||||
|
||||
local promise = not cb and promise.new()
|
||||
|
||||
local function resolve(result)
|
||||
if promise then
|
||||
promise:resolve(result)
|
||||
elseif cb then
|
||||
cb(result)
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
local function reject(err)
|
||||
if promise then
|
||||
promise:reject(err)
|
||||
end
|
||||
error(err)
|
||||
end
|
||||
|
||||
CreateThread(function()
|
||||
if not vehicleType then
|
||||
local xPlayer = ESX.GetExtendedPlayers()[1]
|
||||
if xPlayer then
|
||||
vehicleType = ESX.GetVehicleType(vehicleModel, xPlayer.source)
|
||||
end
|
||||
end
|
||||
|
||||
if not vehicleType then
|
||||
return reject("No players found nearby to check vehicle type! Alternatively, you can specify the vehicle type manually.")
|
||||
end
|
||||
|
||||
local createdVehicle = CreateVehicleServerSetter(vehicleModel, vehicleType, coords.x, coords.y, coords.z, heading)
|
||||
local tries = 0
|
||||
|
||||
local hasNetOwner = next(ESX.OneSync.GetClosestPlayer(coords, 300, nil, 0) or {}) ~= nil
|
||||
|
||||
while not createdVehicle or createdVehicle == 0
|
||||
or (hasNetOwner and NetworkGetEntityOwner(createdVehicle) == -1)
|
||||
or (not hasNetOwner and not DoesEntityExist(createdVehicle)) do
|
||||
Wait(200)
|
||||
tries = tries + 1
|
||||
if tries > 40 then
|
||||
return reject(("Could not spawn vehicle - ^5%s^7!"):format(vehicleModel))
|
||||
end
|
||||
end
|
||||
|
||||
-- luacheck: ignore
|
||||
SetEntityOrphanMode(createdVehicle, 2)
|
||||
local networkId = NetworkGetNetworkIdFromEntity(createdVehicle)
|
||||
Entity(createdVehicle).state:set("VehicleProperties", vehicleProperties, true)
|
||||
|
||||
resolve(networkId)
|
||||
end)
|
||||
|
||||
if promise then
|
||||
return Citizen.Await(promise)
|
||||
end
|
||||
end
|
||||
|
||||
---@param model number|string
|
||||
---@param coords vector3|table
|
||||
---@param heading number
|
||||
---@param cb? fun(netId: number)
|
||||
---@return number? netId
|
||||
function ESX.OneSync.SpawnObject(model, coords, heading, cb)
|
||||
if type(model) == "string" then
|
||||
model = joaat(model)
|
||||
end
|
||||
|
||||
local promise = not cb and promise.new()
|
||||
local objectCoords = type(coords) == "vector3" and coords or vector3(coords.x, coords.y, coords.z)
|
||||
|
||||
local function resolve(result)
|
||||
if promise then
|
||||
promise:resolve(result)
|
||||
elseif cb then
|
||||
cb(result)
|
||||
end
|
||||
end
|
||||
|
||||
local function reject(err)
|
||||
if promise then
|
||||
promise:reject(err)
|
||||
end
|
||||
error(err)
|
||||
end
|
||||
|
||||
CreateThread(function()
|
||||
local entity = CreateObject(model, objectCoords.x, objectCoords.y, objectCoords.z, true, true, false)
|
||||
local tries = 0
|
||||
|
||||
while not DoesEntityExist(entity) do
|
||||
Wait(200)
|
||||
|
||||
tries = tries + 1
|
||||
|
||||
if tries > 40 then
|
||||
return reject(("Could not spawn object - ^5%s^7!"):format(entity))
|
||||
end
|
||||
end
|
||||
|
||||
local networkId = NetworkGetNetworkIdFromEntity(entity)
|
||||
|
||||
SetEntityHeading(entity, heading)
|
||||
resolve(networkId)
|
||||
end)
|
||||
|
||||
if promise then
|
||||
return Citizen.Await(promise)
|
||||
end
|
||||
end
|
||||
|
||||
---@param model number|string
|
||||
---@param coords vector3|table
|
||||
---@param heading number
|
||||
---@param cb? fun(netId: number)
|
||||
---@return number? netId
|
||||
function ESX.OneSync.SpawnPed(model, coords, heading, cb)
|
||||
if type(model) == "string" then
|
||||
model = joaat(model)
|
||||
end
|
||||
|
||||
local promise = not cb and promise.new()
|
||||
|
||||
local function resolve(result)
|
||||
if promise then
|
||||
promise:resolve(result)
|
||||
elseif cb then
|
||||
cb(result)
|
||||
end
|
||||
end
|
||||
|
||||
local function reject(err)
|
||||
if promise then
|
||||
promise:reject(err)
|
||||
end
|
||||
error(err)
|
||||
end
|
||||
|
||||
CreateThread(function()
|
||||
local entity = CreatePed(0, model, coords.x, coords.y, coords.z, heading, true, true)
|
||||
local tries = 0
|
||||
|
||||
while not DoesEntityExist(entity) do
|
||||
Wait(200)
|
||||
|
||||
tries = tries + 1
|
||||
|
||||
if tries > 40 then
|
||||
return reject(("Could not spawn ped - ^5%s^7!"):format(model))
|
||||
end
|
||||
end
|
||||
|
||||
local networkId = NetworkGetNetworkIdFromEntity(entity)
|
||||
resolve(networkId)
|
||||
end)
|
||||
|
||||
if promise then
|
||||
return Citizen.Await(promise)
|
||||
end
|
||||
end
|
||||
|
||||
---@param model number|string
|
||||
---@param vehicle number entityId
|
||||
---@param seat number
|
||||
---@param cb? fun(netId: number)
|
||||
---@return number? netId
|
||||
function ESX.OneSync.SpawnPedInVehicle(model, vehicle, seat, cb)
|
||||
if type(model) == "string" then
|
||||
model = joaat(model)
|
||||
end
|
||||
|
||||
local promise = not cb and promise.new()
|
||||
|
||||
local function resolve(result)
|
||||
if promise then
|
||||
promise:resolve(result)
|
||||
elseif cb then
|
||||
cb(result)
|
||||
end
|
||||
end
|
||||
|
||||
local function reject(err)
|
||||
if promise then
|
||||
promise:reject(err)
|
||||
end
|
||||
error(err)
|
||||
end
|
||||
|
||||
CreateThread(function()
|
||||
local entity = CreatePedInsideVehicle(vehicle, 1, model, seat, true, true)
|
||||
local tries = 0
|
||||
|
||||
while not DoesEntityExist(entity) do
|
||||
Wait(200)
|
||||
|
||||
tries = tries + 1
|
||||
|
||||
if tries > 40 then
|
||||
reject(("Could not spawn ped - ^5%s^7!"):format(model))
|
||||
end
|
||||
end
|
||||
|
||||
local networkId = NetworkGetNetworkIdFromEntity(entity)
|
||||
resolve(networkId)
|
||||
end)
|
||||
|
||||
if promise then
|
||||
return Citizen.Await(promise)
|
||||
end
|
||||
end
|
||||
|
||||
local function getNearbyEntities(entities, coords, modelFilter, maxDistance, isPed)
|
||||
local nearbyEntities = {}
|
||||
coords = type(coords) == "number" and GetEntityCoords(GetPlayerPed(coords)) or vector3(coords.x, coords.y, coords.z)
|
||||
for _, entity in pairs(entities) do
|
||||
if not isPed or (isPed and not IsPedAPlayer(entity)) then
|
||||
if not modelFilter or modelFilter[GetEntityModel(entity)] then
|
||||
local entityCoords = GetEntityCoords(entity)
|
||||
if not maxDistance or #(coords - entityCoords) <= maxDistance then
|
||||
nearbyEntities[#nearbyEntities + 1] = NetworkGetNetworkIdFromEntity(entity)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nearbyEntities
|
||||
end
|
||||
|
||||
---@param coords vector3
|
||||
---@param maxDistance number
|
||||
---@param modelFilter table models to ignore, where the key is the model hash and the value is true
|
||||
---@return table
|
||||
function ESX.OneSync.GetPedsInArea(coords, maxDistance, modelFilter)
|
||||
return getNearbyEntities(GetAllPeds(), coords, modelFilter, maxDistance, true)
|
||||
end
|
||||
|
||||
---@param coords vector3
|
||||
---@param maxDistance number
|
||||
---@param modelFilter table models to ignore, where the key is the model hash and the value is true
|
||||
---@return table
|
||||
function ESX.OneSync.GetObjectsInArea(coords, maxDistance, modelFilter)
|
||||
return getNearbyEntities(GetAllObjects(), coords, modelFilter, maxDistance)
|
||||
end
|
||||
|
||||
---@param coords vector3
|
||||
---@param maxDistance number
|
||||
---@param modelFilter table | nil models to ignore, where the key is the model hash and the value is true
|
||||
---@return table
|
||||
function ESX.OneSync.GetVehiclesInArea(coords, maxDistance, modelFilter)
|
||||
return getNearbyEntities(GetAllVehicles(), coords, modelFilter, maxDistance)
|
||||
end
|
||||
|
||||
local function getClosestEntity(entities, coords, modelFilter, isPed)
|
||||
local distance, closestEntity, closestCoords = 100, 0, vector3(0, 0, 0)
|
||||
coords = type(coords) == "number" and GetEntityCoords(GetPlayerPed(coords)) or vector3(coords.x, coords.y, coords.z)
|
||||
|
||||
for _, entity in pairs(entities) do
|
||||
if not isPed or (isPed and not IsPedAPlayer(entity)) then
|
||||
if not modelFilter or modelFilter[GetEntityModel(entity)] then
|
||||
local entityCoords = GetEntityCoords(entity)
|
||||
local dist = #(coords - entityCoords)
|
||||
if dist < distance then
|
||||
closestEntity, distance, closestCoords = entity, dist, entityCoords
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return NetworkGetNetworkIdFromEntity(closestEntity), distance, closestCoords
|
||||
end
|
||||
|
||||
---@param coords vector3
|
||||
---@param modelFilter table models to ignore, where the key is the model hash and the value is true
|
||||
---@return number entityId, number distance, vector3 coords
|
||||
function ESX.OneSync.GetClosestPed(coords, modelFilter)
|
||||
return getClosestEntity(GetAllPeds(), coords, modelFilter, true)
|
||||
end
|
||||
|
||||
---@param coords vector3
|
||||
---@param modelFilter table models to ignore, where the key is the model hash and the value is true
|
||||
---@return number entityId, number distance, vector3 coords
|
||||
function ESX.OneSync.GetClosestObject(coords, modelFilter)
|
||||
return getClosestEntity(GetAllObjects(), coords, modelFilter)
|
||||
end
|
||||
|
||||
---@param coords vector3
|
||||
---@param modelFilter table models to ignore, where the key is the model hash and the value is true
|
||||
---@return number entityId, number distance, vector3 coords
|
||||
function ESX.OneSync.GetClosestVehicle(coords, modelFilter)
|
||||
return getClosestEntity(GetAllVehicles(), coords, modelFilter)
|
||||
end
|
||||
71
resources/[core]/es_extended/server/modules/paycheck.lua
Normal file
71
resources/[core]/es_extended/server/modules/paycheck.lua
Normal file
@@ -0,0 +1,71 @@
|
||||
function StartPayCheck()
|
||||
CreateThread(function()
|
||||
while true do
|
||||
Wait(Config.PaycheckInterval)
|
||||
for player, xPlayer in pairs(ESX.Players) do
|
||||
local jobLabel = xPlayer.job.label
|
||||
local job = xPlayer.job.grade_name
|
||||
local onDuty = xPlayer.job.onDuty
|
||||
local salary = (job == "unemployed" or onDuty) and xPlayer.job.grade_salary or ESX.Math.Round(xPlayer.job.grade_salary * Config.OffDutyPaycheckMultiplier)
|
||||
|
||||
if xPlayer.paycheckEnabled then
|
||||
if salary > 0 then
|
||||
if job == "unemployed" then -- unemployed
|
||||
xPlayer.addAccountMoney("bank", salary, "Welfare Check")
|
||||
TriggerClientEvent("esx:showAdvancedNotification", player, TranslateCap("bank"), TranslateCap("received_paycheck"), TranslateCap("received_help", salary), "CHAR_BANK_MAZE", 9)
|
||||
if Config.LogPaycheck then
|
||||
ESX.DiscordLogFields("Paycheck", "Paycheck - Unemployment Benefits", "green", {
|
||||
{ name = "Player", value = xPlayer.name, inline = true },
|
||||
{ name = "ID", value = xPlayer.source, inline = true },
|
||||
{ name = "Amount", value = salary, inline = true },
|
||||
})
|
||||
end
|
||||
elseif Config.EnableSocietyPayouts then -- possibly a society
|
||||
TriggerEvent("esx_society:getSociety", xPlayer.job.name, function(society)
|
||||
if society ~= nil then -- verified society
|
||||
TriggerEvent("esx_addonaccount:getSharedAccount", society.account, function(account)
|
||||
if account.money >= salary then -- does the society money to pay its employees?
|
||||
xPlayer.addAccountMoney("bank", salary, "Paycheck")
|
||||
account.removeMoney(salary)
|
||||
if Config.LogPaycheck then
|
||||
ESX.DiscordLogFields("Paycheck", "Paycheck - " .. jobLabel, "green", {
|
||||
{ name = "Player", value = xPlayer.name, inline = true },
|
||||
{ name = "ID", value = xPlayer.source, inline = true },
|
||||
{ name = "Amount", value = salary, inline = true },
|
||||
})
|
||||
end
|
||||
|
||||
TriggerClientEvent("esx:showAdvancedNotification", player, TranslateCap("bank"), TranslateCap("received_paycheck"), TranslateCap("received_salary", salary), "CHAR_BANK_MAZE", 9)
|
||||
else
|
||||
TriggerClientEvent("esx:showAdvancedNotification", player, TranslateCap("bank"), "", TranslateCap("company_nomoney"), "CHAR_BANK_MAZE", 1)
|
||||
end
|
||||
end)
|
||||
else -- not a society
|
||||
xPlayer.addAccountMoney("bank", salary, "Paycheck")
|
||||
if Config.LogPaycheck then
|
||||
ESX.DiscordLogFields("Paycheck", "Paycheck - " .. jobLabel, "green", {
|
||||
{ name = "Player", value = xPlayer.name, inline = true },
|
||||
{ name = "ID", value = xPlayer.source, inline = true },
|
||||
{ name = "Amount", value = salary, inline = true },
|
||||
})
|
||||
end
|
||||
TriggerClientEvent("esx:showAdvancedNotification", player, TranslateCap("bank"), TranslateCap("received_paycheck"), TranslateCap("received_salary", salary), "CHAR_BANK_MAZE", 9)
|
||||
end
|
||||
end)
|
||||
else -- generic job
|
||||
xPlayer.addAccountMoney("bank", salary, "Paycheck")
|
||||
if Config.LogPaycheck then
|
||||
ESX.DiscordLogFields("Paycheck", "Paycheck - Generic", "green", {
|
||||
{ name = "Player", value = xPlayer.name, inline = true },
|
||||
{ name = "ID", value = xPlayer.source, inline = true },
|
||||
{ name = "Amount", value = salary, inline = true },
|
||||
})
|
||||
end
|
||||
TriggerClientEvent("esx:showAdvancedNotification", player, TranslateCap("bank"), TranslateCap("received_paycheck"), TranslateCap("received_salary", salary), "CHAR_BANK_MAZE", 9)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
Reference in New Issue
Block a user