From 1b9c8c15d26d4ff3cd724b29e32348ab92b6c0ad Mon Sep 17 00:00:00 2001 From: HR0110 Date: Sun, 14 Sep 2025 18:39:24 +0300 Subject: [PATCH 1/2] feat(xPlayer): added the possibility of adding and removing hooks for the xPlayer functions --- .../server/classes/overrides/oxinventory.lua | 219 ---- [core]/es_extended/server/classes/player.lua | 968 ----------------- .../server/classes/xPlayer/classes.lua | 174 ++++ .../server/classes/xPlayer/main.lua | 976 ++++++++++++++++++ .../classes/xPlayer/oxInv_overrides.lua | 132 +++ [core]/es_extended/server/functions.lua | 2 +- [core]/es_extended/server/main.lua | 147 +-- [core]/es_extended/server/migration/main.lua | 2 +- .../es_extended/server/modules/getXPlayer.lua | 36 + [core]/es_extended/shared/modules/table.lua | 2 +- [core]/esx_notify/Notify.lua | 14 +- [core]/skinchanger/client/main.lua | 2 +- 12 files changed, 1384 insertions(+), 1290 deletions(-) delete mode 100644 [core]/es_extended/server/classes/overrides/oxinventory.lua delete mode 100644 [core]/es_extended/server/classes/player.lua create mode 100644 [core]/es_extended/server/classes/xPlayer/classes.lua create mode 100644 [core]/es_extended/server/classes/xPlayer/main.lua create mode 100644 [core]/es_extended/server/classes/xPlayer/oxInv_overrides.lua create mode 100644 [core]/es_extended/server/modules/getXPlayer.lua diff --git a/[core]/es_extended/server/classes/overrides/oxinventory.lua b/[core]/es_extended/server/classes/overrides/oxinventory.lua deleted file mode 100644 index c3be26a8..00000000 --- a/[core]/es_extended/server/classes/overrides/oxinventory.lua +++ /dev/null @@ -1,219 +0,0 @@ -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, -} diff --git a/[core]/es_extended/server/classes/player.lua b/[core]/es_extended/server/classes/player.lua deleted file mode 100644 index 19e18088..00000000 --- a/[core]/es_extended/server/classes/player.lua +++ /dev/null @@ -1,968 +0,0 @@ ----@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 # Get all accounts, optionally minimal. ---- Inventory Functions ----@field getInventory fun(minimal?: boolean): ESXInventoryItem[]|table # 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 # 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 = 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 = GetPlayerPed(self.source) - local entityCoords = GetEntityCoords(ped) - local entityHeading = 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, - 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 = 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 , weapon = 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 , weapon = self.getWeapon(weaponName) - - if weapon then - local _, weaponObject = 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 = self.getWeapon(weaponName) - - if weapon then - return weapon.tintIndex - end - - return 0 - end - - function self.removeWeapon(weaponName) - local weaponLabel, playerPed = 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 , weapon = self.getWeapon(weaponName) - - if weapon then - local component = 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 = 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) diff --git a/[core]/es_extended/server/classes/xPlayer/classes.lua b/[core]/es_extended/server/classes/xPlayer/classes.lua new file mode 100644 index 00000000..3bb46baa --- /dev/null +++ b/[core]/es_extended/server/classes/xPlayer/classes.lua @@ -0,0 +1,174 @@ +---@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 +--- Money Functions +---@field setMoney { __call: fun(self: StaticPlayer, money: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Set player's cash balance. +---@field getMoney { (__call: fun(self: StaticPlayer): number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Get player's current cash balance. +---@field addMoney { __call: fun(self: StaticPlayer, money: number, reason: string), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Add money to the player's cash balance. +---@field removeMoney { __call: fun(self: StaticPlayer, money: number, reason: string), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Remove money from the player's cash balance. +---@field setAccountMoney { __call: fun(self: StaticPlayer, accountName: string, money: number, reason?: string), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Set specific account balance. +---@field addAccountMoney { __call: fun(self: StaticPlayer, accountName: string, money: number, reason?: string), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Add money to an account. +---@field removeAccountMoney { __call: fun(self: StaticPlayer, accountName: string, money: number, reason?: string), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Remove money from an account. +---@field getAccount { (__call: fun(self: StaticPlayer, account: string): ESXAccount?), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Get account data by name. +---@field getAccounts { (__call: fun(self: StaticPlayer, minimal?: boolean): ESXAccount[]|table), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Get all accounts, optionally minimal. +--- Inventory Functions +---@field getInventory { (__call: fun(self: StaticPlayer, minimal?: boolean): ESXInventoryItem[]|table), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Get inventory, optionally minimal. +---@field getInventoryItem { (__call: fun(self: StaticPlayer, itemName: string): ESXInventoryItem?), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Get a specific item from inventory. +---@field addInventoryItem { __call: fun(self: StaticPlayer, itemName: string, count: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Add items to inventory. +---@field removeInventoryItem { __call: fun(self: StaticPlayer, itemName: string, count: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Remove items from inventory. +---@field setInventoryItem { __call: fun(self: StaticPlayer, itemName: string, count: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Set item count in inventory. +---@field getWeight { (__call: fun(self: StaticPlayer): number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Get current carried weight. +---@field getMaxWeight { (__call: fun(self: StaticPlayer): number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Get maximum carry weight. +---@field setMaxWeight { __call: fun(self: StaticPlayer, newWeight: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Set maximum carry weight. +---@field canCarryItem { (__call: fun(self: StaticPlayer, itemName: string, count: number): boolean), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Check if player can carry more of an item. +---@field canSwapItem { (__call: fun(self: StaticPlayer, firstItem: string, firstItemCount: number, testItem: string, testItemCount: number): boolean), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Check if items can be swapped. +---@field hasItem { (__call: fun(self: StaticPlayer, item: string): ESXInventoryItem|false, number?), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Check if player has an item. +---@field getLoadout { (__call: fun(self: StaticPlayer, minimal?: boolean): ESXInventoryWeapon[]|table), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # 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 { __call: fun(self: StaticPlayer, weaponName: string, ammo: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Give player a weapon. +---@field removeWeapon { __call: fun(self: StaticPlayer, weaponName: string), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Remove weapon from player. +---@field hasWeapon { (__call: fun(self: StaticPlayer, weaponName: string): boolean), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Check if player has a weapon. +---@field getWeapon { (__call: fun(self: StaticPlayer, weaponName: string): number?, table?), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Get weapon ammo & components. +---@field addWeaponAmmo { __call: fun(self: StaticPlayer, weaponName: string, ammoCount: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Add ammo to a weapon. +---@field removeWeaponAmmo { __call: fun(self: StaticPlayer, weaponName: string, ammoCount: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Remove ammo from a weapon. +---@field updateWeaponAmmo { __call: fun(self: StaticPlayer, weaponName: string, ammoCount: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Update ammo count for a weapon. +---@field addWeaponComponent { __call: fun(self: StaticPlayer, weaponName: string, weaponComponent: string), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Add component to weapon. +---@field removeWeaponComponent { __call: fun(self: StaticPlayer, weaponName: string, weaponComponent: string), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Remove component from weapon. +---@field hasWeaponComponent { (__call: fun(self: StaticPlayer, weaponName: string, weaponComponent: string): boolean), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Check if weapon has component. +---@field setWeaponTint { __call: fun(self: StaticPlayer, weaponName: string, weaponTintIndex: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Set weapon tint. +---@field getWeaponTint { (__call: fun(self: StaticPlayer, weaponName: string): number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # 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 { __call: fun(self: StaticPlayer, msg: string, notifyType?: string, length?: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Show a simple notification. +---@field showAdvancedNotification { __call: fun(self: StaticPlayer, sender: string, subject: string, msg: string, textureDict: string, iconType: string, flash: boolean, saveToBrief: boolean, hudColorIndex: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Show advanced notification. +---@field showHelpNotification { __call: fun(self: StaticPlayer, msg: string, thisFrame?: boolean, beep?: boolean, duration?: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # 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. + +---@class xPlayerClass:xPlayer +---@field hooks { functionName: string, isJustAddition: boolean, functionReference: fun(xPlayer: xPlayer, ...: any): any }[] +---@field __removeHook fun(self: xPlayerClass, hookId: integer): boolean Returns success status +---@field kick function +---@field setMoney function +---@field getMoney function +---@field addMoney function +---@field removeMoney function +---@field getInventory function +---@field getLoadout function +---@field setAccountMoney function +---@field addAccountMoney function +---@field removeAccountMoney function +---@field getInventoryItem function +---@field addInventoryItem function +---@field removeInventoryItem function +---@field setInventoryItem function +---@field canCarryItem function +---@field canSwapItem function +---@field setMaxWeight function +---@field addWeapon function +---@field addWeaponComponent function +---@field addWeaponAmmo function +---@field updateWeaponAmmo function +---@field setWeaponTint function +---@field getWeaponTint function +---@field removeWeapon function +---@field removeWeaponComponent function +---@field removeWeaponAmmo function +---@field hasWeaponComponent function +---@field hasWeapon function +---@field hasItem function +---@field getWeapon function +---@field showNotification function +---@field showAdvancedNotification function +---@field showHelpNotification function +---@field __index fun(self: xPlayerClass, k: string): any +---@field __newindex fun(self: xPlayerClass, k: string, v: any) +---@field __call fun(self: xPlayerClass, playerId: integer, data: { identifier: string, ssn: string, group: string, accounts: ESXAccount[], inventory: table, weight: number, job: ESXJob, loadout: ESXInventoryWeapon[], steamName: string, coords: vector4|{x: number, y: number, z: number, heading: number}, metadata: table }): xPlayer? \ No newline at end of file diff --git a/[core]/es_extended/server/classes/xPlayer/main.lua b/[core]/es_extended/server/classes/xPlayer/main.lua new file mode 100644 index 00000000..47c5e66e --- /dev/null +++ b/[core]/es_extended/server/classes/xPlayer/main.lua @@ -0,0 +1,976 @@ +IsAuthorized = {} + +local hookIdGeneratorRadius = 1e5 +local hookableFunctions = setmetatable({ + 'kick', + 'setMoney', + 'getMoney', + 'addMoney', + 'removeMoney', + 'addAccountMoney', + 'setAccountMoney', + 'removeAccountMoney', + 'getInventoryItem', + 'addInventoryItem', + 'removeInventoryItem', + 'setInventoryItem', + 'hasItem', + 'hasWeapon', + 'hasWeaponComponent', + 'getWeaponTint', + 'setWeaponTint', + 'addWeaponAmmo', + 'getInventory', + 'getLoadout', + 'canCarryItem', + 'canSwapItem', + 'setMaxWeight', + 'addWeapon', + 'addWeaponComponent', + 'updateWeaponAmmo', + 'removeWeapon', + 'removeWeaponComponent', + 'removeWeaponAmmo', + 'getWeapon', + 'showNotification', + 'showAdvancedNotification', + 'showHelpNotification' +}, { + __call = function(self, functionName) + for i=1, #self do + if self[i] == functionName then + return true + end + end + + return false + end +}) + +-- Local Functions + +local otherResCheck = function() + return GetInvokingResource() ~= 'es_extended' and not IsAuthorized[#IsAuthorized] +end + +---@param hooks { hookId: string, functionName: string, invokingRes: string, isJustAddition: boolean, functionReference: fun(xPlayer: xPlayer, ...: any): any }[] +---@return string +local generateHookId = function(hooks) + local idText = ('xPlayerHook_%s'):format(math.random(-hookIdGeneratorRadius, hookIdGeneratorRadius)) + + if hooks('hookId', idText) then + local timesExecuted = 0 --2*hookIdGeneratorRadius + while hooks('hookId', idText) do + Wait(0) + + if timesExecuted < 2*hookIdGeneratorRadius then + idText = ('xPlayerHook_%s'):format(math.random(-hookIdGeneratorRadius, hookIdGeneratorRadius)) + timesExecuted += 1 + else + idText = ('xPlayerHook_%s'):format(hookIdGeneratorRadius + 1) + + hookIdGeneratorRadius *= 1e2 + + break + end + end + end + + return idText +end + +-- Main Code + +XPlayerClass = setmetatable({ + hooks = setmetatable({}, { + __call = function(self, compareField, compareValue, returnIndex) + if compareField ~= 'hookId' and compareField ~= 'functionName' and compareField ~= 'invokerRes' and compareField ~= 'functionReference' then return error(('Resource %s tried to compare unexisting field in ESX\'s xPlayer hooks.\nStopped the code to prevent other errors.'):format(GetInvokingResource())) end + + for i=1, #self do + if self[i][compareField] == compareValue then + if returnIndex then + return true, i + else + return true + end + end + end + end + }), + + addHook = function(self, functionName, isJustAddition, functionReference) + IsAuthorized[#IsAuthorized+1] = true + + if not self[functionName] or not ESX.IsFunctionReference(functionReference) then + warn(('Resource %s tried to\n%s!'):format(GetInvokingResource(), not self[functionName] and 'add a hook function to missing xPlayer function' or 'add a hook function by providing a non-function for the hook\'s function reference')) + + return false + elseif not hookableFunctions(functionName) then + warn(('Resource %s tried to add a hook for a function which does not support hooks!'):format(GetInvokingResource())) + end + + if not isJustAddition then + if #self.hooks > 0 then + for i=1, #self.hooks do + if self.hooks[i].functionName == functionName then + table.remove(self.hooks, i) + end + end + end + end + + local hookId = generateHookId(self.hooks) + + self.hooks[#self.hooks+1] = { + hookId = hookId, + functionName = functionName, + invokerRes = GetInvokingResource(), + isJustAddition = true, + functionReference = functionReference + } + + table.remove(IsAuthorized, #IsAuthorized) + + return hookId + end, + + removeHook = function(self, hookId) + IsAuthorized[#IsAuthorized+1] = true + + local found , index = self.hooks('hookId', hookId, true) + + if found then + table.remove(self.hooks, index) + end + + table.remove(IsAuthorized, #IsAuthorized) + + return found + end, + + triggerEvent = function(xPlayer, eventName, ...) + assert(type(eventName) == 'string', 'eventName should be string!') + TriggerClientEvent(eventName, xPlayer.source, ...) + end, + + togglePaycheck = function(xPlayer, toggle) + xPlayer.paycheckEnabled = toggle + end, + + isPaycheckEnabled = function(xPlayer) + return xPlayer.paycheckEnabled + end, + + isAdmin = function(xPlayer) + return Core.IsPlayerAdmin(xPlayer.source) + end, + + setCoords = function(xPlayer, coordinates) + local ped = GetPlayerPed(xPlayer.source) + + SetEntityCoords(ped, coordinates.x, coordinates.y, coordinates.z, false, false, false, false) + SetEntityHeading(ped, coordinates.w or coordinates.heading or 0.0) + end, + + getCoords = function(xPlayer, vector, heading) + local ped = GetPlayerPed(xPlayer.source) + local entityCoords = GetEntityCoords(ped) + local entityHeading = 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) + elseif heading then + coordinates.heading = entityHeading + end + + return coordinates + end, + + kick = function(xPlayer, reason) + DropPlayer(tostring(xPlayer.source), reason) + end, + + getPlayTime = function(xPlayer) + return xPlayer.lastPlaytime + GetPlayerTimeOnline(xPlayer.source --[[@as string]]) + end, + + setMoney = function(xPlayer, money) + assert(type(money) == 'number', 'money should be number!') + + xPlayer.setAccountMoney('money', ESX.Math.Round(money)) + end, + + getMoney = function(xPlayer) + return xPlayer.getAccount('money').money + end, + + addMoney = function(xPlayer, money, reason) + xPlayer.addAccountMoney('money', ESX.Math.Round(money), reason) + end, + + removeMoney = function(xPlayer, money, reason) + xPlayer.removeAccountMoney('money', ESX.Math.Round(money), reason) + end, + + ---@deprecated use xPlayer.identifier instead + getIdentifier = function(xPlayer) return xPlayer.identifier end, + + ---@deprecated use xPlayer.ssn instead + getSSN = function(xPlayer) return xPlayer.ssn end, + + setGroup = function(xPlayer, newGroup) + local lastGroup = xPlayer.group + xPlayer.group = newGroup + + xPlayer.triggerEvent('esx:setGroup', xPlayer.group, lastGroup) + Player(xPlayer.source).state:set('group', xPlayer.group, true) + TriggerEvent('esx:setGroup', xPlayer.source, xPlayer.group, lastGroup) + ExecuteCommand(('remove_principal identifier.%s group.%s'):format(xPlayer.license, lastGroup)) + ExecuteCommand(('add_principal identifier.%s group.%s'):format(xPlayer.license, xPlayer.group)) + end, + + ---@deprecated use xPlayer.group instead + getGroup = function(xPlayer) return xPlayer.group end, + + set = function(xPlayer, k, v) + xPlayer.variables[k] = v + + xPlayer.triggerEvent('esx:updatePlayerData', 'variables', xPlayer.variables) + end, + + get = function(xPlayer, k) + return xPlayer.variables[k] + end, + + getAccounts = function(xPlayer, minimal) + if not minimal then + return xPlayer.accounts + end + + local minimalAccounts = {} + + for i = 1, #xPlayer.accounts do + minimalAccounts[xPlayer.accounts[i].name] = xPlayer.accounts[i].money + end + + return minimalAccounts + end, + + getAccount = function(xPlayer, account) + account = string.lower(account) + for i = 1, #xPlayer.accounts do + local accountName = string.lower(xPlayer.accounts[i].name) + if accountName == account then + return xPlayer.accounts[i] + end + end + + return nil + end, + + getInventory = function(xPlayer, minimal) + if minimal then + local minimalInventory = {} + + for _, v in ipairs(xPlayer.inventory) do + if v.count > 0 then + minimalInventory[v.name] = v.count + end + end + + return minimalInventory + end + + return xPlayer.inventory + end, + + ---@deprecated use xPlayer.job instead + getJob = function(xPlayer) + return xPlayer.job + end, + + getLoadout = function(xPlayer, minimal) + if not minimal then + return xPlayer.loadout + end + local minimalLoadout = {} + + for _, v in ipairs(xPlayer.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, + + ---@deprecated use xPlayer.name instead + getName = function(xPlayer) return xPlayer.name end, + + setName = function(xPlayer, newName) + xPlayer.name = newName + Player(xPlayer.source).state:set('name', xPlayer.name, true) + end, + + setAccountMoney = function(xPlayer, accountName, money, reason) + reason = reason or 'unknown' + if not tonumber(money) then + return error(('Tried To Set Account ^5%s^1 For Player ^5%s^1 To An Invalid Number -> ^5%s^1'):format(accountName, xPlayer.playerId, money)) + end + if money >= 0 then + local account = xPlayer.getAccount(accountName) + + if account then + money = account.round and ESX.Math.Round(money) or money + xPlayer.accounts[account.index].money = money + + xPlayer.triggerEvent('esx:setAccountMoney', account) + TriggerEvent('esx:setAccountMoney', xPlayer.source, accountName, money, reason) + else + error(('Tried To Set Invalid Account ^5%s^1 For Player ^5%s^1!'):format(accountName, xPlayer.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, xPlayer.playerId, money)) + end + end, + + addAccountMoney = function(xPlayer, 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, xPlayer.playerId, money)) + return + end + if money > 0 then + local account = xPlayer.getAccount(accountName) + if account then + money = account.round and ESX.Math.Round(money) or money + xPlayer.accounts[account.index].money = xPlayer.accounts[account.index].money + money + + xPlayer.triggerEvent('esx:setAccountMoney', account) + TriggerEvent('esx:addAccountMoney', xPlayer.source, accountName, money, reason) + else + error(('Tried To Set Add To Invalid Account ^5%s^1 For Player ^5%s^1!'):format(accountName, xPlayer.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, xPlayer.playerId, money)) + end + end, + + removeAccountMoney = function(xPlayer, 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, xPlayer.playerId, money)) + return + end + if money > 0 then + local account = xPlayer.getAccount(accountName) + + if account then + money = account.round and ESX.Math.Round(money) or money + if xPlayer.accounts[account.index].money - money > xPlayer.accounts[account.index].money then + error(('Tried To Underflow Account ^5%s^1 For Player ^5%s^1!'):format(accountName, xPlayer.playerId)) + return + end + xPlayer.accounts[account.index].money = xPlayer.accounts[account.index].money - money + + xPlayer.triggerEvent('esx:setAccountMoney', account) + TriggerEvent('esx:removeAccountMoney', xPlayer.source, accountName, money, reason) + else + error(('Tried To Set Add To Invalid Account ^5%s^1 For Player ^5%s^1!'):format(accountName, xPlayer.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, xPlayer.playerId, money)) + end + end, + + getInventoryItem = function(xPlayer, itemName) + for _, v in ipairs(xPlayer.inventory) do + if v.name == itemName then + return v + end + end + return nil + end, + + addInventoryItem = function(xPlayer, itemName, count) + local item = xPlayer.getInventoryItem(itemName) + + if item then + count = ESX.Math.Round(count) + item.count = item.count + count + xPlayer.weight = xPlayer.weight + (item.weight * count) + + TriggerEvent('esx:onAddInventoryItem', xPlayer.source, item.name, item.count) + xPlayer.triggerEvent('esx:addInventoryItem', item.name, item.count) + end + end, + + removeInventoryItem = function(xPlayer, itemName, count) + local item = xPlayer.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 + xPlayer.weight = xPlayer.weight - (item.weight * count) + + TriggerEvent('esx:onRemoveInventoryItem', xPlayer.source, item.name, item.count) + xPlayer.triggerEvent('esx:removeInventoryItem', item.name, item.count) + end + else + error(('Player ID:^5%s Tried remove a Invalid count -> %s of %s'):format(xPlayer.playerId, count, itemName)) + end + end + end, + + setInventoryItem = function(xPlayer, itemName, count) + local item = xPlayer.getInventoryItem(itemName) + + if item and count >= 0 then + count = ESX.Math.Round(count) + + if count > item.count then + xPlayer.addInventoryItem(item.name, count - item.count) + else + xPlayer.removeInventoryItem(item.name, item.count - count) + end + end + end, + + ---@deprecated use xPlayer.weight instead + getWeight = function(xPlayer) return xPlayer.weight end, + + ---@deprecated use xPlayer.source instead + getSource = function(xPlayer) return xPlayer.source end, + + ---@deprecated use xPlayer.source instead + getPlayerId = function(xPlayer) return xPlayer.source end, + + ---@deprecated use xPlayer.maxWeight instead + getMaxWeight = function(xPlayer) return xPlayer.maxWeight end, + + canCarryItem = function(xPlayer, itemName, count) + if ESX.Items[itemName] then + local currentWeight, itemWeight = xPlayer.weight, ESX.Items[itemName].weight + local newWeight = currentWeight + (itemWeight * count) + + return newWeight <= xPlayer.maxWeight + else + print(('[^3WARNING^7] Item ^5\'%s\'^7 was used but does not exist!'):format(itemName)) + return false + end + end, + + canSwapItem = function(xPlayer, firstItem, firstItemCount, testItem, testItemCount) + local firstItemObject = xPlayer.getInventoryItem(firstItem) + if not firstItemObject then + return false + end + local testItemObject = xPlayer.getInventoryItem(testItem) + if not testItemObject then + return false + end + + if firstItemObject.count >= firstItemCount then + local weightWithoutFirstItem = ESX.Math.Round(xPlayer.weight - (firstItemObject.weight * firstItemCount)) + local weightWithTestItem = ESX.Math.Round(weightWithoutFirstItem + (testItemObject.weight * testItemCount)) + + return weightWithTestItem <= xPlayer.maxWeight + end + + return false + end, + + setMaxWeight = function(xPlayer, newWeight) + xPlayer.maxWeight = newWeight + + xPlayer.triggerEvent('esx:setMaxWeight', xPlayer.maxWeight) + end, + + setJob = function(xPlayer, newJob, grade, onDuty) + grade = tostring(grade) + local lastJob = xPlayer.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(xPlayer.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] + + xPlayer.job = { + id = jobObject.id, + name = jobObject.name, + label = jobObject.label, + 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 {}, + } + + xPlayer.metadata.jobDuty = onDuty + TriggerEvent('esx:setJob', xPlayer.source, xPlayer.job, lastJob) + xPlayer.triggerEvent('esx:setJob', xPlayer.job, lastJob) + Player(xPlayer.source).state:set('job', xPlayer.job, true) + end, + + addWeapon = function(xPlayer, weaponName, ammo) + if not xPlayer.hasWeapon(weaponName) then + local weaponLabel = ESX.GetWeaponLabel(weaponName) + + table.insert(xPlayer.loadout, { + name = weaponName, + ammo = ammo, + label = weaponLabel, + components = {}, + tintIndex = 0, + }) + + GiveWeaponToPed(GetPlayerPed(xPlayer.source), joaat(weaponName), ammo, false, false) + xPlayer.triggerEvent('esx:addInventoryItem', weaponLabel, false, true) + xPlayer.triggerEvent('esx:addLoadoutItem', weaponName, weaponLabel, ammo) + end + end, + + addWeaponComponent = function(xPlayer, weaponName, weaponComponent) + local loadoutNum , weapon = xPlayer.getWeapon(weaponName) + + if weapon then + local component = ESX.GetWeaponComponent(weaponName, weaponComponent) + + if component then + if not xPlayer.hasWeaponComponent(weaponName, weaponComponent) then + xPlayer.loadout[loadoutNum].components[#xPlayer.loadout[loadoutNum].components + 1] = weaponComponent + local componentHash = ESX.GetWeaponComponent(weaponName, weaponComponent).hash + GiveWeaponComponentToPed(GetPlayerPed(xPlayer.source), joaat(weaponName), componentHash) + xPlayer.triggerEvent('esx:addInventoryItem', component.label, false, true) + end + end + end + end, + + addWeaponAmmo = function(xPlayer, weaponName, ammoCount) + local _, weapon = xPlayer.getWeapon(weaponName) + + if weapon then + weapon.ammo = weapon.ammo + ammoCount + SetPedAmmo(GetPlayerPed(xPlayer.source), joaat(weaponName), weapon.ammo) + end + end, + + updateWeaponAmmo = function(xPlayer, weaponName, ammoCount) + local _, weapon = xPlayer.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 + xPlayer.removeWeapon(weaponName) + end + end + end, + + setWeaponTint = function(xPlayer, weaponName, weaponTintIndex) + local loadoutNum , weapon = xPlayer.getWeapon(weaponName) + + if weapon then + local _, weaponObject = ESX.GetWeapon(weaponName) + + if weaponObject.tints and weaponObject.tints[weaponTintIndex] then + xPlayer.loadout[loadoutNum].tintIndex = weaponTintIndex + xPlayer.triggerEvent('esx:setWeaponTint', weaponName, weaponTintIndex) + xPlayer.triggerEvent('esx:addInventoryItem', weaponObject.tints[weaponTintIndex], false, true) + end + end + end, + + getWeaponTint = function(xPlayer, weaponName) + local _, weapon = xPlayer.getWeapon(weaponName) + + if weapon then + return weapon.tintIndex + end + + return 0 + end, + + removeWeapon = function(xPlayer, weaponName) + local weaponLabel, playerPed = nil, GetPlayerPed(xPlayer.source) + + if not playerPed then + return error('xPlayer.removeWeapon ^5invalid^1 player ped!') + end + + for k, v in ipairs(xPlayer.loadout) do + if v.name == weaponName then + weaponLabel = v.label + + for _, v2 in ipairs(v.components) do + xPlayer.removeWeaponComponent(weaponName, v2) + end + + local weaponHash = joaat(v.name) + RemoveWeaponFromPed(playerPed, weaponHash) + SetPedAmmo(playerPed, weaponHash, 0) + + table.remove(xPlayer.loadout, k) + break + end + end + + if weaponLabel then + xPlayer.triggerEvent('esx:removeInventoryItem', weaponLabel, false, true) + xPlayer.triggerEvent('esx:removeLoadoutItem', weaponName, weaponLabel) + end + end, + + removeWeaponComponent = function(xPlayer, weaponName, weaponComponent) + local loadoutNum , weapon = xPlayer.getWeapon(weaponName) + + if weapon then + local component = ESX.GetWeaponComponent(weaponName, weaponComponent) + + if component then + if xPlayer.hasWeaponComponent(weaponName, weaponComponent) then + for k, v in ipairs(xPlayer.loadout[loadoutNum].components) do + if v == weaponComponent then + table.remove(xPlayer.loadout[loadoutNum].components, k) + break + end + end + + xPlayer.triggerEvent('esx:removeWeaponComponent', weaponName, weaponComponent) + xPlayer.triggerEvent('esx:removeInventoryItem', component.label, false, true) + end + end + end + end, + + removeWeaponAmmo = function(xPlayer, weaponName, ammoCount) + local _, weapon = xPlayer.getWeapon(weaponName) + + if weapon then + weapon.ammo = weapon.ammo - ammoCount + SetPedAmmo(GetPlayerPed(xPlayer.source), joaat(weaponName), weapon.ammo) + end + end, + + hasWeaponComponent = function(xPlayer, weaponName, weaponComponent) + local _, weapon = xPlayer.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, + + hasWeapon = function(xPlayer, weaponName) + for _, v in ipairs(xPlayer.loadout) do + if v.name == weaponName then + return true + end + end + + return false + end, + + hasItem = function(xPlayer, item) + for _, v in ipairs(xPlayer.inventory) do + if v.name == item and v.count >= 1 then + return v, v.count + end + end + + return false + end, + + getWeapon = function(xPlayer, weaponName) + for k, v in ipairs(xPlayer.loadout) do + if v.name == weaponName then + return k, v + end + end + + return nil, nil + end, + + showNotification = function(xPlayer, msg, notifyType, length, title, position) + xPlayer.triggerEvent('esx:showNotification', msg, notifyType, length, title, position) + end, + + showAdvancedNotification = function(xPlayer, sender, subject, msg, textureDict, iconType, flash, saveToBrief, hudColorIndex) + xPlayer.triggerEvent('esx:showAdvancedNotification', sender, subject, msg, textureDict, iconType, flash, saveToBrief, hudColorIndex) + end, + + showHelpNotification = function(xPlayer, msg, thisFrame, beep, duration) + xPlayer.triggerEvent('esx:showHelpNotification', msg, thisFrame, beep, duration) + end, + + getMeta = function(xPlayer, index, subIndex) + if not index then + return xPlayer.metadata + end + + if type(index) ~= 'string' then + error('xPlayer.getMeta ^5index^1 should be ^5string^1!') + return + end + + local metaData = xPlayer.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] = xPlayer.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, + + setMeta = function(xPlayer, 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 + + xPlayer.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 xPlayer.metadata[index] or type(xPlayer.metadata[index]) ~= 'table' then + xPlayer.metadata[index] = {} + end + + xPlayer.metadata[index] = type(xPlayer.metadata[index]) == 'table' and xPlayer.metadata[index] or {} + xPlayer.metadata[index][value] = subValue + end + xPlayer.triggerEvent('esx:updatePlayerData', 'metadata', xPlayer.metadata) + end, + + clearMeta = function(xPlayer, 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 = xPlayer.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 + xPlayer.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 + xPlayer.triggerEvent('esx:updatePlayerData', 'metadata', xPlayer.metadata) + end, + + executeCommand = function(xPlayer, command) + if type(command) ~= 'string' then + error('xPlayer.executeCommand must be of type string!') + return + end + + xPlayer.triggerEvent('esx:executeCommand', command) + end, + + syncInventory = Config.CustomInventory == 'ox' and function(xPlayer, weight, maxWeight, items, money) + xPlayer.weight, xPlayer.maxWeight = weight, maxWeight + xPlayer.inventory = items + + if not money then return end + for accountName, amount in pairs(money) do + local account = xPlayer.getAccount(accountName) + + if account and ESX.Math.Round(account.money) ~= amount then + account.money = amount + xPlayer.triggerEvent('esx:setAccountMoney', account) + TriggerEvent('esx:setAccountMoney', xPlayer.source, accountName, amount, 'Sync account with item') + end + end + end or nil +}, { + __index = function(self, k) + if otherResCheck() and (k ~= 'addHook' or k ~= 'removeHook') then return end + + return rawget(self, k) + end, + __newindex = function(self, k, v) + if otherResCheck() then return end + + rawset(self, k, v) + end, + __call = function(self, xPlayer, functionName) + if otherResCheck() then return end + + return function(...) + if self.hooks('functionName', functionName) then + for i=1, #self.hooks do + if self.hooks[i].functionName == functionName then + local _, result = pcall(self.hooks[i].functionReference, xPlayer, ...) + if (self.hooks[i].isJustAddition and result == false) or not self.hooks[i].isJustAddition then return end + end + end + end + + return self[functionName](xPlayer, ...) + end + end +}) + +ESX.XPlayerClass = setmetatable({ + addHook = function(functionName, isJustAddition, functionReference) + return exports.es_extended:addXPlayerHook(functionName, isJustAddition, functionReference) + end, + removeHook = function(hookId) + return exports.es_extended:removeXPlayerHook(hookId) + end +}, { + __newindex = nil +}) + +-- ox_inventory's overrides + +do + if Config.CustomInventory == 'ox' then + local resName , path = 'es_extended', 'server/classes/xPlayer/oxInv_overrides.lua' + load(LoadResourceFile(resName, path), ('@@%s/%s'):format(resName, path))() --[[@as fun(xPlayer: xPlayer) ]] + end +end + +-- Exports + +exports('RunStaticPlayerMethod', function(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('addXPlayerHook', function(functionName, isJustAddition, functionReference) + return XPlayerClass:addHook(functionName, isJustAddition, functionReference) +end) + +exports('removeXPlayerHook', function(hookId) + return XPlayerClass:removeHook(hookId) +end) \ No newline at end of file diff --git a/[core]/es_extended/server/classes/xPlayer/oxInv_overrides.lua b/[core]/es_extended/server/classes/xPlayer/oxInv_overrides.lua new file mode 100644 index 00000000..c390f53d --- /dev/null +++ b/[core]/es_extended/server/classes/xPlayer/oxInv_overrides.lua @@ -0,0 +1,132 @@ +local Inventory + +-- Event Handlers + +AddEventHandler('ox_inventory:loadInventory', function(module) + Inventory = module +end) + +-- Main Code + +do + IsAuthorized[#IsAuthorized+1] = true + + XPlayerClass.getInventory = function(xPlayer, minimal) + if not minimal then + return xPlayer.inventory + end + + local minimalInventory = {} + + for i=1, #xPlayer.inventory do + local curr = xPlayer.inventory[i] + if curr.count and curr.count > 0 then + minimalInventory[#minimalInventory + 1] = { + name = curr.name, + count = curr.count, + slot = i, + } + end + end + + return minimalInventory + end + + XPlayerClass.getLoadout = function() + return {} + end + + XPlayerClass.setAccountMoney = function(xPlayer, accountName, money, reason) + reason = reason or 'unknown' + if money < 0 then return end + local account = xPlayer.getAccount(accountName) + + if not account then return end + + money = account.round and ESX.Math.Round(money) or money + xPlayer.accounts[account.index].money = money + + xPlayer.triggerEvent('esx:setAccountMoney', account) + TriggerEvent('esx:setAccountMoney', xPlayer.source, accountName, money, reason) + if Inventory.accounts[accountName] then + Inventory.SetItem(xPlayer.source, accountName, money) + end + end + + XPlayerClass.addAccountMoney = function(xPlayer, accountName, money, reason) + reason = reason or 'unknown' + if money < 1 then return end + + local account = xPlayer.getAccount(accountName) + if not account then return end + + money = account.round and ESX.Math.Round(money) or money + xPlayer.accounts[account.index].money = xPlayer.accounts[account.index].money + money + xPlayer.triggerEvent('esx:setAccountMoney', account) + TriggerEvent('esx:addAccountMoney', xPlayer.source, accountName, money, reason) + if Inventory.accounts[accountName] then + Inventory.AddItem(xPlayer.source, accountName, money) + end + end + + XPlayerClass.removeAccountMoney = function(xPlayer, accountName, money, reason) + reason = reason or 'unknown' + if money < 1 then return end + + local account = xPlayer.getAccount(accountName) + if not account then return end + + money = account.round and ESX.Math.Round(money) or money + xPlayer.accounts[account.index].money = xPlayer.accounts[account.index].money - money + xPlayer.triggerEvent('esx:setAccountMoney', account) + TriggerEvent('esx:removeAccountMoney', xPlayer.source, accountName, money, reason) + if Inventory.accounts[accountName] then + Inventory.RemoveItem(xPlayer.source, accountName, money) + end + end + + XPlayerClass.getInventoryItem = function(xPlayer, name, metadata) + return Inventory.GetItem(xPlayer.source, name, metadata) + end + + do + local modules = { 'add', 'remove', 'set' } + for i=1, #modules do + local curr = modules[i] + XPlayerClass[('%sInventoryItem'):format(curr)] = function(xPlayer, ...) ---@diagnostic disable-line: param-type-mismatch + local params = { ... } + + if i ~= 3 then params[2] = params[2] or 1 end + + return Inventory[('%s%sItem'):format(curr:sub(1,1):upper(), curr:sub(2, #curr))](xPlayer.source, table.unpack(params)) + end + end + end + + XPlayerClass.canCarryItem = function(xPlayer, name, count, metadata) + return Inventory.CanCarryItem(xPlayer.source, name, count, metadata) + end + + XPlayerClass.canSwapItem = function(xPlayer, firstItem, firstItemCount, testItem, testItemCount) + return Inventory.CanSwapItem(xPlayer.source, firstItem, firstItemCount, testItem, testItemCount) + end + + XPlayerClass.setMaxWeight = function(xPlayer, newWeight) + xPlayer.maxWeight = newWeight + xPlayer.triggerEvent('esx:setMaxWeight', xPlayer.maxWeight) + return Inventory.SetMaxWeight(xPlayer.source, newWeight) + end + + do + local modules = { 'addWeapon', 'addWeaponComponent', 'addWeaponAmmo', 'addWeaponAmmo', 'updateWeaponAmmo', 'setWeaponTint', 'getWeaponTint', 'removeWeapon', 'removeWeaponComponent', 'removeWeaponAmmo', 'getWeapon', 'hasWeaponComponent', 'hasWeapon' } + for i=1, #modules do + XPlayerClass[modules[i]] = i <= (#modules - 2) and function()end or function() return false end + end + end + + XPlayerClass.hasItem = function(xPlayer, name, metadata) + return Inventory.GetItem(xPlayer.source, name, metadata) + end + + table.remove(IsAuthorized, #IsAuthorized) +end \ No newline at end of file diff --git a/[core]/es_extended/server/functions.lua b/[core]/es_extended/server/functions.lua index 4ff790b0..495dfc90 100644 --- a/[core]/es_extended/server/functions.lua +++ b/[core]/es_extended/server/functions.lua @@ -562,7 +562,7 @@ function ESX.RefreshJobs() if not Jobs then -- Fallback data, if no jobs exist - ESX.Jobs["unemployed"] = { name = "unemployed", label = "Unemployed", whitelisted = false, grades = { ["0"] = { grade = 0, name = "unemployed", label = "Unemployed", salary = 200, skin_male = {}, skin_female = {} } } } + ESX.Jobs["unemployed"] = { name = "unemployed", label = "Unemployed", whitelisted = false, grades = { ["0"] = { grade = 0, name = "unemployed", label = "Unemployed", salary = 200, skin_male = '[]', skin_female = '[]' } } } else ESX.Jobs = Jobs end diff --git a/[core]/es_extended/server/main.lua b/[core]/es_extended/server/main.lua index 30b24394..89b12502 100644 --- a/[core]/es_extended/server/main.lua +++ b/[core]/es_extended/server/main.lua @@ -40,7 +40,7 @@ local function createESXPlayer(identifier, playerId, data) end MySQL.prepare(newPlayer, parameters, function() - loadESXPlayer(identifier, playerId, true) + LoadESXPlayer(identifier, playerId, true) end) end @@ -61,7 +61,7 @@ local function onPlayerJoined(playerId) else local result = MySQL.scalar.await("SELECT 1 FROM users WHERE identifier = ?", { identifier }) if result then - loadESXPlayer(identifier, playerId, false) + LoadESXPlayer(identifier, playerId, false) else createESXPlayer(identifier, playerId) end @@ -119,7 +119,7 @@ if Config.Multichar then if data then createESXPlayer(identifier, src, data) else - loadESXPlayer(identifier, src, false) + LoadESXPlayer(identifier, src, false) end end end) @@ -183,75 +183,57 @@ if not Config.Multichar then end) end -function loadESXPlayer(identifier, playerId, isNew) - local userData = { +function LoadESXPlayer(identifier, playerId, isNew) + local result = MySQL.prepare.await(loadPlayer, { identifier }) + result.accounts = type(result.accounts) == 'string' and json.decode(result.accounts) or {} + + local job = ESX.Jobs[result.job] or ESX.Jobs['unemployed'] + local grade = job.grades[tostring(result.job_grade)] or job.grades['0'] + local userData = { + steamName = GetPlayerName(playerId), + name = ('%s %s'):format(result.firstname, result.lastname), + identifier = identifier, accounts = {}, inventory = {}, loadout = {}, + ssn = result.ssn, weight = 0, - name = GetPlayerName(playerId), - identifier = identifier, - firstName = "John", - lastName = "Doe", - dateofbirth = "01/01/2000", - height = 120, - dead = false, + job = { + id = job.id, + name = job.name, + label = job.label, + grade = tonumber(grade), + grade_name = grade.name, + grade_label = grade.label, + grade_salary = grade.salary, + skin_male = json.decode(grade.skin_male or '[]'), + skin_female = json.decode(grade.skin_female or '[]'), + }, + group = result.group == 'superadmin' and 'admin' or result.group and result.group or 'user', + coords = json.decode(result.position) or Config.DefaultSpawns[ESX.Math.Random(1,#Config.DefaultSpawns)], + skin = (result.skin and result.skin ~= '') and json.decode(result.skin) or { sex = 0 }, + metadata = (result.metadata and result.metadata ~= '') and json.decode(result.metadata) or {} } - 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 + for k,v in pairs(Config.Accounts) do + v.round = v.round or v.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, + name = k, + money = result.accounts[k] or Config.StartingAccountMoney[k] or 0, + label = v.label, + round = v.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, - - 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 {} + -- Inventory + 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 + local count = inventory[name] or 0 userData.weight += (count * item.weight) - userData.inventory[#userData.inventory + 1] = { name = name, count = count, @@ -262,33 +244,16 @@ function loadESXPlayer(identifier, playerId, isNew) 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) + -- Loadout + if result.loadout and result.loadout ~= '' then + for name, weapon in pairs(json.decode(result.loadout)) do + local label = ESX.GetWeaponLabel(name) if label then userData.loadout[#userData.loadout + 1] = { name = name, @@ -300,21 +265,14 @@ function loadESXPlayer(identifier, playerId, isNew) end end end + elseif result.inventory and result.inventory ~= '' then + userData.inventory = json.decode(result.inventory) 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) + local xPlayer = GetXPlayer(playerId, userData) - GlobalState["playerCount"] = GlobalState["playerCount"] + 1 + GlobalState.playerCount += 1 ESX.Players[playerId] = xPlayer Core.playersByIdentifier[identifier] = xPlayer @@ -344,10 +302,12 @@ function loadESXPlayer(identifier, playerId, isNew) end end - TriggerEvent("esx:playerLoaded", playerId, xPlayer, isNew) userData.money = xPlayer.getMoney() userData.maxWeight = xPlayer.getMaxWeight() userData.variables = xPlayer.variables or {} + + TriggerEvent("esx:playerLoaded", playerId, xPlayer, isNew) + xPlayer.triggerEvent("esx:registerSuggestions", Core.RegisteredCommands) xPlayer.triggerEvent("esx:playerLoaded", userData, isNew, userData.skin) if not Config.CustomInventory then @@ -356,7 +316,10 @@ function loadESXPlayer(identifier, playerId, isNew) setPlayerInventory(playerId, xPlayer, userData.inventory, isNew) end - xPlayer.triggerEvent("esx:registerSuggestions", Core.RegisteredCommands) + if not ESX.DoesJobExist(result.job.name, tostring(result.job_grade) == 'NULL' and '0' or result.job_grade) then + warn(('^7Ignoring invalid job for ^5%s^7 [job: ^5%s^7, grade: ^5%s^7]'):format(identifier, result.job, result.grade)) + end + print(('[^2INFO^0] Player ^5"%s"^0 has connected to the server. ID: ^5%s^7'):format(xPlayer.getName(), playerId)) end diff --git a/[core]/es_extended/server/migration/main.lua b/[core]/es_extended/server/migration/main.lua index 378894fd..cf6e52b3 100644 --- a/[core]/es_extended/server/migration/main.lua +++ b/[core]/es_extended/server/migration/main.lua @@ -8,7 +8,7 @@ RegisterCommand("resetmigrations", function(src) 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) +end, false) local migrationsRan = 0 local restartRequired = false diff --git a/[core]/es_extended/server/modules/getXPlayer.lua b/[core]/es_extended/server/modules/getXPlayer.lua new file mode 100644 index 00000000..a6866844 --- /dev/null +++ b/[core]/es_extended/server/modules/getXPlayer.lua @@ -0,0 +1,36 @@ +---@param playerId integer +---@param data { identifier: string, ssn: string, group: string, accounts: ESXAccount[], inventory: table, weight: number, job: ESXJob, loadout: ESXInventoryWeapon[], steamName: string, coords: vector4|{x: number, y: number, z: number, heading: number}, metadata: table } +---@return xPlayer +function GetXPlayer(playerId, data) + local xPlayer = { + playerId = playerId, + source = playerId, + variables = {}, + job = {}, + metadata = {}, + maxWeight = Config.MaxWeight, + lastPlaytime = 0, + paycheckEnabled = true, + admin = Core.IsPlayerAdmin(playerId), + state = Player(playerId).state, + license = GetPlayerIdentifierByType(tostring(playerId), 'license') + } + + for k,v in pairs(data) do + xPlayer[k] = v + end + + if type(data.metadata.jobDuty) ~= 'boolean' then + xPlayer.metadata.jobDuty = data.job.name ~= 'unemployed' and Config.DefaultJobDuty or false + end + + IsAuthorized[#IsAuthorized+1] = true + + for k,v in pairs(XPlayerClass) do + if type(v) == 'function' then + xPlayer[k] = XPlayerClass(xPlayer, k) + end + end + + return xPlayer +end \ No newline at end of file diff --git a/[core]/es_extended/shared/modules/table.lua b/[core]/es_extended/shared/modules/table.lua index ab4b147d..a80347eb 100644 --- a/[core]/es_extended/shared/modules/table.lua +++ b/[core]/es_extended/shared/modules/table.lua @@ -225,7 +225,7 @@ function ESX.Table.Sort(t, order) end ---@param t table ----@return Array +---@return any[] function ESX.Table.ToArray(t) local array = {} for _, v in pairs(t) do diff --git a/[core]/esx_notify/Notify.lua b/[core]/esx_notify/Notify.lua index d690fbd6..61ebe881 100644 --- a/[core]/esx_notify/Notify.lua +++ b/[core]/esx_notify/Notify.lua @@ -64,26 +64,26 @@ RegisterNetEvent("ESX:Notify", Notify) if Debug then RegisterCommand("oldnotify", function() - ESX.ShowNotification('No Waypoint Set.', true, false, 140) - end) + ESX.ShowNotification('No Waypoint Set.', true, false, 140) ---@diagnostic disable-line: param-type-mismatch + end, false) RegisterCommand("notify", function() ESX.ShowNotification("You Received
1x ball!", "success", 3000) - end) + end, false) RegisterCommand("notify1", function() ESX.ShowNotification("Well ~g~Done~s~!", "success", 3000, "Achievement") - end) + end, false) RegisterCommand("notify2", function() ESX.ShowNotification("Information Received", "info", 3000, "System Info") - end) + end, false) RegisterCommand("notify3", function() ESX.ShowNotification("You Did something ~r~WRONG~s~!", "error", 3000, "Error") - end) + end, false) RegisterCommand("notify4", function() ESX.ShowNotification("You Did something ~r~WRONG~s~!", "warning", 3000, "~y~Warning~s~") - end) + end, false) end \ No newline at end of file diff --git a/[core]/skinchanger/client/main.lua b/[core]/skinchanger/client/main.lua index 67be80d5..b386ef4b 100644 --- a/[core]/skinchanger/client/main.lua +++ b/[core]/skinchanger/client/main.lua @@ -278,7 +278,7 @@ exports("LoadSkin", function(skin) end) exports("GetData", function(noMax) - return SkinChanger:GetData(noMax) + return SkinChanger:GetData(noMax) ---@diagnostic disable-line: redundant-return-value end) exports("LoadClothes", function(playerSkin, clothesSkin) From 48194a78acbd43448f6bca1d6760467b536966e7 Mon Sep 17 00:00:00 2001 From: HR0110 Date: Sun, 14 Sep 2025 18:56:46 +0300 Subject: [PATCH 2/2] fix(es_extended/server/classes/xPlayer/classes): fixed the xPlayerClass's fields and returned the default StaticPlayer fields --- .../server/classes/xPlayer/classes.lua | 82 ++++++++++--------- [core]/es_extended/server/functions.lua | 4 +- 2 files changed, 44 insertions(+), 42 deletions(-) diff --git a/[core]/es_extended/server/classes/xPlayer/classes.lua b/[core]/es_extended/server/classes/xPlayer/classes.lua index 3bb46baa..b05706a8 100644 --- a/[core]/es_extended/server/classes/xPlayer/classes.lua +++ b/[core]/es_extended/server/classes/xPlayer/classes.lua @@ -1,5 +1,5 @@ ---@class ESXAccount ----@field name string # Account name (e.g., 'bank', 'money'). +---@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. @@ -43,47 +43,48 @@ ---@field hash string|number # Component hash or identifier. ---@class StaticPlayer +---@field src number # Player's server ID. --- Money Functions ----@field setMoney { __call: fun(self: StaticPlayer, money: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Set player's cash balance. ----@field getMoney { (__call: fun(self: StaticPlayer): number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Get player's current cash balance. ----@field addMoney { __call: fun(self: StaticPlayer, money: number, reason: string), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Add money to the player's cash balance. ----@field removeMoney { __call: fun(self: StaticPlayer, money: number, reason: string), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Remove money from the player's cash balance. ----@field setAccountMoney { __call: fun(self: StaticPlayer, accountName: string, money: number, reason?: string), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Set specific account balance. ----@field addAccountMoney { __call: fun(self: StaticPlayer, accountName: string, money: number, reason?: string), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Add money to an account. ----@field removeAccountMoney { __call: fun(self: StaticPlayer, accountName: string, money: number, reason?: string), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Remove money from an account. ----@field getAccount { (__call: fun(self: StaticPlayer, account: string): ESXAccount?), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Get account data by name. ----@field getAccounts { (__call: fun(self: StaticPlayer, minimal?: boolean): ESXAccount[]|table), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Get all accounts, optionally minimal. +---@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 # Get all accounts, optionally minimal. --- Inventory Functions ----@field getInventory { (__call: fun(self: StaticPlayer, minimal?: boolean): ESXInventoryItem[]|table), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Get inventory, optionally minimal. ----@field getInventoryItem { (__call: fun(self: StaticPlayer, itemName: string): ESXInventoryItem?), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Get a specific item from inventory. ----@field addInventoryItem { __call: fun(self: StaticPlayer, itemName: string, count: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Add items to inventory. ----@field removeInventoryItem { __call: fun(self: StaticPlayer, itemName: string, count: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Remove items from inventory. ----@field setInventoryItem { __call: fun(self: StaticPlayer, itemName: string, count: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Set item count in inventory. ----@field getWeight { (__call: fun(self: StaticPlayer): number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Get current carried weight. ----@field getMaxWeight { (__call: fun(self: StaticPlayer): number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Get maximum carry weight. ----@field setMaxWeight { __call: fun(self: StaticPlayer, newWeight: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Set maximum carry weight. ----@field canCarryItem { (__call: fun(self: StaticPlayer, itemName: string, count: number): boolean), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Check if player can carry more of an item. ----@field canSwapItem { (__call: fun(self: StaticPlayer, firstItem: string, firstItemCount: number, testItem: string, testItemCount: number): boolean), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Check if items can be swapped. ----@field hasItem { (__call: fun(self: StaticPlayer, item: string): ESXInventoryItem|false, number?), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Check if player has an item. ----@field getLoadout { (__call: fun(self: StaticPlayer, minimal?: boolean): ESXInventoryWeapon[]|table), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Get player's weapon loadout. +---@field getInventory fun(minimal?: boolean): ESXInventoryItem[]|table # 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 # 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 { __call: fun(self: StaticPlayer, weaponName: string, ammo: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Give player a weapon. ----@field removeWeapon { __call: fun(self: StaticPlayer, weaponName: string), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Remove weapon from player. ----@field hasWeapon { (__call: fun(self: StaticPlayer, weaponName: string): boolean), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Check if player has a weapon. ----@field getWeapon { (__call: fun(self: StaticPlayer, weaponName: string): number?, table?), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Get weapon ammo & components. ----@field addWeaponAmmo { __call: fun(self: StaticPlayer, weaponName: string, ammoCount: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Add ammo to a weapon. ----@field removeWeaponAmmo { __call: fun(self: StaticPlayer, weaponName: string, ammoCount: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Remove ammo from a weapon. ----@field updateWeaponAmmo { __call: fun(self: StaticPlayer, weaponName: string, ammoCount: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Update ammo count for a weapon. ----@field addWeaponComponent { __call: fun(self: StaticPlayer, weaponName: string, weaponComponent: string), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Add component to weapon. ----@field removeWeaponComponent { __call: fun(self: StaticPlayer, weaponName: string, weaponComponent: string), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Remove component from weapon. ----@field hasWeaponComponent { (__call: fun(self: StaticPlayer, weaponName: string, weaponComponent: string): boolean), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Check if weapon has component. ----@field setWeaponTint { __call: fun(self: StaticPlayer, weaponName: string, weaponTintIndex: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Set weapon tint. ----@field getWeaponTint { (__call: fun(self: StaticPlayer, weaponName: string): number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Get weapon tint. +---@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. @@ -103,9 +104,9 @@ ---@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 { __call: fun(self: StaticPlayer, msg: string, notifyType?: string, length?: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Show a simple notification. ----@field showAdvancedNotification { __call: fun(self: StaticPlayer, sender: string, subject: string, msg: string, textureDict: string, iconType: string, flash: boolean, saveToBrief: boolean, hudColorIndex: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Show advanced notification. ----@field showHelpNotification { __call: fun(self: StaticPlayer, msg: string, thisFrame?: boolean, beep?: boolean, duration?: number), set: fun(self: StaticPlayer, isJustAddition: boolean, fn: function) } # Show help notification. +---@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. @@ -134,8 +135,9 @@ ---@field admin boolean # Whether the player is an admin. ---@class xPlayerClass:xPlayer ----@field hooks { functionName: string, isJustAddition: boolean, functionReference: fun(xPlayer: xPlayer, ...: any): any }[] ----@field __removeHook fun(self: xPlayerClass, hookId: integer): boolean Returns success status +---@field hooks { hookId: string, functionName: string, invokerRes: string, isJustAddition: boolean, functionReference: fun(xPlayer: xPlayer, ...: any): any }[] +---@field addHook fun(self: xPlayerClass, functionName: string, isJustAddition: boolean, functionReference: fun(xPlayer: xPlayer, ...: any): any): string Returns the hookId of the created hook +---@field removeHook fun(self: xPlayerClass, hookId: string): boolean Returns success status ---@field kick function ---@field setMoney function ---@field getMoney function @@ -171,4 +173,4 @@ ---@field showHelpNotification function ---@field __index fun(self: xPlayerClass, k: string): any ---@field __newindex fun(self: xPlayerClass, k: string, v: any) ----@field __call fun(self: xPlayerClass, playerId: integer, data: { identifier: string, ssn: string, group: string, accounts: ESXAccount[], inventory: table, weight: number, job: ESXJob, loadout: ESXInventoryWeapon[], steamName: string, coords: vector4|{x: number, y: number, z: number, heading: number}, metadata: table }): xPlayer? \ No newline at end of file +---@field __call fun(self: xPlayerClass, xPlayer: xPlayer, functionName): fun(...: any): any Function to convert each function from the xPlayer class to actual xPlayer function (only available to call from es_extended) \ No newline at end of file diff --git a/[core]/es_extended/server/functions.lua b/[core]/es_extended/server/functions.lua index 495dfc90..903271da 100644 --- a/[core]/es_extended/server/functions.lua +++ b/[core]/es_extended/server/functions.lua @@ -685,7 +685,7 @@ if not Config.CustomInventory then for itemName, _ in pairs(minimalInv) do if not ESX.Items[itemName] then - xPlayer.setInventoryItem(itemName, 0) + xPlayer.setInventoryItem(itemName --[[@as string]], 0) minimalInv[itemName] = nil end end @@ -695,7 +695,7 @@ if not Config.CustomInventory then for itemName, itemData in pairs(ESX.Items) do xPlayer.inventory[playerInvIndex] = { name = itemName, - count = minimalInv[itemName] or 0, + count = minimalInv[itemName].count or 0, label = itemData.label, weight = itemData.weight, usable = Core.UsableItemsCallbacks[itemName] ~= nil,