mirror of
https://github.com/esx-framework/esx_core.git
synced 2026-09-04 08:13:21 +00:00
Merge branch 'dev' into job-duty
This commit is contained in:
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"Lua.diagnostics.globals": [
|
||||
"MySQL",
|
||||
"GetPlayerTimeOnline"
|
||||
]
|
||||
}
|
||||
@@ -78,17 +78,3 @@ This code of conduct and its related procedures also applies to unacceptable beh
|
||||
## 10. Contact info
|
||||
|
||||
esxframework@gmail.com
|
||||
|
||||
## 11. License and attribution
|
||||
|
||||
The Citizen Code of Conduct is distributed by [Stumptown Syndicate](http://stumptownsyndicate.org) under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/).
|
||||
|
||||
Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy).
|
||||
|
||||
_Revision 2.3. Posted 6 March 2017._
|
||||
|
||||
_Revision 2.2. Posted 4 February 2016._
|
||||
|
||||
_Revision 2.1. Posted 23 June 2014._
|
||||
|
||||
_Revision 2.0, adopted by the [Stumptown Syndicate](http://stumptownsyndicate.org) board on 10 January 2013. Posted 17 March 2013._
|
||||
|
||||
@@ -1,8 +1,32 @@
|
||||
exports("getSharedObject", function()
|
||||
return ESX
|
||||
end)
|
||||
Core = {}
|
||||
ESX.PlayerData = {}
|
||||
ESX.playerId = PlayerId()
|
||||
ESX.serverId = GetPlayerServerId(ESX.playerId)
|
||||
ESX.PlayerLoaded = false
|
||||
Core.Input = {}
|
||||
ESX.UI = {}
|
||||
ESX.UI.Menu = {}
|
||||
ESX.UI.Menu.RegisteredTypes = {}
|
||||
ESX.UI.Menu.Opened = {}
|
||||
|
||||
AddEventHandler("esx:getSharedObject", function()
|
||||
local Invoke = GetInvokingResource()
|
||||
error(("Resource ^5%s^7 Used the ^5getSharedObject^7 Event, this event ^1no longer exists!^7 Visit https://documentation.esx-framework.org/tutorials/tutorials-esx/sharedevent for how to fix!"):format(Invoke))
|
||||
ESX.Game = {}
|
||||
ESX.Game.Utils = {}
|
||||
|
||||
ESX.Scaleform = {}
|
||||
ESX.Scaleform.Utils = {}
|
||||
|
||||
ESX.Streaming = {}
|
||||
|
||||
CreateThread(function()
|
||||
while not Config.Multichar do
|
||||
Wait(100)
|
||||
|
||||
if NetworkIsPlayerActive(ESX.playerId) then
|
||||
ESX.DisableSpawnManager()
|
||||
DoScreenFadeOut(0)
|
||||
Wait(500)
|
||||
TriggerServerEvent("esx:onPlayerJoined")
|
||||
break
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -1,21 +1,3 @@
|
||||
ESX = {}
|
||||
Core = {}
|
||||
ESX.PlayerData = {}
|
||||
ESX.PlayerLoaded = false
|
||||
Core.Input = {}
|
||||
ESX.UI = {}
|
||||
ESX.UI.Menu = {}
|
||||
ESX.UI.Menu.RegisteredTypes = {}
|
||||
ESX.UI.Menu.Opened = {}
|
||||
|
||||
ESX.Game = {}
|
||||
ESX.Game.Utils = {}
|
||||
|
||||
ESX.Scaleform = {}
|
||||
ESX.Scaleform.Utils = {}
|
||||
|
||||
ESX.Streaming = {}
|
||||
|
||||
function ESX.IsPlayerLoaded()
|
||||
return ESX.PlayerLoaded
|
||||
end
|
||||
@@ -28,13 +10,22 @@ local addonResourcesState = {
|
||||
['esx_progressbar'] = GetResourceState('esx_progressbar') ~= 'missing',
|
||||
['esx_notify'] = GetResourceState('esx_notify') ~= 'missing',
|
||||
['esx_textui'] = GetResourceState('esx_textui') ~= 'missing',
|
||||
['esx_context'] = GetResourceState('esx_context') ~= 'missing'
|
||||
['esx_context'] = GetResourceState('esx_context') ~= 'missing',
|
||||
}
|
||||
|
||||
local function IsResourceFound(resource)
|
||||
return addonResourcesState[resource] or print(('[^1ERROR^7] ^5%s^7 is Missing!'):format(resource))
|
||||
return addonResourcesState[resource] or error(('Resource [^5%s^1] is Missing!'):format(resource))
|
||||
end
|
||||
|
||||
function ESX.DisableSpawnManager()
|
||||
if GetResourceState("spawnmanager") == "started" then
|
||||
exports.spawnmanager:setAutoSpawn(false)
|
||||
end
|
||||
end
|
||||
|
||||
---@param items string | table The item(s) to search for
|
||||
---@param count? boolean Whether to return the count of the item as well
|
||||
---@return table | number
|
||||
function ESX.SearchInventory(items, count)
|
||||
local item
|
||||
if type(items) == 'string' then
|
||||
@@ -58,6 +49,9 @@ function ESX.SearchInventory(items, count)
|
||||
return not item and data or data[item]
|
||||
end
|
||||
|
||||
---@param key string Table key to set
|
||||
---@param val any Value to set
|
||||
---@return nil
|
||||
function ESX.SetPlayerData(key, val)
|
||||
local current = ESX.PlayerData[key]
|
||||
ESX.PlayerData[key] = val
|
||||
@@ -68,10 +62,67 @@ function ESX.SetPlayerData(key, val)
|
||||
end
|
||||
end
|
||||
|
||||
function ESX.Progressbar(...)
|
||||
return IsResourceFound('esx_progressbar') and exports['esx_progressbar']:Progressbar(...)
|
||||
---@param freeze boolean Whether to freeze the player
|
||||
---@return nil
|
||||
function Core.FreezePlayer(freeze)
|
||||
local ped = PlayerPedId()
|
||||
SetPlayerControl(ESX.playerId, not freeze, 0)
|
||||
|
||||
if freeze then
|
||||
SetEntityCollision(ped, false, false)
|
||||
FreezeEntityPosition(ped, true)
|
||||
else
|
||||
SetEntityCollision(ped, true, true)
|
||||
FreezeEntityPosition(ped, false)
|
||||
end
|
||||
end
|
||||
|
||||
---@param skin table Skin data to set
|
||||
---@param coords table Coords to spawn the player at
|
||||
---@param cb function Callback function
|
||||
---@return nil
|
||||
function ESX.SpawnPlayer(skin, coords, cb)
|
||||
local p = promise.new()
|
||||
TriggerEvent("skinchanger:loadSkin", skin, function()
|
||||
p:resolve()
|
||||
end)
|
||||
Citizen.Await(p)
|
||||
|
||||
RequestCollisionAtCoord(coords.x, coords.y, coords.z)
|
||||
|
||||
local playerPed = PlayerPedId()
|
||||
local timer = GetGameTimer()
|
||||
|
||||
Core.FreezePlayer(true)
|
||||
SetEntityCoordsNoOffset(playerPed, coords.x, coords.y, coords.z, false, false, true)
|
||||
SetEntityHeading(playerPed, coords.heading)
|
||||
|
||||
RequestCollisionAtCoord(coords.x, coords.y, coords.z)
|
||||
while not HasCollisionLoadedAroundEntity(playerPed) and (GetGameTimer() - timer) < 5000 do
|
||||
Wait(0)
|
||||
end
|
||||
|
||||
NetworkResurrectLocalPlayer(coords.x, coords.y, coords.z, coords.heading, 0, true)
|
||||
TriggerEvent('playerSpawned', coords)
|
||||
cb()
|
||||
end
|
||||
|
||||
---@param message string
|
||||
---@param length? number Timeout in milliseconds
|
||||
---@param options? ProgressBarOptions
|
||||
---@return boolean Success Whether the progress bar was successfully created or not
|
||||
function ESX.Progressbar(message, length, options)
|
||||
return IsResourceFound('esx_progressbar') and exports['esx_progressbar']:Progressbar(message, length, options)
|
||||
end
|
||||
|
||||
function ESX.CancelProgressbar()
|
||||
return IsResourceFound('esx_progressbar') and exports['esx_progressbar']:CancelProgressbar()
|
||||
end
|
||||
|
||||
---@param message string The message to show
|
||||
---@param notifyType? string The type of notification to show
|
||||
---@param length? number The length of the notification
|
||||
---@return nil
|
||||
function ESX.ShowNotification(message, notifyType, length)
|
||||
return IsResourceFound('esx_notify') and exports['esx_notify']:Notify(notifyType, length, message)
|
||||
end
|
||||
@@ -80,10 +131,20 @@ function ESX.TextUI(...)
|
||||
return IsResourceFound('esx_textui') and exports['esx_textui']:TextUI(...)
|
||||
end
|
||||
|
||||
---@return nil
|
||||
function ESX.HideUI()
|
||||
return IsResourceFound('esx_textui') and exports['esx_textui']:HideUI()
|
||||
end
|
||||
|
||||
---@param sender string
|
||||
---@param subject string
|
||||
---@param msg string
|
||||
---@param textureDict string
|
||||
---@param iconType integer
|
||||
---@param flash boolean
|
||||
---@param saveToBrief? boolean
|
||||
---@param hudColorIndex? integer
|
||||
---@return nil
|
||||
function ESX.ShowAdvancedNotification(sender, subject, msg, textureDict, iconType, flash, saveToBrief, hudColorIndex)
|
||||
AddTextEntry("esxAdvancedNotification", msg)
|
||||
BeginTextCommandThefeedPost("esxAdvancedNotification")
|
||||
@@ -94,24 +155,35 @@ function ESX.ShowAdvancedNotification(sender, subject, msg, textureDict, iconTyp
|
||||
EndTextCommandThefeedPostTicker(flash, saveToBrief == nil or saveToBrief)
|
||||
end
|
||||
|
||||
---@param msg string The message to show
|
||||
---@param thisFrame? boolean Whether to show the message this frame
|
||||
---@param beep? boolean Whether to beep
|
||||
---@param duration? number The duration of the message
|
||||
---@return nil
|
||||
function ESX.ShowHelpNotification(msg, thisFrame, beep, duration)
|
||||
AddTextEntry("esxHelpNotification", msg)
|
||||
if thisFrame then
|
||||
DisplayHelpTextThisFrame("esxHelpNotification")
|
||||
DisplayHelpTextThisFrame("esxHelpNotification", false)
|
||||
else
|
||||
BeginTextCommandDisplayHelp("esxHelpNotification")
|
||||
EndTextCommandDisplayHelp(0, false, beep == nil or beep, duration or -1)
|
||||
end
|
||||
end
|
||||
|
||||
---@param msg string The message to show
|
||||
---@param coords table The coords to show the message at
|
||||
---@return nil
|
||||
function ESX.ShowFloatingHelpNotification(msg, coords)
|
||||
AddTextEntry("esxFloatingHelpNotification", msg)
|
||||
SetFloatingHelpTextWorldPosition(1, coords)
|
||||
SetFloatingHelpTextWorldPosition(1, coords.x, coords.y, coords.z)
|
||||
SetFloatingHelpTextStyle(1, 1, 2, -1, 3, 0)
|
||||
BeginTextCommandDisplayHelp("esxFloatingHelpNotification")
|
||||
EndTextCommandDisplayHelp(2, false, false, -1)
|
||||
end
|
||||
|
||||
---@param msg string The message to show
|
||||
---@param time number The duration of the message
|
||||
---@return nil
|
||||
function ESX.DrawMissionText(msg, time)
|
||||
ClearPrints()
|
||||
BeginTextCommandPrint('STRING')
|
||||
@@ -119,6 +191,8 @@ function ESX.DrawMissionText(msg, time)
|
||||
EndTextCommandPrint(time, true)
|
||||
end
|
||||
|
||||
---@param str string The string to hash
|
||||
---@return string The hashed string
|
||||
function ESX.HashString(str)
|
||||
return ('~INPUT_%s~'):format(('%x'):format(joaat(str) & 0x7fffffff + 2 ^ 31):upper())
|
||||
end
|
||||
@@ -139,16 +213,25 @@ function ESX.RefreshContext(...)
|
||||
return IsResourceFound('esx_context') and exports['esx_context']:Refresh(...)
|
||||
end
|
||||
|
||||
---@param command_name string The command name
|
||||
---@param label string The label to show
|
||||
---@param input_group string The input group
|
||||
---@param key string The key to bind
|
||||
---@param on_press function The function to call on press
|
||||
---@param on_release? function The function to call on release
|
||||
function ESX.RegisterInput(command_name, label, input_group, key, on_press, on_release)
|
||||
local command = on_release and '+' .. command_name or command_name
|
||||
RegisterCommand(command, on_press)
|
||||
RegisterCommand(command, on_press, false)
|
||||
Core.Input[command_name] = ESX.HashString(command)
|
||||
if on_release then
|
||||
RegisterCommand('-' .. command_name, on_release)
|
||||
RegisterCommand('-' .. command_name, on_release, false)
|
||||
end
|
||||
RegisterKeyMapping(command, label or '', input_group or 'keyboard', key or '')
|
||||
end
|
||||
|
||||
---@param menuType string
|
||||
---@param open function The function to call on open
|
||||
---@param close function The function to call on close
|
||||
function ESX.UI.Menu.RegisterType(menuType, open, close)
|
||||
ESX.UI.Menu.RegisteredTypes[menuType] = {
|
||||
open = open,
|
||||
@@ -156,7 +239,34 @@ function ESX.UI.Menu.RegisterType(menuType, open, close)
|
||||
}
|
||||
end
|
||||
|
||||
---@class ESXMenu
|
||||
---@field type string
|
||||
---@field namespace string
|
||||
---@field resourceName string
|
||||
---@field name string
|
||||
---@field data table
|
||||
---@field submit? function
|
||||
---@field cancel? function
|
||||
---@field change? function
|
||||
---@field close function
|
||||
---@field update function
|
||||
---@field refresh function
|
||||
---@field setElement function
|
||||
---@field setElements function
|
||||
---@field setTitle function
|
||||
---@field removeElement function
|
||||
|
||||
---@param menuType string
|
||||
---@param namespace string
|
||||
---@param name string
|
||||
---@param data table
|
||||
---@param submit? function
|
||||
---@param cancel? function
|
||||
---@param change? function
|
||||
---@param close? function
|
||||
---@return ESXMenu
|
||||
function ESX.UI.Menu.Open(menuType, namespace, name, data, submit, cancel, change, close)
|
||||
---@class ESXMenu
|
||||
local menu = {}
|
||||
|
||||
menu.type = menuType
|
||||
@@ -237,6 +347,11 @@ function ESX.UI.Menu.Open(menuType, namespace, name, data, submit, cancel, chang
|
||||
return menu
|
||||
end
|
||||
|
||||
---@param menuType string
|
||||
---@param namespace string
|
||||
---@param name string
|
||||
---@param cancel? boolean Should the close be classified as a cancel
|
||||
---@return nil
|
||||
function ESX.UI.Menu.Close(menuType, namespace, name, cancel)
|
||||
for i = 1, #ESX.UI.Menu.Opened, 1 do
|
||||
if ESX.UI.Menu.Opened[i] then
|
||||
@@ -252,6 +367,8 @@ function ESX.UI.Menu.Close(menuType, namespace, name, cancel)
|
||||
end
|
||||
end
|
||||
|
||||
---@param cancel? boolean Should the close be classified as a cancel
|
||||
---@return nil
|
||||
function ESX.UI.Menu.CloseAll(cancel)
|
||||
for i = 1, #ESX.UI.Menu.Opened, 1 do
|
||||
if ESX.UI.Menu.Opened[i] then
|
||||
@@ -265,6 +382,10 @@ function ESX.UI.Menu.CloseAll(cancel)
|
||||
end
|
||||
end
|
||||
|
||||
---@param menuType string
|
||||
---@param namespace string
|
||||
---@param name string
|
||||
---@return ESXMenu | nil
|
||||
function ESX.UI.Menu.GetOpened(menuType, namespace, name)
|
||||
for i = 1, #ESX.UI.Menu.Opened, 1 do
|
||||
if ESX.UI.Menu.Opened[i] then
|
||||
@@ -275,12 +396,17 @@ function ESX.UI.Menu.GetOpened(menuType, namespace, name)
|
||||
end
|
||||
end
|
||||
|
||||
---@return ESXMenu[]
|
||||
function ESX.UI.Menu.GetOpenedMenus()
|
||||
return ESX.UI.Menu.Opened
|
||||
end
|
||||
|
||||
ESX.UI.Menu.IsOpen = ESX.UI.Menu.GetOpened
|
||||
|
||||
---@param add boolean Whether the item is being added or removed
|
||||
---@param item string The item to show
|
||||
---@param count number How many of the item to show
|
||||
---@return nil
|
||||
function ESX.UI.ShowInventoryItemNotification(add, item, count)
|
||||
SendNUIMessage({
|
||||
action = "inventoryNotification",
|
||||
@@ -290,6 +416,8 @@ function ESX.UI.ShowInventoryItemNotification(add, item, count)
|
||||
})
|
||||
end
|
||||
|
||||
---@param ped integer The ped to get the mugshot of
|
||||
---@param transparent? boolean Whether the mugshot should be transparent
|
||||
function ESX.Game.GetPedMugshot(ped, transparent)
|
||||
if not DoesEntityExist(ped) then
|
||||
return
|
||||
@@ -303,17 +431,19 @@ function ESX.Game.GetPedMugshot(ped, transparent)
|
||||
return mugshot, GetPedheadshotTxdString(mugshot)
|
||||
end
|
||||
|
||||
---@param entity integer The entity to get the coords of
|
||||
---@param coords table | vector3 | vector4 The coords to teleport the entity to
|
||||
---@param cb? function The callback function
|
||||
function ESX.Game.Teleport(entity, coords, cb)
|
||||
local vector = type(coords) == "vector4" and coords or type(coords) == "vector3" and vector4(coords, 0.0) or vec(coords.x, coords.y, coords.z, coords.heading or 0.0)
|
||||
|
||||
if DoesEntityExist(entity) then
|
||||
RequestCollisionAtCoord(vector.xyz)
|
||||
RequestCollisionAtCoord(coords.x, coords.y, coords.z)
|
||||
while not HasCollisionLoadedAroundEntity(entity) do
|
||||
Wait(0)
|
||||
end
|
||||
|
||||
SetEntityCoords(entity, vector.xyz, false, false, false, false)
|
||||
SetEntityHeading(entity, vector.w)
|
||||
SetEntityCoords(entity, coords.x, coords.y, coords.z, false, false, false, false)
|
||||
SetEntityHeading(entity, coords.w or coords.heading or 0.0)
|
||||
end
|
||||
|
||||
if cb then
|
||||
@@ -321,29 +451,52 @@ function ESX.Game.Teleport(entity, coords, cb)
|
||||
end
|
||||
end
|
||||
|
||||
---@param object integer | string The object to spawn
|
||||
---@param coords table | vector3 The coords to spawn the object at
|
||||
---@param cb? function The callback function
|
||||
---@param networked? boolean Whether the object should be networked
|
||||
---@return integer | nil
|
||||
function ESX.Game.SpawnObject(object, coords, cb, networked)
|
||||
local obj = CreateObject(ESX.Streaming.RequestModel(object), coords.x, coords.y, coords.z, networked == nil or networked, false, true)
|
||||
local model = type(object) == "number" and object or joaat(object)
|
||||
|
||||
ESX.Streaming.RequestModel(model)
|
||||
|
||||
local obj = CreateObject(model, coords.x, coords.y, coords.z, networked == nil or networked, false, true)
|
||||
return cb and cb(obj) or obj
|
||||
end
|
||||
|
||||
---@param object integer | string The object to spawn
|
||||
---@param coords table | vector3 The coords to spawn the object at
|
||||
---@param cb? function The callback function
|
||||
---@return nil
|
||||
function ESX.Game.SpawnLocalObject(object, coords, cb)
|
||||
ESX.Game.SpawnObject(object, coords, cb, false)
|
||||
end
|
||||
|
||||
---@param vehicle integer The vehicle to delete
|
||||
---@return nil
|
||||
function ESX.Game.DeleteVehicle(vehicle)
|
||||
SetEntityAsMissionEntity(vehicle, true, true)
|
||||
DeleteVehicle(vehicle)
|
||||
end
|
||||
|
||||
---@param object integer The object to delete
|
||||
---@return nil
|
||||
function ESX.Game.DeleteObject(object)
|
||||
SetEntityAsMissionEntity(object, false, true)
|
||||
DeleteObject(object)
|
||||
end
|
||||
|
||||
---@param vehicleModel integer | string The vehicle to spawn
|
||||
---@param coords table | vector3 The coords to spawn the vehicle at
|
||||
---@param heading number The heading of the vehicle
|
||||
---@param cb? function The callback function
|
||||
---@param networked? boolean Whether the vehicle should be networked
|
||||
---@return nil
|
||||
function ESX.Game.SpawnVehicle(vehicleModel, coords, heading, cb, networked)
|
||||
local model = type(vehicleModel) == "number" and vehicleModel or joaat(vehicleModel)
|
||||
local vector = type(coords) == "vector3" and coords or vec(coords.x, coords.y, coords.z)
|
||||
networked = networked == nil and true or networked
|
||||
local isNetworked = networked == nil or networked
|
||||
|
||||
local playerCoords = GetEntityCoords(ESX.PlayerData.ped)
|
||||
if not vector or not playerCoords then
|
||||
@@ -353,13 +506,13 @@ function ESX.Game.SpawnVehicle(vehicleModel, coords, heading, cb, networked)
|
||||
local dist = #(playerCoords - vector)
|
||||
if dist > 424 then -- Onesync infinity Range (https://docs.fivem.net/docs/scripting-reference/onesync/)
|
||||
local executingResource = GetInvokingResource() or "Unknown"
|
||||
return error(("Resource ^5%s^7 Tried to spawn vehicle on the client but the position is too far away (Out of onesync range)."):format(executingResource))
|
||||
return error(("Resource ^5%s^1 Tried to spawn vehicle on the client but the position is too far away (Out of onesync range)."):format(executingResource))
|
||||
end
|
||||
|
||||
CreateThread(function()
|
||||
ESX.Streaming.RequestModel(model)
|
||||
|
||||
local vehicle = CreateVehicle(model, vector.xyz, heading, networked, true)
|
||||
local vehicle = CreateVehicle(model, vector.x, vector.y, vector.z, heading, isNetworked, true)
|
||||
|
||||
if networked then
|
||||
local id = NetworkGetNetworkIdFromEntity(vehicle)
|
||||
@@ -371,7 +524,7 @@ function ESX.Game.SpawnVehicle(vehicleModel, coords, heading, cb, networked)
|
||||
SetModelAsNoLongerNeeded(model)
|
||||
SetVehRadioStation(vehicle, "OFF")
|
||||
|
||||
RequestCollisionAtCoord(vector.xyz)
|
||||
RequestCollisionAtCoord(vector.x, vector.y, vector.z)
|
||||
while not HasCollisionLoadedAroundEntity(vehicle) do
|
||||
Wait(0)
|
||||
end
|
||||
@@ -382,18 +535,28 @@ function ESX.Game.SpawnVehicle(vehicleModel, coords, heading, cb, networked)
|
||||
end)
|
||||
end
|
||||
|
||||
---@param vehicle integer The vehicle to spawn
|
||||
---@param coords table | vector3 The coords to spawn the vehicle at
|
||||
---@param heading number The heading of the vehicle
|
||||
---@param cb? function The callback function
|
||||
---@return nil
|
||||
function ESX.Game.SpawnLocalVehicle(vehicle, coords, heading, cb)
|
||||
ESX.Game.SpawnVehicle(vehicle, coords, heading, cb, false)
|
||||
end
|
||||
|
||||
---@param vehicle integer The vehicle to check
|
||||
---@return boolean
|
||||
function ESX.Game.IsVehicleEmpty(vehicle)
|
||||
return GetVehicleNumberOfPassengers(vehicle) == 0 and IsVehicleSeatFree(vehicle, -1)
|
||||
end
|
||||
|
||||
---@return table
|
||||
function ESX.Game.GetObjects() -- Leave the function for compatibility
|
||||
return GetGamePool("CObject")
|
||||
end
|
||||
|
||||
---@param onlyOtherPeds? boolean Whether to exlude the player ped
|
||||
---@return table
|
||||
function ESX.Game.GetPeds(onlyOtherPeds)
|
||||
local pool = GetGamePool("CPed")
|
||||
|
||||
@@ -410,19 +573,24 @@ function ESX.Game.GetPeds(onlyOtherPeds)
|
||||
return pool
|
||||
end
|
||||
|
||||
---@return table
|
||||
function ESX.Game.GetVehicles() -- Leave the function for compatibility
|
||||
return GetGamePool("CVehicle")
|
||||
end
|
||||
|
||||
---@param onlyOtherPlayers? boolean Whether to exclude the player
|
||||
---@param returnKeyValue? boolean Whether to return the key value pair
|
||||
---@param returnPeds? boolean Whether to return the peds
|
||||
---@return table
|
||||
function ESX.Game.GetPlayers(onlyOtherPlayers, returnKeyValue, returnPeds)
|
||||
local players, myPlayer = {}, PlayerId()
|
||||
local players = {}
|
||||
local active = GetActivePlayers()
|
||||
|
||||
for i = 1, #active do
|
||||
local currentPlayer = active[i]
|
||||
local ped = GetPlayerPed(currentPlayer)
|
||||
|
||||
if DoesEntityExist(ped) and ((onlyOtherPlayers and currentPlayer ~= myPlayer) or not onlyOtherPlayers) then
|
||||
if DoesEntityExist(ped) and ((onlyOtherPlayers and currentPlayer ~= ESX.playerId) or not onlyOtherPlayers) then
|
||||
if returnKeyValue then
|
||||
players[currentPlayer] = ped
|
||||
else
|
||||
@@ -434,22 +602,38 @@ function ESX.Game.GetPlayers(onlyOtherPlayers, returnKeyValue, returnPeds)
|
||||
return players
|
||||
end
|
||||
|
||||
---@param coords? table | vector3 The coords to get the closest object to
|
||||
---@param modelFilter? table The model filter
|
||||
---@return integer, integer
|
||||
function ESX.Game.GetClosestObject(coords, modelFilter)
|
||||
return ESX.Game.GetClosestEntity(ESX.Game.GetObjects(), false, coords, modelFilter)
|
||||
end
|
||||
|
||||
---@param coords? table | vector3 The coords to get the closest ped to
|
||||
---@param modelFilter? table The model filter
|
||||
---@return integer, integer
|
||||
function ESX.Game.GetClosestPed(coords, modelFilter)
|
||||
return ESX.Game.GetClosestEntity(ESX.Game.GetPeds(true), false, coords, modelFilter)
|
||||
end
|
||||
|
||||
---@param coords? table | vector3 The coords to get the closest player to
|
||||
---@return integer, integer
|
||||
function ESX.Game.GetClosestPlayer(coords)
|
||||
return ESX.Game.GetClosestEntity(ESX.Game.GetPlayers(true, true), true, coords, nil)
|
||||
end
|
||||
|
||||
---@param coords? table | vector3 The coords to get the closest vehicle to
|
||||
---@param modelFilter? table The model filter
|
||||
---@return integer, integer
|
||||
function ESX.Game.GetClosestVehicle(coords, modelFilter)
|
||||
return ESX.Game.GetClosestEntity(ESX.Game.GetVehicles(), false, coords, modelFilter)
|
||||
end
|
||||
|
||||
---@param entities table The entities to search through
|
||||
---@param isPlayerEntities boolean Whether the entities are players
|
||||
---@param coords table | vector3 The coords to search from
|
||||
---@param maxDistance number The max distance to search within
|
||||
---@return table
|
||||
local function EnumerateEntitiesWithinDistance(entities, isPlayerEntities, coords, maxDistance)
|
||||
local nearbyEntities = {}
|
||||
|
||||
@@ -471,25 +655,41 @@ local function EnumerateEntitiesWithinDistance(entities, isPlayerEntities, coord
|
||||
return nearbyEntities
|
||||
end
|
||||
|
||||
---@param coords table | vector3 The coords to search from
|
||||
---@param maxDistance number The max distance to search within
|
||||
---@return table
|
||||
function ESX.Game.GetPlayersInArea(coords, maxDistance)
|
||||
return EnumerateEntitiesWithinDistance(ESX.Game.GetPlayers(true, true), true, coords, maxDistance)
|
||||
end
|
||||
|
||||
---@param coords table | vector3 The coords to search from
|
||||
---@param maxDistance number The max distance to search within
|
||||
---@return table
|
||||
function ESX.Game.GetVehiclesInArea(coords, maxDistance)
|
||||
return EnumerateEntitiesWithinDistance(ESX.Game.GetVehicles(), false, coords, maxDistance)
|
||||
end
|
||||
|
||||
---@param coords table | vector3 The coords to search from
|
||||
---@param maxDistance number The max distance to search within
|
||||
---@return boolean
|
||||
function ESX.Game.IsSpawnPointClear(coords, maxDistance)
|
||||
return #ESX.Game.GetVehiclesInArea(coords, maxDistance) == 0
|
||||
end
|
||||
|
||||
---@param shape integer The shape to get the test result from
|
||||
---@return boolean, table, table, integer, integer
|
||||
function ESX.Game.GetShapeTestResultSync(shape)
|
||||
local handle, hit, coords, normal, material, entity
|
||||
repeat handle, hit, coords, normal, material, entity = GetShapeTestResultIncludingMaterial(shape)
|
||||
until handle ~= 1 or Wait()
|
||||
repeat
|
||||
handle, hit, coords, normal, material, entity = GetShapeTestResultIncludingMaterial(shape)
|
||||
Wait(0)
|
||||
until handle ~= 1
|
||||
return hit, coords, normal, material, entity
|
||||
end
|
||||
|
||||
---@param depth number The depth to raycast
|
||||
---@vararg any The arguments to pass to the shape test
|
||||
---@return table, boolean, table, table, integer, integer
|
||||
function ESX.Game.RaycastScreen(depth, ...)
|
||||
local world, normal = GetWorldCoordFromScreenCoord(.5, .5)
|
||||
local origin = world + normal
|
||||
@@ -497,6 +697,11 @@ function ESX.Game.RaycastScreen(depth, ...)
|
||||
return target, ESX.Game.GetShapeTestResultSync(StartShapeTestLosProbe(origin.x, origin.y, origin.z, target.x, target.y, target.z, ...))
|
||||
end
|
||||
|
||||
---@param entities table The entities to search through
|
||||
---@param isPlayerEntities boolean Whether the entities are players
|
||||
---@param coords? table | vector3 The coords to search from
|
||||
---@param modelFilter? table The model filter
|
||||
---@return integer, integer
|
||||
function ESX.Game.GetClosestEntity(entities, isPlayerEntities, coords, modelFilter)
|
||||
local closestEntity, closestEntityDistance, filteredEntities = -1, -1, nil
|
||||
|
||||
@@ -528,6 +733,7 @@ function ESX.Game.GetClosestEntity(entities, isPlayerEntities, coords, modelFilt
|
||||
return closestEntity, closestEntityDistance
|
||||
end
|
||||
|
||||
---@return integer | nil, vector3 | nil
|
||||
function ESX.Game.GetVehicleInDirection()
|
||||
local _, hit, coords, _, _, entity = ESX.Game.RaycastScreen(5, 10, ESX.PlayerData.ped)
|
||||
if hit and IsEntityAVehicle(entity) then
|
||||
@@ -535,6 +741,8 @@ function ESX.Game.GetVehicleInDirection()
|
||||
end
|
||||
end
|
||||
|
||||
---@param vehicle integer The vehicle to get the properties of
|
||||
---@return table | nil
|
||||
function ESX.Game.GetVehicleProperties(vehicle)
|
||||
if not DoesEntityExist(vehicle) then
|
||||
return
|
||||
@@ -690,6 +898,9 @@ function ESX.Game.GetVehicleProperties(vehicle)
|
||||
}
|
||||
end
|
||||
|
||||
---@param vehicle integer The vehicle to set the properties of
|
||||
---@param props table The properties to set
|
||||
---@return nil
|
||||
function ESX.Game.SetVehicleProperties(vehicle, props)
|
||||
if not DoesEntityExist(vehicle) then
|
||||
return
|
||||
@@ -766,7 +977,10 @@ function ESX.Game.SetVehicleProperties(vehicle, props)
|
||||
|
||||
if props.extras ~= nil then
|
||||
for extraId, enabled in pairs(props.extras) do
|
||||
SetVehicleExtra(vehicle, tonumber(extraId), enabled and 0 or 1)
|
||||
extraId = tonumber(extraId)
|
||||
if extraId then
|
||||
SetVehicleExtra(vehicle, extraId, enabled)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -928,7 +1142,10 @@ function ESX.Game.SetVehicleProperties(vehicle, props)
|
||||
if props.windowsBroken ~= nil then
|
||||
for k, v in pairs(props.windowsBroken) do
|
||||
if v then
|
||||
RemoveVehicleWindow(vehicle, tonumber(k))
|
||||
k = tonumber(k)
|
||||
if k then
|
||||
RemoveVehicleWindow(vehicle, k)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -936,7 +1153,10 @@ function ESX.Game.SetVehicleProperties(vehicle, props)
|
||||
if props.doorsBroken ~= nil then
|
||||
for k, v in pairs(props.doorsBroken) do
|
||||
if v then
|
||||
SetVehicleDoorBroken(vehicle, tonumber(k), true)
|
||||
k = tonumber(k)
|
||||
if k then
|
||||
SetVehicleDoorBroken(vehicle, k, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -944,12 +1164,20 @@ function ESX.Game.SetVehicleProperties(vehicle, props)
|
||||
if props.tyreBurst ~= nil then
|
||||
for k, v in pairs(props.tyreBurst) do
|
||||
if v then
|
||||
SetVehicleTyreBurst(vehicle, tonumber(k), true, 1000.0)
|
||||
k = tonumber(k)
|
||||
if k then
|
||||
SetVehicleTyreBurst(vehicle, k, true, 1000.0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param coords vector3 | table coords to get the closest pickup to
|
||||
---@param text string The text to display
|
||||
---@param size? number The size of the text
|
||||
---@param font? number The font of the text
|
||||
---@return nil
|
||||
function ESX.Game.Utils.DrawText3D(coords, text, size, font)
|
||||
local vector = type(coords) == "vector3" and coords or vec(coords.x, coords.y, coords.z)
|
||||
|
||||
@@ -965,12 +1193,12 @@ function ESX.Game.Utils.DrawText3D(coords, text, size, font)
|
||||
|
||||
SetTextScale(0.0, 0.55 * scale)
|
||||
SetTextFont(font)
|
||||
SetTextProportional(1)
|
||||
SetTextProportional(true)
|
||||
SetTextColour(255, 255, 255, 215)
|
||||
BeginTextCommandDisplayText("STRING")
|
||||
SetTextCentre(true)
|
||||
AddTextComponentSubstringPlayerName(text)
|
||||
SetDrawOrigin(vector.xyz, 0)
|
||||
SetDrawOrigin(vector.x, vector.y, vector.z, 0)
|
||||
EndTextCommandDisplayText(0.0, 0.0)
|
||||
ClearDrawOrigin()
|
||||
end
|
||||
@@ -986,6 +1214,7 @@ function ESX.GetAccount(account)
|
||||
return nil
|
||||
end
|
||||
|
||||
---@return nil
|
||||
function ESX.ShowInventory()
|
||||
if not Config.EnableDefaultInventory then
|
||||
return
|
||||
@@ -1310,11 +1539,11 @@ local mismatchedTypes = {
|
||||
}
|
||||
|
||||
---@param model number|string
|
||||
---@return string
|
||||
function ESX.GetVehicleType(model)
|
||||
---@return string | boolean
|
||||
function ESX.GetVehicleTypeClient(model)
|
||||
model = type(model) == "string" and joaat(model) or model
|
||||
if not IsModelInCdimage(model) then
|
||||
return
|
||||
return false
|
||||
end
|
||||
if mismatchedTypes[model] then
|
||||
return mismatchedTypes[model]
|
||||
@@ -1333,3 +1562,5 @@ function ESX.GetVehicleType(model)
|
||||
|
||||
return types[vehicleType] or "automobile"
|
||||
end
|
||||
|
||||
ESX.GetVehicleType = ESX.GetVehicleTypeClient
|
||||
|
||||
+140
-153
@@ -1,71 +1,17 @@
|
||||
local pickups = {}
|
||||
|
||||
CreateThread(function()
|
||||
while not Config.Multichar do
|
||||
Wait(100)
|
||||
|
||||
if NetworkIsPlayerActive(PlayerId()) then
|
||||
exports.spawnmanager:setAutoSpawn(false)
|
||||
DoScreenFadeOut(0)
|
||||
Wait(500)
|
||||
TriggerServerEvent("esx:onPlayerJoined")
|
||||
break
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx:requestModel", function(model)
|
||||
ESX.Streaming.RequestModel(model)
|
||||
end)
|
||||
|
||||
function ESX.SpawnPlayer(skin, coords, cb)
|
||||
local p = promise.new()
|
||||
TriggerEvent("skinchanger:loadSkin", skin, function()
|
||||
p:resolve()
|
||||
end)
|
||||
Citizen.Await(p)
|
||||
|
||||
RequestCollisionAtCoord(coords.x, coords.y, coords.z)
|
||||
|
||||
local playerPed = PlayerPedId()
|
||||
local timer = GetGameTimer()
|
||||
|
||||
FreezeEntityPosition(playerPed, true)
|
||||
SetEntityCoordsNoOffset(playerPed, coords.x, coords.y, coords.z, false, false, false, true)
|
||||
SetEntityHeading(playerPed, coords.heading)
|
||||
|
||||
while not HasCollisionLoadedAroundEntity(playerPed) and (GetGameTimer() - timer) < 5000 do
|
||||
Wait(0)
|
||||
local function EnablePvP()
|
||||
if Config.EnablePVP then
|
||||
SetCanAttackFriendly(ESX.PlayerData.ped, true, false)
|
||||
NetworkSetFriendlyFireOption(true)
|
||||
end
|
||||
|
||||
NetworkResurrectLocalPlayer(coords.x, coords.y, coords.z, coords.heading, true, true, false)
|
||||
TriggerEvent('playerSpawned', coords)
|
||||
cb()
|
||||
end
|
||||
|
||||
RegisterNetEvent("esx:playerLoaded")
|
||||
AddEventHandler("esx:playerLoaded", function(xPlayer, _, skin)
|
||||
ESX.PlayerData = xPlayer
|
||||
|
||||
if not Config.Multichar then
|
||||
ESX.SpawnPlayer(skin, ESX.PlayerData.coords, function()
|
||||
TriggerEvent("esx:onPlayerSpawn")
|
||||
TriggerEvent("esx:restoreLoadout")
|
||||
TriggerServerEvent("esx:onPlayerSpawn")
|
||||
TriggerEvent("esx:loadingScreenOff")
|
||||
ShutdownLoadingScreen()
|
||||
ShutdownLoadingScreenNui()
|
||||
end)
|
||||
end
|
||||
|
||||
while not DoesEntityExist(ESX.PlayerData.ped) do
|
||||
Wait(20)
|
||||
end
|
||||
|
||||
ESX.PlayerLoaded = true
|
||||
|
||||
local metadata = ESX.PlayerData.metadata
|
||||
|
||||
local function ApplyMetadata(metadata)
|
||||
if metadata.health then
|
||||
SetEntityHealth(ESX.PlayerData.ped, metadata.health)
|
||||
end
|
||||
@@ -73,74 +19,10 @@ AddEventHandler("esx:playerLoaded", function(xPlayer, _, skin)
|
||||
if metadata.armor and metadata.armor > 0 then
|
||||
SetPedArmour(ESX.PlayerData.ped, metadata.armor)
|
||||
end
|
||||
end
|
||||
|
||||
local timer = GetGameTimer()
|
||||
while not HaveAllStreamingRequestsCompleted(ESX.PlayerData.ped) and (GetGameTimer() - timer) < 2000 do
|
||||
Wait(0)
|
||||
end
|
||||
|
||||
if Config.EnablePVP then
|
||||
SetCanAttackFriendly(ESX.PlayerData.ped, true, false)
|
||||
NetworkSetFriendlyFireOption(true)
|
||||
end
|
||||
|
||||
local playerId = PlayerId()
|
||||
-- RemoveHudComponents
|
||||
for i = 1, #Config.RemoveHudComponents do
|
||||
if Config.RemoveHudComponents[i] then
|
||||
SetHudComponentPosition(i, 999999.0, 999999.0)
|
||||
end
|
||||
end
|
||||
|
||||
-- DisableNPCDrops
|
||||
if Config.DisableNPCDrops then
|
||||
local weaponPickups = { `PICKUP_WEAPON_CARBINERIFLE`, `PICKUP_WEAPON_PISTOL`, `PICKUP_WEAPON_PUMPSHOTGUN` }
|
||||
for i = 1, #weaponPickups do
|
||||
ToggleUsePickupsForPlayer(playerId, weaponPickups[i], false)
|
||||
end
|
||||
end
|
||||
|
||||
if Config.DisableVehicleSeatShuff then
|
||||
AddEventHandler("esx:enteredVehicle", function(vehicle, _, seat)
|
||||
if seat == 0 then
|
||||
SetPedIntoVehicle(ESX.PlayerData.ped, vehicle, 0)
|
||||
SetPedConfigFlag(ESX.PlayerData.ped, 184, true)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
if Config.DisableHealthRegeneration then
|
||||
SetPlayerHealthRechargeMultiplier(playerId, 0.0)
|
||||
end
|
||||
|
||||
if Config.DisableWeaponWheel or Config.DisableAimAssist or Config.DisableVehicleRewards then
|
||||
CreateThread(function()
|
||||
while true do
|
||||
if Config.DisableDisplayAmmo then
|
||||
DisplayAmmoThisFrame(false)
|
||||
end
|
||||
|
||||
if Config.DisableWeaponWheel then
|
||||
BlockWeaponWheelThisFrame()
|
||||
DisableControlAction(0, 37, true)
|
||||
end
|
||||
|
||||
if Config.DisableAimAssist then
|
||||
if IsPedArmed(ESX.PlayerData.ped, 4) then
|
||||
SetPlayerLockonRangeOverride(playerId, 2.0)
|
||||
end
|
||||
end
|
||||
|
||||
if Config.DisableVehicleRewards then
|
||||
DisablePlayerVehicleRewards(playerId)
|
||||
end
|
||||
|
||||
Wait(0)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- Disable Dispatch services
|
||||
local function DisableNPCs()
|
||||
-- Disable Dispatch services
|
||||
if Config.DisableDispatchServices then
|
||||
for i = 1, 15 do
|
||||
EnableDispatchService(i, false)
|
||||
@@ -148,7 +30,6 @@ AddEventHandler("esx:playerLoaded", function(xPlayer, _, skin)
|
||||
SetAudioFlag('PoliceScannerDisabled', true)
|
||||
end
|
||||
|
||||
-- Disable Scenarios
|
||||
if Config.DisableScenarios then
|
||||
local scenarios = {
|
||||
"WORLD_VEHICLE_ATTRACTOR",
|
||||
@@ -208,15 +89,106 @@ AddEventHandler("esx:playerLoaded", function(xPlayer, _, skin)
|
||||
SetScenarioTypeEnabled(v, false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function DisableAmmoOrVehicleRewards(playerId)
|
||||
CreateThread(function()
|
||||
while true do
|
||||
if Config.DisableDisplayAmmo then
|
||||
DisplayAmmoThisFrame(false)
|
||||
end
|
||||
|
||||
if Config.DisableVehicleRewards then
|
||||
DisablePlayerVehicleRewards(playerId)
|
||||
end
|
||||
|
||||
Wait(0)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function ApplyConfig()
|
||||
-- RemoveHudComponents
|
||||
for i = 1, #Config.RemoveHudComponents do
|
||||
if Config.RemoveHudComponents[i] then
|
||||
SetHudComponentSize(i, 0.0, 0.0)
|
||||
end
|
||||
end
|
||||
|
||||
if Config.DisableAimAssist then
|
||||
SetPlayerLockon(ESX.playerId, false)
|
||||
end
|
||||
|
||||
-- DisableNPCDrops
|
||||
if Config.DisableNPCDrops then
|
||||
local weaponPickups = { `PICKUP_WEAPON_CARBINERIFLE`, `PICKUP_WEAPON_PISTOL`, `PICKUP_WEAPON_PUMPSHOTGUN` }
|
||||
for i = 1, #weaponPickups do
|
||||
ToggleUsePickupsForPlayer(ESX.playerId, weaponPickups[i], false)
|
||||
end
|
||||
end
|
||||
|
||||
if Config.DisableVehicleSeatShuff then
|
||||
AddEventHandler("esx:enteredVehicle", function(vehicle, _, seat)
|
||||
if seat > -1 then
|
||||
SetPedIntoVehicle(ESX.PlayerData.ped, vehicle, seat)
|
||||
SetPedConfigFlag(ESX.PlayerData.ped, 184, true)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
if Config.DisableHealthRegeneration then
|
||||
SetPlayerHealthRechargeMultiplier(ESX.playerId, 0.0)
|
||||
end
|
||||
|
||||
if Config.DisableDisplayAmmo or Config.DisableVehicleRewards then
|
||||
DisableAmmoOrVehicleRewards(ESX.playerId)
|
||||
end
|
||||
|
||||
EnablePvP()
|
||||
DisableNPCs()
|
||||
end
|
||||
|
||||
RegisterNetEvent("esx:playerLoaded")
|
||||
AddEventHandler("esx:playerLoaded", function(xPlayer, _, skin)
|
||||
ESX.PlayerData = xPlayer
|
||||
|
||||
if not Config.Multichar then
|
||||
FreezeEntityPosition(ESX.PlayerData.ped, false)
|
||||
ESX.SpawnPlayer(skin, ESX.PlayerData.coords, function()
|
||||
TriggerEvent("esx:onPlayerSpawn")
|
||||
TriggerEvent("esx:restoreLoadout")
|
||||
TriggerServerEvent("esx:onPlayerSpawn")
|
||||
TriggerEvent("esx:loadingScreenOff")
|
||||
ShutdownLoadingScreen()
|
||||
ShutdownLoadingScreenNui()
|
||||
end)
|
||||
end
|
||||
|
||||
while not DoesEntityExist(ESX.PlayerData.ped) do
|
||||
Wait(20)
|
||||
end
|
||||
|
||||
ESX.PlayerLoaded = true
|
||||
|
||||
ApplyMetadata(ESX.PlayerData.metadata)
|
||||
|
||||
local timer = GetGameTimer()
|
||||
while not HaveAllStreamingRequestsCompleted(ESX.PlayerData.ped) and (GetGameTimer() - timer) < 2000 do
|
||||
Wait(0)
|
||||
end
|
||||
|
||||
ApplyConfig()
|
||||
|
||||
ClearPedTasksImmediately(ESX.PlayerData.ped)
|
||||
|
||||
if not Config.Multichar then
|
||||
Core.FreezePlayer(false)
|
||||
end
|
||||
|
||||
if IsScreenFadedOut() then
|
||||
DoScreenFadeIn(500)
|
||||
end
|
||||
|
||||
Actions:Init()
|
||||
SetDefaultVehicleNumberPlateTextPattern(-1, Config.CustomAIPlates)
|
||||
StartServerSyncLoops()
|
||||
end)
|
||||
@@ -281,13 +253,26 @@ AddEventHandler("esx:restoreLoadout", function()
|
||||
end
|
||||
end)
|
||||
|
||||
---@diagnostic disable-next-line: param-type-mismatch
|
||||
AddStateBagChangeHandler("VehicleProperties", nil, function(bagName, _, value)
|
||||
if not value then
|
||||
return
|
||||
end
|
||||
|
||||
local netId = bagName:gsub("entity:", "")
|
||||
local vehicle = NetToVeh(tonumber(netId))
|
||||
local netId = tonumber(bagName:gsub("entity:", ""))
|
||||
if not netId then
|
||||
error("Tried to set vehicle properties with invalid netId")
|
||||
return
|
||||
end
|
||||
local vehicle = NetToVeh(netId)
|
||||
if not DoesEntityExist(vehicle) then
|
||||
error("Tried to set vehicle properties with invalid vehicle")
|
||||
return
|
||||
end
|
||||
|
||||
if not NetworkGetEntityOwner(vehicle) == ESX.playerId then
|
||||
return
|
||||
end
|
||||
|
||||
ESX.Game.SetVehicleProperties(vehicle, value)
|
||||
end)
|
||||
@@ -337,17 +322,17 @@ if not Config.OxInventory then
|
||||
|
||||
RegisterNetEvent("esx:addWeapon")
|
||||
AddEventHandler("esx:addWeapon", function()
|
||||
error("event ^5'esx:addWeapon'^7 Has Been Removed. Please use ^5xPlayer.addWeapon^7 Instead!")
|
||||
error("event ^5'esx:addWeapon'^1 Has Been Removed. Please use ^5xPlayer.addWeapon^1 Instead!")
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx:addWeaponComponent")
|
||||
AddEventHandler("esx:addWeaponComponent", function()
|
||||
error("event ^5'esx:addWeaponComponent'^7 Has Been Removed. Please use ^5xPlayer.addWeaponComponent^7 Instead!")
|
||||
error("event ^5'esx:addWeaponComponent'^1 Has Been Removed. Please use ^5xPlayer.addWeaponComponent^1 Instead!")
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx:setWeaponAmmo")
|
||||
AddEventHandler("esx:setWeaponAmmo", function()
|
||||
error("event ^5'esx:setWeaponAmmo'^7 Has Been Removed. Please use ^5xPlayer.addWeaponAmmo^7 Instead!")
|
||||
error("event ^5'esx:setWeaponAmmo'^1 Has Been Removed. Please use ^5xPlayer.addWeaponAmmo^1 Instead!")
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx:setWeaponTint")
|
||||
@@ -357,7 +342,7 @@ if not Config.OxInventory then
|
||||
|
||||
RegisterNetEvent("esx:removeWeapon")
|
||||
AddEventHandler("esx:removeWeapon", function()
|
||||
error("event ^5'esx:removeWeapon'^7 Has Been Removed. Please use ^5xPlayer.removeWeapon^7 Instead!")
|
||||
error("event ^5'esx:removeWeapon'^1 Has Been Removed. Please use ^5xPlayer.removeWeapon^1 Instead!")
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx:removeWeaponComponent")
|
||||
@@ -402,7 +387,9 @@ if not Config.OxInventory then
|
||||
|
||||
for _, v in ipairs(components) do
|
||||
local component = ESX.GetWeaponComponent(name, v)
|
||||
GiveWeaponComponentToWeaponObject(pickupObject, component.hash)
|
||||
if component then
|
||||
GiveWeaponComponentToWeaponObject(pickupObject, component.hash)
|
||||
end
|
||||
end
|
||||
|
||||
setObjectProperties(pickupObject)
|
||||
@@ -479,7 +466,7 @@ end
|
||||
|
||||
-- disable wanted level
|
||||
if not Config.EnableWantedLevel then
|
||||
ClearPlayerWantedLevel(PlayerId())
|
||||
ClearPlayerWantedLevel(ESX.playerId)
|
||||
SetMaxWantedLevel(0)
|
||||
end
|
||||
|
||||
@@ -516,11 +503,8 @@ if not Config.OxInventory then
|
||||
label = ("%s~n~%s"):format(label, TranslateCap("threw_pickup_prompt"))
|
||||
end
|
||||
|
||||
ESX.Game.Utils.DrawText3D({
|
||||
x = pickup.coords.x,
|
||||
y = pickup.coords.y,
|
||||
z = pickup.coords.z + 0.25,
|
||||
}, label, 1.2, 1)
|
||||
local textCoords = pickup.coords + vector3(0.0, 0.0, 0.25)
|
||||
ESX.Game.Utils.DrawText3D(textCoords, label, 1.2, 1)
|
||||
elseif pickup.inRange then
|
||||
pickup.inRange = false
|
||||
end
|
||||
@@ -548,7 +532,7 @@ AddEventHandler("esx:tpm", function()
|
||||
end
|
||||
local blipMarker = GetFirstBlipInfoId(8)
|
||||
if not DoesBlipExist(blipMarker) then
|
||||
ESX.ShowNotification(TranslateCap("tpm_nowaypoint"), true, false, 140)
|
||||
ESX.ShowNotification(TranslateCap("tpm_nowaypoint"), "error")
|
||||
return "marker"
|
||||
end
|
||||
|
||||
@@ -611,12 +595,12 @@ AddEventHandler("esx:tpm", function()
|
||||
-- If we can't find the coords, set the coords to the old ones.
|
||||
-- We don't unpack them before since they aren't in a loop and only called once.
|
||||
SetPedCoordsKeepVehicle(ped, oldCoords["x"], oldCoords["y"], oldCoords["z"] - 1.0)
|
||||
ESX.ShowNotification(TranslateCap("tpm_success"), true, false, 140)
|
||||
ESX.ShowNotification(TranslateCap("tpm_success"), "success")
|
||||
end
|
||||
|
||||
-- If Z coord was found, set coords in found coords.
|
||||
SetPedCoordsKeepVehicle(ped, x, y, groundZ)
|
||||
ESX.ShowNotification(TranslateCap("tpm_success"), true, false, 140)
|
||||
ESX.ShowNotification(TranslateCap("tpm_success"), "success")
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -626,7 +610,7 @@ local heading = 0
|
||||
|
||||
local function noclipThread()
|
||||
while noclip do
|
||||
SetEntityCoordsNoOffset(ESX.PlayerData.ped, noclip_pos.x, noclip_pos.y, noclip_pos.z, 0, 0, 0)
|
||||
SetEntityCoordsNoOffset(ESX.PlayerData.ped, noclip_pos.x, noclip_pos.y, noclip_pos.z, false, false, true)
|
||||
|
||||
if IsControlPressed(1, 34) then
|
||||
heading = heading + 1.5
|
||||
@@ -682,7 +666,11 @@ AddEventHandler("esx:noclip", function()
|
||||
CreateThread(noclipThread)
|
||||
end
|
||||
|
||||
ESX.ShowNotification(TranslateCap("noclip_message", noclip and Translate("enabled") or Translate("disabled")), true, false, 140)
|
||||
if noclip then
|
||||
ESX.ShowNotification(TranslateCap("noclip_message", Translate("enabled")), "success")
|
||||
else
|
||||
ESX.ShowNotification(TranslateCap("noclip_message", Translate("disabled")), "error")
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -696,27 +684,26 @@ AddEventHandler("esx:repairPedVehicle", function()
|
||||
local ped = ESX.PlayerData.ped
|
||||
local vehicle = GetVehiclePedIsIn(ped, false)
|
||||
SetVehicleEngineHealth(vehicle, 1000)
|
||||
SetVehicleEngineOn(vehicle, true, true)
|
||||
SetVehicleEngineOn(vehicle, true, true, false)
|
||||
SetVehicleFixed(vehicle)
|
||||
SetVehicleDirtLevel(vehicle, 0)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx:freezePlayer")
|
||||
AddEventHandler("esx:freezePlayer", function(input)
|
||||
local player = PlayerId()
|
||||
if input == "freeze" then
|
||||
SetEntityCollision(ESX.PlayerData.ped, false)
|
||||
SetEntityCollision(ESX.PlayerData.ped, false, false)
|
||||
FreezeEntityPosition(ESX.PlayerData.ped, true)
|
||||
SetPlayerInvincible(player, true)
|
||||
SetPlayerInvincible(ESX.playerId, true)
|
||||
elseif input == "unfreeze" then
|
||||
SetEntityCollision(ESX.PlayerData.ped, true)
|
||||
SetEntityCollision(ESX.PlayerData.ped, true, true)
|
||||
FreezeEntityPosition(ESX.PlayerData.ped, false)
|
||||
SetPlayerInvincible(player, false)
|
||||
SetPlayerInvincible(ESX.playerId, false)
|
||||
end
|
||||
end)
|
||||
|
||||
ESX.RegisterClientCallback("esx:GetVehicleType", function(cb, model)
|
||||
cb(ESX.GetVehicleType(model))
|
||||
cb(ESX.GetVehicleTypeClient(model))
|
||||
end)
|
||||
|
||||
RegisterNetEvent('esx:updatePlayerData', function(key, val)
|
||||
|
||||
@@ -1,129 +1,152 @@
|
||||
local isInVehicle, isEnteringVehicle, isJumping, inPauseMenu = false, false, false, false
|
||||
local playerPed = PlayerPedId()
|
||||
local current = {}
|
||||
Actions = {}
|
||||
Actions._index = Actions
|
||||
|
||||
local function GetPedVehicleSeat(ped, vehicle)
|
||||
Actions.inVehicle = false
|
||||
Actions.enteringVehicle = false
|
||||
Actions.inPauseMenu = false
|
||||
|
||||
function Actions:GetSeatPedIsIn()
|
||||
for i = -1, 16 do
|
||||
if GetPedInVehicleSeat(vehicle, i) == ped then
|
||||
if GetPedInVehicleSeat(self.vehicle, i) == ESX.PlayerData.ped then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return -1
|
||||
end
|
||||
|
||||
local function GetData(vehicle)
|
||||
if not DoesEntityExist(vehicle) then
|
||||
function Actions:GetVehicleData()
|
||||
if not DoesEntityExist(self.vehicle) then
|
||||
return
|
||||
end
|
||||
local model = GetEntityModel(vehicle)
|
||||
local displayName = GetDisplayNameFromVehicleModel(model)
|
||||
local netId = vehicle
|
||||
if NetworkGetEntityIsNetworked(vehicle) then
|
||||
netId = VehToNet(vehicle)
|
||||
|
||||
local vehicleModel = GetEntityModel(self.vehicle)
|
||||
local displayName = GetDisplayNameFromVehicleModel(vehicleModel)
|
||||
local netId = NetworkGetEntityIsNetworked(self.vehicle) and VehToNet(self.vehicle) or self.vehicle
|
||||
local plate = GetVehicleNumberPlateText(self.vehicle)
|
||||
|
||||
return displayName, netId, plate
|
||||
end
|
||||
|
||||
function Actions:SetVehicleStatus()
|
||||
ESX.SetPlayerData("vehicle", self.vehicle)
|
||||
ESX.SetPlayerData("seat", self.seat)
|
||||
end
|
||||
|
||||
function Actions:TrackPedCoords()
|
||||
local playerPed = ESX.PlayerData.ped
|
||||
local coords = GetEntityCoords(playerPed)
|
||||
|
||||
ESX.SetPlayerData("coords", coords)
|
||||
end
|
||||
|
||||
function Actions:TrackPed()
|
||||
local playerPed = ESX.PlayerData.ped
|
||||
local newPed = PlayerPedId()
|
||||
|
||||
if playerPed ~= newPed then
|
||||
ESX.PlayerData.ped = newPed
|
||||
ESX.SetPlayerData("ped", playerPed)
|
||||
|
||||
TriggerEvent("esx:playerPedChanged", playerPed)
|
||||
TriggerServerEvent("esx:playerPedChanged", PedToNet(playerPed))
|
||||
end
|
||||
return displayName, netId
|
||||
end
|
||||
|
||||
local function ToggleVehicleStatus(inVehicle, seat)
|
||||
ESX.SetPlayerData("vehicle", inVehicle)
|
||||
ESX.SetPlayerData("seat", seat)
|
||||
end
|
||||
function Actions:TrackPauseMenu()
|
||||
local isActive = IsPauseMenuActive()
|
||||
|
||||
CreateThread(function()
|
||||
while not ESX.PlayerLoaded do Wait(200) end
|
||||
while true do
|
||||
ESX.SetPlayerData("coords", GetEntityCoords(playerPed))
|
||||
if playerPed ~= PlayerPedId() then
|
||||
playerPed = PlayerPedId()
|
||||
ESX.SetPlayerData("ped", playerPed)
|
||||
TriggerEvent("esx:playerPedChanged", playerPed)
|
||||
TriggerServerEvent("esx:playerPedChanged", PedToNet(playerPed))
|
||||
if Config.DisableHealthRegeneration then
|
||||
SetPlayerHealthRechargeMultiplier(PlayerId(), 0.0)
|
||||
end
|
||||
end
|
||||
|
||||
if IsPedJumping(playerPed) and not isJumping then
|
||||
isJumping = true
|
||||
TriggerEvent("esx:playerJumping")
|
||||
TriggerServerEvent("esx:playerJumping")
|
||||
elseif not IsPedJumping(playerPed) and isJumping then
|
||||
isJumping = false
|
||||
end
|
||||
|
||||
if IsPauseMenuActive() and not inPauseMenu then
|
||||
inPauseMenu = true
|
||||
TriggerEvent("esx:pauseMenuActive", inPauseMenu)
|
||||
elseif not IsPauseMenuActive() and inPauseMenu then
|
||||
inPauseMenu = false
|
||||
TriggerEvent("esx:pauseMenuActive", inPauseMenu)
|
||||
end
|
||||
|
||||
if not isInVehicle and not IsPlayerDead(PlayerId()) then
|
||||
if DoesEntityExist(GetVehiclePedIsTryingToEnter(playerPed)) and not isEnteringVehicle then
|
||||
-- trying to enter a vehicle!
|
||||
local vehicle = GetVehiclePedIsTryingToEnter(playerPed)
|
||||
local plate = GetVehicleNumberPlateText(vehicle)
|
||||
local seat = GetSeatPedIsTryingToEnter(playerPed)
|
||||
local _, netId = GetData(vehicle)
|
||||
isEnteringVehicle = true
|
||||
TriggerEvent("esx:enteringVehicle", vehicle, plate, seat, netId)
|
||||
TriggerServerEvent("esx:enteringVehicle", plate, seat, netId)
|
||||
ToggleVehicleStatus(vehicle, seat)
|
||||
elseif not DoesEntityExist(GetVehiclePedIsTryingToEnter(playerPed)) and not IsPedInAnyVehicle(playerPed, true) and isEnteringVehicle then
|
||||
-- vehicle entering aborted
|
||||
TriggerEvent("esx:enteringVehicleAborted")
|
||||
TriggerServerEvent("esx:enteringVehicleAborted")
|
||||
isEnteringVehicle = false
|
||||
ToggleVehicleStatus(false, false)
|
||||
elseif IsPedInAnyVehicle(playerPed, false) then
|
||||
-- suddenly appeared in a vehicle, possible teleport
|
||||
isEnteringVehicle = false
|
||||
isInVehicle = true
|
||||
current.vehicle = GetVehiclePedIsUsing(playerPed)
|
||||
current.seat = GetPedVehicleSeat(playerPed, current.vehicle)
|
||||
current.plate = GetVehicleNumberPlateText(current.vehicle)
|
||||
current.displayName, current.netId = GetData(current.vehicle)
|
||||
TriggerEvent("esx:enteredVehicle", current.vehicle, current.plate, current.seat, current.displayName, current.netId)
|
||||
TriggerServerEvent("esx:enteredVehicle", current.plate, current.seat, current.displayName, current.netId)
|
||||
ToggleVehicleStatus(current.vehicle, current.seat)
|
||||
end
|
||||
elseif isInVehicle then
|
||||
if (current.vehicle ~= GetVehiclePedIsUsing(playerPed)) or IsPlayerDead(PlayerId()) then
|
||||
-- bye, vehicle
|
||||
TriggerEvent("esx:exitedVehicle", current.vehicle, current.plate, current.seat, current.displayName, current.netId)
|
||||
TriggerServerEvent("esx:exitedVehicle", current.plate, current.seat, current.displayName, current.netId)
|
||||
isInVehicle = false
|
||||
current = {}
|
||||
ToggleVehicleStatus(false,false)
|
||||
end
|
||||
end
|
||||
Wait(200)
|
||||
if isActive ~= self.inPauseMenu then
|
||||
self.inPauseMenu = isActive
|
||||
TriggerEvent("esx:pauseMenuActive", isActive)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
if Config.EnableDebug then
|
||||
AddEventHandler("esx:playerPedChanged", function(netId)
|
||||
print("esx:playerPedChanged", netId)
|
||||
end)
|
||||
function Actions:EnterVehicle()
|
||||
self.seat = GetSeatPedIsTryingToEnter(ESX.PlayerData.ped)
|
||||
|
||||
AddEventHandler("esx:playerJumping", function()
|
||||
print("esx:playerJumping")
|
||||
end)
|
||||
local _, netId, plate = self:GetVehicleData()
|
||||
|
||||
AddEventHandler("esx:enteringVehicle", function(vehicle, plate, seat, netId)
|
||||
print("esx:enteringVehicle", "vehicle", vehicle, "plate", plate, "seat", seat, "netId", netId)
|
||||
end)
|
||||
self.isEnteringVehicle = true
|
||||
TriggerEvent("esx:enteringVehicle", self.vehicle, plate, self.seat, netId)
|
||||
TriggerServerEvent("esx:enteringVehicle", plate, self.seat, netId)
|
||||
|
||||
AddEventHandler("esx:enteringVehicleAborted", function()
|
||||
print("esx:enteringVehicleAborted")
|
||||
end)
|
||||
self:SetVehicleStatus()
|
||||
end
|
||||
|
||||
AddEventHandler("esx:enteredVehicle", function(vehicle, plate, seat, displayName, netId)
|
||||
print("esx:enteredVehicle", "vehicle", vehicle, "plate", plate, "seat", seat, "displayName", displayName, "netId", netId)
|
||||
end)
|
||||
function Actions:ResetVehicleData()
|
||||
self.enteringVehicle = false
|
||||
self.vehicle = false
|
||||
self.seat = false
|
||||
self.inVehicle = false
|
||||
|
||||
AddEventHandler("esx:exitedVehicle", function(vehicle, plate, seat, displayName, netId)
|
||||
print("esx:exitedVehicle", "vehicle", vehicle, "plate", plate, "seat", seat, "displayName", displayName, "netId", netId)
|
||||
self:SetVehicleStatus()
|
||||
end
|
||||
|
||||
function Actions:EnterAborted()
|
||||
self:ResetVehicleData()
|
||||
|
||||
TriggerEvent("esx:enteringVehicleAborted")
|
||||
TriggerServerEvent("esx:enteringVehicleAborted")
|
||||
end
|
||||
|
||||
function Actions:WarpEnter()
|
||||
self.enteringVehicle = false
|
||||
self.inVehicle = true
|
||||
|
||||
self.seat = self:GetSeatPedIsIn()
|
||||
|
||||
local displayName, netId, plate = self:GetVehicleData()
|
||||
|
||||
self:SetVehicleStatus()
|
||||
TriggerEvent("esx:enteredVehicle", self.vehicle, plate, self.seat, displayName, netId)
|
||||
TriggerServerEvent("esx:enteredVehicle", plate, self.seat, displayName, netId)
|
||||
end
|
||||
|
||||
function Actions:ExitVehicle()
|
||||
local currentVehicle = GetVehiclePedIsIn(ESX.PlayerData.ped, false)
|
||||
|
||||
if currentVehicle ~= self.vehicle or ESX.PlayerData.dead then
|
||||
local displayName, netId, plate = self:GetVehicleData()
|
||||
|
||||
TriggerEvent("esx:exitedVehicle", self.vehicle, plate, self.seat, displayName, netId)
|
||||
TriggerServerEvent("esx:exitedVehicle", plate, self.seat, displayName, netId)
|
||||
|
||||
self:ResetVehicleData()
|
||||
end
|
||||
end
|
||||
|
||||
function Actions:TrackVehicle()
|
||||
if not self.inVehicle and not ESX.PlayerData.dead then
|
||||
local tempVehicle = GetVehiclePedIsTryingToEnter(ESX.PlayerData.ped)
|
||||
|
||||
if DoesEntityExist(tempVehicle) and not self.enteringVehicle then
|
||||
self.vehicle = tempVehicle
|
||||
self:EnterVehicle()
|
||||
elseif not DoesEntityExist(tempVehicle) and not IsPedInAnyVehicle(ESX.PlayerData.ped, true) and self.enteringVehicle then
|
||||
self:EnterAborted()
|
||||
elseif IsPedInAnyVehicle(ESX.PlayerData.ped, false) then
|
||||
self.vehicle = GetVehiclePedIsIn(ESX.PlayerData.ped, false)
|
||||
self:WarpEnter()
|
||||
end
|
||||
elseif self.inVehicle then
|
||||
self:ExitVehicle()
|
||||
end
|
||||
end
|
||||
|
||||
function Actions:SlowLoop()
|
||||
CreateThread(function()
|
||||
while ESX.PlayerLoaded do
|
||||
self:TrackPed()
|
||||
self:TrackPedCoords()
|
||||
self:TrackPauseMenu()
|
||||
self:TrackVehicle()
|
||||
Wait(500)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function Actions:Init()
|
||||
self:SlowLoop()
|
||||
end
|
||||
|
||||
Actions:Init()
|
||||
|
||||
@@ -1,40 +1,82 @@
|
||||
local RequestId = 0
|
||||
local serverRequests = {}
|
||||
---@diagnostic disable: duplicate-set-field
|
||||
|
||||
local clientCallbacks = {}
|
||||
Callbacks = {}
|
||||
|
||||
Callbacks.requests = {}
|
||||
Callbacks.storage = {}
|
||||
Callbacks.id = 0
|
||||
|
||||
function Callbacks:Trigger(event, cb, invoker, ...)
|
||||
self.requests[self.id] = cb
|
||||
TriggerServerEvent("esx:triggerServerCallback", event, self.id, invoker, ...)
|
||||
|
||||
self.id += 1
|
||||
end
|
||||
|
||||
function Callbacks:Execute(cb, ...)
|
||||
local success, errorString = pcall(cb, ...)
|
||||
|
||||
if not success then
|
||||
print(("[^1ERROR^7] Failed to execute Callback with RequestId: ^5%s^7"):format(self.currentId))
|
||||
error(errorString)
|
||||
return
|
||||
end
|
||||
self.currentId = nil
|
||||
end
|
||||
|
||||
function Callbacks:ServerRecieve(requestId, invoker, ...)
|
||||
self.currentId = requestId
|
||||
if not self.requests[self.currentId] then
|
||||
return error(("Server Callback with requestId ^5%s^1 Was Called by ^5%s^1 but does not exist."):format(self.currentId, invoker))
|
||||
end
|
||||
|
||||
local callback = self.requests[self.currentId]
|
||||
|
||||
Callbacks:Execute(callback, ...)
|
||||
self.requests[requestId] = nil
|
||||
end
|
||||
|
||||
function Callbacks:Register(name, cb)
|
||||
self.storage[name] = cb
|
||||
end
|
||||
|
||||
function Callbacks:ClientRecieve(eventName, requestId, invoker, ...)
|
||||
self.currentId = requestId
|
||||
|
||||
if not self.storage[eventName] then
|
||||
return error(("Client Callback with requestId ^5%s^1 Was Called by ^5%s^1 but does not exist."):format(eventName, invoker))
|
||||
end
|
||||
|
||||
local returnCb = function(...)
|
||||
TriggerServerEvent("esx:clientCallback", requestId, invoker, ...)
|
||||
end
|
||||
local callback = self.storage[eventName]
|
||||
|
||||
Callbacks:Execute(callback, returnCb, ...)
|
||||
end
|
||||
|
||||
---@param eventName string
|
||||
---@param callback function
|
||||
---@param ... any
|
||||
---@return nil
|
||||
ESX.TriggerServerCallback = function(eventName, callback, ...)
|
||||
serverRequests[RequestId] = callback
|
||||
local invokingResource = GetInvokingResource()
|
||||
local invoker = (invokingResource and invokingResource ~= "unknown") and invokingResource or "es_extended"
|
||||
|
||||
TriggerServerEvent("esx:triggerServerCallback", eventName, RequestId, GetInvokingResource() or "unknown", ...)
|
||||
|
||||
RequestId = RequestId + 1
|
||||
Callbacks:Trigger(eventName, callback, invoker, ...)
|
||||
end
|
||||
|
||||
RegisterNetEvent("esx:serverCallback", function(requestId, invoker, ...)
|
||||
if not serverRequests[requestId] then
|
||||
return error(("Server Callback with requestId ^5%s^7 Was Called by ^5%s^7 but does not exist."):format(requestId, invoker))
|
||||
end
|
||||
|
||||
serverRequests[requestId](...)
|
||||
serverRequests[requestId] = nil
|
||||
RegisterNetEvent("esx:serverCallback", function(...)
|
||||
Callbacks:ServerRecieve(...)
|
||||
end)
|
||||
|
||||
---@param eventName string
|
||||
---@param callback function
|
||||
---@return nil
|
||||
ESX.RegisterClientCallback = function(eventName, callback)
|
||||
clientCallbacks[eventName] = callback
|
||||
Callbacks:Register(eventName, callback)
|
||||
end
|
||||
|
||||
RegisterNetEvent("esx:triggerClientCallback", function(eventName, requestId, invoker, ...)
|
||||
if not clientCallbacks[eventName] then
|
||||
return error(("Client Callback not registered, name: ^5%s^7, invoker resource: ^5%s^7"):format(eventName, invoker))
|
||||
end
|
||||
|
||||
clientCallbacks[eventName](function(...)
|
||||
TriggerServerEvent("esx:clientCallback", requestId, invoker, ...)
|
||||
end, ...)
|
||||
RegisterNetEvent("esx:triggerClientCallback", function(...)
|
||||
Callbacks:ClientRecieve(...)
|
||||
end)
|
||||
|
||||
@@ -1,27 +1,16 @@
|
||||
AddEventHandler("gameEventTriggered", function(event, data)
|
||||
if event ~= "CEventNetworkEntityDamage" then
|
||||
return
|
||||
end
|
||||
local victim, victimDied = data[1], data[4]
|
||||
if not IsPedAPlayer(victim) then
|
||||
return
|
||||
end
|
||||
local player = PlayerId()
|
||||
local playerPed = PlayerPedId()
|
||||
if victimDied and NetworkGetPlayerIndexFromPed(victim) == player and (IsPedDeadOrDying(victim, true) or IsPedFatallyInjured(victim)) then
|
||||
local killerEntity, deathCause = GetPedSourceOfDeath(playerPed), GetPedCauseOfDeath(playerPed)
|
||||
local killerClientId = NetworkGetPlayerIndexFromPed(killerEntity)
|
||||
if killerEntity ~= playerPed and killerClientId and NetworkIsPlayerActive(killerClientId) then
|
||||
PlayerKilledByPlayer(GetPlayerServerId(killerClientId), killerClientId, deathCause)
|
||||
else
|
||||
PlayerKilled(deathCause)
|
||||
end
|
||||
end
|
||||
end)
|
||||
Death = {}
|
||||
Death._index = Death
|
||||
|
||||
function PlayerKilledByPlayer(killerServerId, killerClientId, deathCause)
|
||||
local victimCoords = GetEntityCoords(PlayerPedId())
|
||||
local killerCoords = GetEntityCoords(GetPlayerPed(killerClientId))
|
||||
function Death:ResetValues()
|
||||
self.killerEntity = nil
|
||||
self.deathCause = nil
|
||||
self.killerId = nil
|
||||
self.killerServerId = nil
|
||||
end
|
||||
|
||||
function Death:ByPlayer()
|
||||
local victimCoords = GetEntityCoords(ESX.PlayerData.ped)
|
||||
local killerCoords = GetEntityCoords(self.killerEntity)
|
||||
local distance = #(victimCoords - killerCoords)
|
||||
|
||||
local data = {
|
||||
@@ -29,28 +18,65 @@ function PlayerKilledByPlayer(killerServerId, killerClientId, deathCause)
|
||||
killerCoords = { x = ESX.Math.Round(killerCoords.x, 1), y = ESX.Math.Round(killerCoords.y, 1), z = ESX.Math.Round(killerCoords.z, 1) },
|
||||
|
||||
killedByPlayer = true,
|
||||
deathCause = deathCause,
|
||||
deathCause = self.deathCause,
|
||||
distance = ESX.Math.Round(distance, 1),
|
||||
|
||||
killerServerId = killerServerId,
|
||||
killerClientId = killerClientId,
|
||||
killerServerId = self.killerServerId,
|
||||
killerClientId = self.killerId,
|
||||
}
|
||||
|
||||
self:ResetValues()
|
||||
TriggerEvent("esx:onPlayerDeath", data)
|
||||
TriggerServerEvent("esx:onPlayerDeath", data)
|
||||
end
|
||||
|
||||
function PlayerKilled(deathCause)
|
||||
local playerPed = PlayerPedId()
|
||||
local victimCoords = GetEntityCoords(playerPed)
|
||||
function Death:Natual()
|
||||
local coords = GetEntityCoords(ESX.PlayerData.ped)
|
||||
|
||||
local data = {
|
||||
victimCoords = { x = ESX.Math.Round(victimCoords.x, 1), y = ESX.Math.Round(victimCoords.y, 1), z = ESX.Math.Round(victimCoords.z, 1) },
|
||||
victimCoords = { x = ESX.Math.Round(coords.x, 1), y = ESX.Math.Round(coords.y, 1), z = ESX.Math.Round(coords.z, 1) },
|
||||
|
||||
killedByPlayer = false,
|
||||
deathCause = deathCause,
|
||||
deathCause = self.deathCause,
|
||||
}
|
||||
|
||||
self:ResetValues()
|
||||
TriggerEvent("esx:onPlayerDeath", data)
|
||||
TriggerServerEvent("esx:onPlayerDeath", data)
|
||||
end
|
||||
|
||||
function Death:Damaged(victim, victimDied)
|
||||
if not IsEntityAPed(victim) then
|
||||
return
|
||||
end
|
||||
|
||||
if IsPedAPlayer(victim) then
|
||||
return
|
||||
end
|
||||
|
||||
local victimId = NetworkGetPlayerIndexFromPed(victim)
|
||||
local isDead = IsPedDeadOrDying(victim, true) or IsPedFatallyInjured(victim)
|
||||
if not victimDied or victimId ~= ESX.playerId or not isDead then
|
||||
return
|
||||
end
|
||||
|
||||
self.killerEntity = GetPedSourceOfDeath(ESX.PlayerData.ped)
|
||||
self.deathCause = GetPedCauseOfDeath(ESX.PlayerData.ped)
|
||||
self.killerId = NetworkGetPlayerIndexFromPed(self.killerEntity)
|
||||
self.killerServerId = GetPlayerServerId(self.killerId)
|
||||
|
||||
local isActive = NetworkIsPlayerActive(self.killerId)
|
||||
|
||||
if self.killerEntity ~= ESX.PlayerData.ped and self.killerId and isActive then
|
||||
self:ByPlayer()
|
||||
else
|
||||
self:Natual()
|
||||
end
|
||||
end
|
||||
|
||||
AddEventHandler("gameEventTriggered", function(event, data)
|
||||
if event ~= "CEventNetworkEntityDamage" then
|
||||
return
|
||||
end
|
||||
Death:Damaged(data[1], data[4])
|
||||
end)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
local interactions = {}
|
||||
local pressedInteractions = {}
|
||||
|
||||
function ESX.RemoveInteraction(name)
|
||||
if not interactions[name] then return end
|
||||
interactions[name] = nil
|
||||
end
|
||||
|
||||
ESX.RegisterInteraction = function(name, onPress, condition)
|
||||
interactions[name] = {
|
||||
condition = condition or function() return true end,
|
||||
onPress = onPress,
|
||||
creator = GetInvokingResource() or "es_extended"
|
||||
}
|
||||
end
|
||||
|
||||
function ESX.GetInteractKey()
|
||||
local hash = joaat('esx_interact') | 0x80000000
|
||||
return GetControlInstructionalButton(0, hash, true):sub(3)
|
||||
end
|
||||
|
||||
ESX.RegisterInput("esx_interact", "Interact", "keyboard", "e", function()
|
||||
for _, interaction in pairs(interactions) do
|
||||
local success, result = pcall(interaction.condition)
|
||||
if success and result then
|
||||
pressedInteractions[#pressedInteractions+1] = interaction
|
||||
interaction.onPress()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
AddEventHandler("onResourceStop", function(resource)
|
||||
for name, interaction in pairs(interactions) do
|
||||
if interaction.creator == resource then
|
||||
interactions[name] = nil
|
||||
end
|
||||
end
|
||||
end)
|
||||
@@ -10,7 +10,7 @@ function ESX.Scaleform.ShowFreemodeMessage(title, msg, sec)
|
||||
Wait(0)
|
||||
sec = sec - 0.01
|
||||
|
||||
DrawScaleformMovieFullscreen(scaleform, 255, 255, 255, 255)
|
||||
DrawScaleformMovieFullscreen(scaleform, 255, 255, 255, 255, 0)
|
||||
end
|
||||
|
||||
SetScaleformMovieAsNoLongerNeeded(scaleform)
|
||||
@@ -41,7 +41,7 @@ function ESX.Scaleform.ShowBreakingNews(title, msg, bottom, sec)
|
||||
Wait(0)
|
||||
sec = sec - 0.01
|
||||
|
||||
DrawScaleformMovieFullscreen(scaleform, 255, 255, 255, 255)
|
||||
DrawScaleformMovieFullscreen(scaleform, 255, 255, 255, 255, 0)
|
||||
end
|
||||
|
||||
SetScaleformMovieAsNoLongerNeeded(scaleform)
|
||||
@@ -64,7 +64,7 @@ function ESX.Scaleform.ShowPopupWarning(title, msg, bottom, sec)
|
||||
Wait(0)
|
||||
sec = sec - 0.01
|
||||
|
||||
DrawScaleformMovieFullscreen(scaleform, 255, 255, 255, 255)
|
||||
DrawScaleformMovieFullscreen(scaleform, 255, 255, 255, 255, 0)
|
||||
end
|
||||
|
||||
SetScaleformMovieAsNoLongerNeeded(scaleform)
|
||||
@@ -81,7 +81,7 @@ function ESX.Scaleform.ShowTrafficMovie(sec)
|
||||
Wait(0)
|
||||
sec = sec - 0.01
|
||||
|
||||
DrawScaleformMovieFullscreen(scaleform, 255, 255, 255, 255)
|
||||
DrawScaleformMovieFullscreen(scaleform, 255, 255, 255, 255, 0)
|
||||
end
|
||||
|
||||
SetScaleformMovieAsNoLongerNeeded(scaleform)
|
||||
|
||||
@@ -1,37 +1,68 @@
|
||||
---@param modelHash number | string
|
||||
---@param cb? function
|
||||
---@return number | nil
|
||||
function ESX.Streaming.RequestModel(modelHash, cb)
|
||||
modelHash = type(modelHash) == "number" and modelHash or joaat(modelHash)
|
||||
|
||||
if not IsModelInCdimage(modelHash) then return end
|
||||
|
||||
RequestModel(modelHash)
|
||||
while not HasModelLoaded(modelHash) do Wait() end
|
||||
while not HasModelLoaded(modelHash) do Wait(500) end
|
||||
|
||||
return cb and cb(modelHash) or modelHash
|
||||
end
|
||||
|
||||
---@param textureDict string
|
||||
---@param cb? function
|
||||
---@return string | nil
|
||||
function ESX.Streaming.RequestStreamedTextureDict(textureDict, cb)
|
||||
RequestStreamedTextureDict(textureDict)
|
||||
while not HasStreamedTextureDictLoaded(textureDict) do Wait() end
|
||||
RequestStreamedTextureDict(textureDict, false)
|
||||
|
||||
while not HasStreamedTextureDictLoaded(textureDict) do Wait(500) end
|
||||
|
||||
return cb and cb(textureDict) or textureDict
|
||||
end
|
||||
|
||||
---@param assetName string
|
||||
---@param cb? function
|
||||
---@return string | nil
|
||||
function ESX.Streaming.RequestNamedPtfxAsset(assetName, cb)
|
||||
RequestNamedPtfxAsset(assetName)
|
||||
while not HasNamedPtfxAssetLoaded(assetName) do Wait() end
|
||||
|
||||
while not HasNamedPtfxAssetLoaded(assetName) do Wait(500) end
|
||||
|
||||
return cb and cb(assetName) or assetName
|
||||
end
|
||||
|
||||
---@param animSet string
|
||||
---@param cb? function
|
||||
---@return string | nil
|
||||
function ESX.Streaming.RequestAnimSet(animSet, cb)
|
||||
RequestAnimSet(animSet)
|
||||
while not HasAnimSetLoaded(animSet) do Wait() end
|
||||
|
||||
while not HasAnimSetLoaded(animSet) do Wait(500) end
|
||||
|
||||
return cb and cb(animSet) or animSet
|
||||
end
|
||||
|
||||
---@param animDict string
|
||||
---@param cb? function
|
||||
---@return string | nil
|
||||
function ESX.Streaming.RequestAnimDict(animDict, cb)
|
||||
RequestAnimDict(animDict)
|
||||
while not HasAnimDictLoaded(animDict) do Wait() end
|
||||
|
||||
while not HasAnimDictLoaded(animDict) do Wait(500) end
|
||||
|
||||
return cb and cb(animDict) or animDict
|
||||
end
|
||||
|
||||
---@param weaponHash number | string
|
||||
---@param cb? function
|
||||
---@return string | number | nil
|
||||
function ESX.Streaming.RequestWeaponAsset(weaponHash, cb)
|
||||
RequestWeaponAsset(weaponHash)
|
||||
while not HasWeaponAssetLoaded(weaponHash) do Wait() end
|
||||
RequestWeaponAsset(weaponHash, 31, 0)
|
||||
|
||||
while not HasWeaponAssetLoaded(weaponHash) do Wait(500) end
|
||||
|
||||
return cb and cb(weaponHash) or weaponHash
|
||||
end
|
||||
|
||||
@@ -20,16 +20,21 @@ CreateThread(function()
|
||||
end
|
||||
end)
|
||||
|
||||
---@param length number
|
||||
---@return string
|
||||
function ESX.GetRandomString(length)
|
||||
math.randomseed(GetGameTimer())
|
||||
|
||||
return length > 0 and ESX.GetRandomString(length - 1) .. Charset[math.random(1, #Charset)] or ""
|
||||
end
|
||||
|
||||
---@return table
|
||||
function ESX.GetConfig()
|
||||
return Config
|
||||
end
|
||||
|
||||
---@param weaponName string
|
||||
---@return number, table
|
||||
function ESX.GetWeapon(weaponName)
|
||||
weaponName = string.upper(weaponName)
|
||||
|
||||
@@ -39,16 +44,22 @@ function ESX.GetWeapon(weaponName)
|
||||
return index, Config.Weapons[index]
|
||||
end
|
||||
|
||||
---@param weaponHash number
|
||||
---@return table
|
||||
function ESX.GetWeaponFromHash(weaponHash)
|
||||
weaponHash = type(weaponHash) == "string" and joaat(weaponHash) or weaponHash
|
||||
|
||||
return weaponsByHash[weaponHash]
|
||||
end
|
||||
|
||||
---@param byHash boolean
|
||||
---@return table
|
||||
function ESX.GetWeaponList(byHash)
|
||||
return byHash and weaponsByHash or Config.Weapons
|
||||
end
|
||||
|
||||
---@param weaponName string
|
||||
---@return string
|
||||
function ESX.GetWeaponLabel(weaponName)
|
||||
weaponName = string.upper(weaponName)
|
||||
|
||||
@@ -58,6 +69,9 @@ function ESX.GetWeaponLabel(weaponName)
|
||||
return Config.Weapons[index].label or ""
|
||||
end
|
||||
|
||||
---@param weaponName string
|
||||
---@param weaponComponent string
|
||||
---@return table | nil
|
||||
function ESX.GetWeaponComponent(weaponName, weaponComponent)
|
||||
weaponName = string.upper(weaponName)
|
||||
|
||||
@@ -71,6 +85,9 @@ function ESX.GetWeaponComponent(weaponName, weaponComponent)
|
||||
end
|
||||
end
|
||||
|
||||
---@param table table
|
||||
---@param nb? number
|
||||
---@return string
|
||||
function ESX.DumpTable(table, nb)
|
||||
if nb == nil then
|
||||
nb = 0
|
||||
@@ -103,10 +120,16 @@ function ESX.DumpTable(table, nb)
|
||||
end
|
||||
end
|
||||
|
||||
---@param value any
|
||||
---@param numDecimalPlaces? number
|
||||
---@return number
|
||||
function ESX.Round(value, numDecimalPlaces)
|
||||
return ESX.Math.Round(value, numDecimalPlaces)
|
||||
end
|
||||
|
||||
---@param value string
|
||||
---@param ... any
|
||||
---@return boolean, string?
|
||||
function ESX.ValidateType(value, ...)
|
||||
local types = { ... }
|
||||
if #types == 0 then return true end
|
||||
@@ -132,6 +155,8 @@ function ESX.ValidateType(value, ...)
|
||||
return true
|
||||
end
|
||||
|
||||
---@param ... any
|
||||
---@return boolean
|
||||
function ESX.AssertType(...)
|
||||
local matches, errorMessage = ESX.ValidateType(...)
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
ESX = {}
|
||||
|
||||
exports("getSharedObject", function()
|
||||
return ESX
|
||||
end)
|
||||
|
||||
AddEventHandler("esx:getSharedObject", function()
|
||||
local Invoke = GetInvokingResource()
|
||||
error(("Resource ^5%s^1 Used the ^5getSharedObject^1 Event, this event ^1no longer exists!^1 Visit https://documentation.esx-framework.org/tutorials/tutorials-esx/sharedevent for how to fix!"):format(Invoke))
|
||||
end)
|
||||
@@ -1,5 +1,8 @@
|
||||
ESX.Math = {}
|
||||
|
||||
---@param value number
|
||||
---@param numDecimalPlaces? number
|
||||
---@return number
|
||||
function ESX.Math.Round(value, numDecimalPlaces)
|
||||
if numDecimalPlaces then
|
||||
local power = 10 ^ numDecimalPlaces
|
||||
@@ -10,20 +13,24 @@ function ESX.Math.Round(value, numDecimalPlaces)
|
||||
end
|
||||
|
||||
-- credit http://richard.warburton.it
|
||||
---@param value number
|
||||
---@return string
|
||||
function ESX.Math.GroupDigits(value)
|
||||
local left, num, right = string.match(value, "^([^%d]*%d)(%d*)(.-)$")
|
||||
|
||||
return left .. (num:reverse():gsub("(%d%d%d)", "%1" .. TranslateCap("locale_digit_grouping_symbol")):reverse()) .. right
|
||||
end
|
||||
|
||||
---@param value string | number
|
||||
---@return string | nil
|
||||
function ESX.Math.Trim(value)
|
||||
if value then
|
||||
return (string.gsub(value, "^%s*(.-)%s*$", "%1"))
|
||||
else
|
||||
return nil
|
||||
end
|
||||
value = tostring(value)
|
||||
return (string.gsub(value, "^%s*(.-)%s*$", "%1"))
|
||||
end
|
||||
|
||||
---@param minRange number
|
||||
---@param maxRange number
|
||||
---@return number
|
||||
function ESX.Math.Random(minRange, maxRange)
|
||||
math.randomseed(GetGameTimer())
|
||||
return math.random(minRange or 1, maxRange or 10)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
ESX.Table = {}
|
||||
|
||||
-- nil proof alternative to #table
|
||||
---@param t table
|
||||
---@return number
|
||||
function ESX.Table.SizeOf(t)
|
||||
local count = 0
|
||||
|
||||
@@ -11,6 +13,8 @@ function ESX.Table.SizeOf(t)
|
||||
return count
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@return table
|
||||
function ESX.Table.Set(t)
|
||||
local set = {}
|
||||
for _, v in ipairs(t) do
|
||||
@@ -19,6 +23,9 @@ function ESX.Table.Set(t)
|
||||
return set
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@param value any
|
||||
---@return number
|
||||
function ESX.Table.IndexOf(t, value)
|
||||
for i = 1, #t, 1 do
|
||||
if t[i] == value then
|
||||
@@ -29,6 +36,9 @@ function ESX.Table.IndexOf(t, value)
|
||||
return -1
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@param value any
|
||||
---@return number
|
||||
function ESX.Table.LastIndexOf(t, value)
|
||||
for i = #t, 1, -1 do
|
||||
if t[i] == value then
|
||||
@@ -39,6 +49,9 @@ function ESX.Table.LastIndexOf(t, value)
|
||||
return -1
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@param cb function
|
||||
---@return any
|
||||
function ESX.Table.Find(t, cb)
|
||||
for i = 1, #t, 1 do
|
||||
if cb(t[i]) then
|
||||
@@ -49,6 +62,9 @@ function ESX.Table.Find(t, cb)
|
||||
return nil
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@param cb function
|
||||
---@return number
|
||||
function ESX.Table.FindIndex(t, cb)
|
||||
for i = 1, #t, 1 do
|
||||
if cb(t[i]) then
|
||||
@@ -59,18 +75,24 @@ function ESX.Table.FindIndex(t, cb)
|
||||
return -1
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@param cb function
|
||||
---@return table
|
||||
function ESX.Table.Filter(t, cb)
|
||||
local newTable = {}
|
||||
|
||||
for i = 1, #t, 1 do
|
||||
if cb(t[i]) then
|
||||
table.insert(newTable, t[i])
|
||||
newTable[#newTable + 1] = t[i]
|
||||
end
|
||||
end
|
||||
|
||||
return newTable
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@param cb function
|
||||
---@return table
|
||||
function ESX.Table.Map(t, cb)
|
||||
local newTable = {}
|
||||
|
||||
@@ -81,6 +103,8 @@ function ESX.Table.Map(t, cb)
|
||||
return newTable
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@return table
|
||||
function ESX.Table.Reverse(t)
|
||||
local newTable = {}
|
||||
|
||||
@@ -91,6 +115,8 @@ function ESX.Table.Reverse(t)
|
||||
return newTable
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@return table
|
||||
function ESX.Table.Clone(t)
|
||||
if type(t) ~= "table" then
|
||||
return t
|
||||
@@ -112,6 +138,9 @@ function ESX.Table.Clone(t)
|
||||
return target
|
||||
end
|
||||
|
||||
---@param t1 table
|
||||
---@param t2 table
|
||||
---@return table
|
||||
function ESX.Table.Concat(t1, t2)
|
||||
local t3 = ESX.Table.Clone(t1)
|
||||
|
||||
@@ -122,6 +151,9 @@ function ESX.Table.Concat(t1, t2)
|
||||
return t3
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@param sep string
|
||||
---@return string
|
||||
function ESX.Table.Join(t, sep)
|
||||
local str = ""
|
||||
|
||||
@@ -137,6 +169,9 @@ function ESX.Table.Join(t, sep)
|
||||
end
|
||||
|
||||
-- Credits: https://github.com/JonasDev99/qb-garages/blob/b0335d67cb72a6b9ac60f62a87fb3946f5c2f33d/server/main.lua#L5
|
||||
---@param tab table
|
||||
---@param val any
|
||||
---@return boolean
|
||||
function ESX.Table.TableContains(tab, val)
|
||||
if type(val) == "table" then
|
||||
for _, value in pairs(tab) do
|
||||
@@ -157,6 +192,9 @@ end
|
||||
|
||||
-- Credit: https://stackoverflow.com/a/15706820
|
||||
-- Description: sort function for pairs
|
||||
---@param t table
|
||||
---@param order function
|
||||
---@return function
|
||||
function ESX.Table.Sort(t, order)
|
||||
-- collect the keys
|
||||
local keys = {}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
local TimeoutCount = 0
|
||||
local CancelledTimeouts = {}
|
||||
|
||||
---@param msec number
|
||||
---@param cb function
|
||||
---@return number
|
||||
ESX.SetTimeout = function(msec, cb)
|
||||
local id <const> = TimeoutCount + 1
|
||||
|
||||
@@ -18,6 +21,8 @@ ESX.SetTimeout = function(msec, cb)
|
||||
return id
|
||||
end
|
||||
|
||||
---@param id number
|
||||
---@return nil
|
||||
ESX.ClearTimeout = function(id)
|
||||
CancelledTimeouts[id] = true
|
||||
end
|
||||
|
||||
@@ -59,8 +59,8 @@ Config.AdminLogging = false -- Logs the usage of certain commands by those with
|
||||
Config.DisableHealthRegeneration = false -- Player will no longer regenerate health
|
||||
Config.DisableVehicleRewards = false -- Disables Player Receiving weapons from vehicles
|
||||
Config.DisableNPCDrops = false -- stops NPCs from dropping weapons on death
|
||||
Config.DisableDispatchServices = false -- Disable Dispatch services
|
||||
Config.DisableScenarios = false -- Disable Scenarios
|
||||
Config.DisableDispatchServices = true -- Disable Dispatch services
|
||||
Config.DisableScenarios = true -- Disable Scenarios
|
||||
Config.DisableWeaponWheel = false -- Disables default weapon wheel
|
||||
Config.DisableAimAssist = false -- disables AIM assist (mainly on controllers)
|
||||
Config.DisableVehicleSeatShuff = false -- Disables vehicle seat shuff
|
||||
@@ -71,10 +71,10 @@ Config.RemoveHudComponents = {
|
||||
[3] = false, --CASH
|
||||
[4] = false, --MP_CASH
|
||||
[5] = false, --MP_MESSAGE
|
||||
[6] = false, --VEHICLE_NAME
|
||||
[7] = false, -- AREA_NAME
|
||||
[8] = false, -- VEHICLE_CLASS
|
||||
[9] = false, --STREET_NAME
|
||||
[6] = true, --VEHICLE_NAME
|
||||
[7] = true, -- AREA_NAME
|
||||
[8] = true, -- VEHICLE_CLASS
|
||||
[9] = true, --STREET_NAME
|
||||
[10] = false, --HELP_TEXT
|
||||
[11] = false, --FLOATING_HELP_TEXT_1
|
||||
[12] = false, --FLOATING_HELP_TEXT_2
|
||||
|
||||
@@ -11,6 +11,10 @@ shared_scripts {
|
||||
|
||||
'config.lua',
|
||||
'config.weapons.lua',
|
||||
|
||||
'common/main.lua',
|
||||
'common/functions.lua',
|
||||
'common/modules/*.lua',
|
||||
}
|
||||
|
||||
server_scripts {
|
||||
@@ -27,27 +31,23 @@ server_scripts {
|
||||
'server/main.lua',
|
||||
'server/commands.lua',
|
||||
|
||||
'common/modules/*.lua',
|
||||
'common/functions.lua',
|
||||
'server/modules/actions.lua',
|
||||
'server/modules/npwd.lua',
|
||||
'server/modules/createJob.lua'
|
||||
}
|
||||
|
||||
client_scripts {
|
||||
'client/common.lua',
|
||||
'client/common.lua',
|
||||
'client/functions.lua',
|
||||
'client/wrapper.lua',
|
||||
'client/modules/callback.lua',
|
||||
|
||||
'client/main.lua',
|
||||
|
||||
'common/modules/*.lua',
|
||||
'common/functions.lua',
|
||||
|
||||
'client/modules/actions.lua',
|
||||
'client/modules/death.lua',
|
||||
'client/modules/npwd.lua',
|
||||
'client/modules/interactions.lua',
|
||||
'client/modules/scaleform.lua',
|
||||
'client/modules/streaming.lua',
|
||||
}
|
||||
@@ -75,5 +75,4 @@ dependencies {
|
||||
'/native:0x6AE51D4B',
|
||||
'/native:0xA61C8FC6',
|
||||
'oxmysql',
|
||||
'spawnmanager',
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ Locales = {}
|
||||
|
||||
function Translate(str, ...) -- Translate string
|
||||
if not str then
|
||||
error(("Resource ^5%s^7 You did not specify a parameter for the Translate function or the value is nil!"):format(GetInvokingResource() or GetCurrentResourceName()))
|
||||
error(("Resource ^5%s^1 You did not specify a parameter for the Translate function or the value is nil!"):format(GetInvokingResource() or GetCurrentResourceName()))
|
||||
return "Given translate function parameter is nil!"
|
||||
end
|
||||
if Locales[Config.Locale] then
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
local _GetPlayerPed = GetPlayerPed
|
||||
local _GetEntityCoords = GetEntityCoords
|
||||
local _GetEntityHeading = GetEntityHeading
|
||||
local _ExecuteCommand = ExecuteCommand
|
||||
local _SetEntityCoords = SetEntityCoords
|
||||
local _SetEntityHeading = SetEntityHeading
|
||||
local _TriggerClientEvent = TriggerClientEvent
|
||||
local _DropPlayer = DropPlayer
|
||||
local _TriggerEvent = TriggerEvent
|
||||
local _GiveWeaponToPed = GiveWeaponToPed
|
||||
local _SetPedAmmo = SetPedAmmo
|
||||
local _RemoveWeaponFromPed = RemoveWeaponFromPed
|
||||
local _assert = assert
|
||||
---@class xPlayer
|
||||
---@field accounts table
|
||||
---@field coords table
|
||||
---@field group string
|
||||
---@field identifier string
|
||||
---@field inventory table
|
||||
---@field job table
|
||||
---@field loadout table
|
||||
---@field name string
|
||||
---@field playerId number
|
||||
---@field source number
|
||||
---@field variables table
|
||||
---@field weight number
|
||||
---@field maxWeight number
|
||||
---@field metadata table
|
||||
---@field lastPlaytime number
|
||||
---@field admin boolean
|
||||
---@field license string
|
||||
|
||||
---@param playerId number
|
||||
---@param identifier string
|
||||
@@ -26,6 +31,7 @@ local _assert = assert
|
||||
function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory, weight, job, loadout, name, coords, metadata)
|
||||
local targetOverrides = Config.PlayerFunctionOverride and Core.PlayerFunctionOverrides[Config.PlayerFunctionOverride] or {}
|
||||
|
||||
---@class xPlayer
|
||||
local self = {}
|
||||
|
||||
self.accounts = accounts
|
||||
@@ -42,11 +48,16 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
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
|
||||
self.license = "license" .. identifier:sub(identifier:find(":"), identifier:len())
|
||||
local startIndex = identifier:find(":", 1)
|
||||
if startIndex then
|
||||
self.license = ("license%s"):format(identifier:sub(startIndex, identifier:len()))
|
||||
end
|
||||
else
|
||||
self.license = "license:" .. identifier
|
||||
self.license = ("license:%s"):format(identifier)
|
||||
end
|
||||
|
||||
if type(metadata.jobduty) ~= "boolean" then
|
||||
@@ -55,7 +66,7 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
self.job.duty = metadata.jobduty
|
||||
end
|
||||
|
||||
_ExecuteCommand(("add_principal identifier.%s group.%s"):format(self.license, self.group))
|
||||
ExecuteCommand(("add_principal identifier.%s group.%s"):format(self.license, self.group))
|
||||
|
||||
local stateBag = Player(self.source).state
|
||||
stateBag:set("identifier", self.identifier, false)
|
||||
@@ -66,33 +77,44 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
|
||||
---@param eventName string
|
||||
---@param ... any
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.triggerEvent(eventName, ...)
|
||||
_assert(type(eventName) == "string", "eventName should be string!")
|
||||
_TriggerClientEvent(eventName, self.source, ...)
|
||||
assert(type(eventName) == "string", "eventName should be string!")
|
||||
TriggerClientEvent(eventName, self.source, ...)
|
||||
end
|
||||
|
||||
---@param toggle boolean
|
||||
---@return nil
|
||||
function self.togglePaycheck(toggle)
|
||||
self.paycheckEnabled = toggle
|
||||
end
|
||||
|
||||
---@return boolean
|
||||
function self.isPaycheckEnabled()
|
||||
return self.paycheckEnabled
|
||||
end
|
||||
|
||||
---@param coordinates vector4 | vector3 | table
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.setCoords(coordinates)
|
||||
local ped <const> = _GetPlayerPed(self.source)
|
||||
local vector = type(coordinates) == "vector4" and coordinates or type(coordinates) == "vector3" and vector4(coordinates, 0.0) or vec(coordinates.x, coordinates.y, coordinates.z, coordinates.heading or 0.0)
|
||||
_SetEntityCoords(ped, vector.xyz, false, false, false, false)
|
||||
_SetEntityHeading(ped, vector.w)
|
||||
local ped <const> = GetPlayerPed(self.source)
|
||||
|
||||
SetEntityCoords(ped, coordinates.x, coordinates.y, coordinates.z, false, false, false, false)
|
||||
SetEntityHeading(ped, coordinates.w or coordinates.heading or 0.0)
|
||||
end
|
||||
|
||||
---@param vector boolean
|
||||
---@param heading boolean
|
||||
---@return vector3 | vector4 | table
|
||||
function self.getCoords(vector, heading)
|
||||
local ped <const> = _GetPlayerPed(self.source)
|
||||
local entityCoords <const> = _GetEntityCoords(ped)
|
||||
local entityHeading <const> = _GetEntityHeading(ped)
|
||||
local ped <const> = GetPlayerPed(self.source)
|
||||
local entityCoords <const> = GetEntityCoords(ped)
|
||||
local entityHeading <const> = GetEntityHeading(ped)
|
||||
|
||||
local coordinates = { x = entityCoords.x, y = entityCoords.y, z = entityCoords.z }
|
||||
|
||||
if vector then
|
||||
coordinates = (heading and vector4(entityCoords.xyz, entityHeading) or entityCoords)
|
||||
coordinates = (heading and vector4(entityCoords.x, entityCoords.y, entityCoords.z, entityHeading) or entityCoords)
|
||||
else
|
||||
if heading then
|
||||
coordinates.heading = entityHeading
|
||||
@@ -103,15 +125,21 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
end
|
||||
|
||||
---@param reason string
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.kick(reason)
|
||||
_DropPlayer(self.source, reason)
|
||||
local source <const> = tostring(self.source)
|
||||
DropPlayer(source, reason)
|
||||
end
|
||||
|
||||
---@return number
|
||||
function self.getPlayTime()
|
||||
return self.lastPlaytime + GetPlayerTimeOnline(self.source)
|
||||
end
|
||||
|
||||
---@param money number
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.setMoney(money)
|
||||
_assert(type(money) == "number", "money should be number!")
|
||||
assert(type(money) == "number", "money should be number!")
|
||||
money = ESX.Math.Round(money)
|
||||
self.setAccountMoney("money", money)
|
||||
end
|
||||
@@ -123,7 +151,7 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
|
||||
---@param money number
|
||||
---@param reason string
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.addMoney(money, reason)
|
||||
money = ESX.Math.Round(money)
|
||||
self.addAccountMoney("money", money, reason)
|
||||
@@ -131,7 +159,7 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
|
||||
---@param money number
|
||||
---@param reason string
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.removeMoney(money, reason)
|
||||
money = ESX.Math.Round(money)
|
||||
self.removeAccountMoney("money", money, reason)
|
||||
@@ -143,19 +171,19 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
end
|
||||
|
||||
---@param newGroup string
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.setGroup(newGroup)
|
||||
local lastGroup = self.group
|
||||
|
||||
_ExecuteCommand(("remove_principal identifier.%s group.%s"):format(self.license, 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)
|
||||
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))
|
||||
ExecuteCommand(("add_principal identifier.%s group.%s"):format(self.license, self.group))
|
||||
end
|
||||
|
||||
---@return string
|
||||
@@ -165,7 +193,7 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
|
||||
---@param k string
|
||||
---@param v any
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.set(k, v)
|
||||
self.variables[k] = v
|
||||
end
|
||||
@@ -266,7 +294,7 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
end
|
||||
|
||||
---@param newName string
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.setName(newName)
|
||||
self.name = newName
|
||||
Player(self.source).state:set("name", self.name, true)
|
||||
@@ -274,12 +302,12 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
|
||||
---@param accountName string
|
||||
---@param money number
|
||||
---@param reason string
|
||||
---@return void
|
||||
---@param reason string | nil
|
||||
---@return nil
|
||||
function self.setAccountMoney(accountName, money, reason)
|
||||
reason = reason or "unknown"
|
||||
if not tonumber(money) then
|
||||
error(("Tried To Set Account ^5%s^0 For Player ^5%s^0 To An Invalid Number -> ^5%s^7"):format(accountName, self.playerId, money))
|
||||
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
|
||||
@@ -290,23 +318,23 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
self.accounts[account.index].money = money
|
||||
|
||||
self.triggerEvent("esx:setAccountMoney", account)
|
||||
_TriggerEvent("esx:setAccountMoney", self.source, accountName, money, reason)
|
||||
TriggerEvent("esx:setAccountMoney", self.source, accountName, money, reason)
|
||||
else
|
||||
error(("Tried To Set Invalid Account ^5%s^0 For Player ^5%s^0!"):format(accountName, self.playerId))
|
||||
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^0 For Player ^5%s^0 To An Invalid Number -> ^5%s^7"):format(accountName, self.playerId, money))
|
||||
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
|
||||
|
||||
---@param accountName string
|
||||
---@param money number
|
||||
---@param reason string
|
||||
---@return void
|
||||
---@param reason string | nil
|
||||
---@return nil
|
||||
function self.addAccountMoney(accountName, money, reason)
|
||||
reason = reason or "Unknown"
|
||||
if not tonumber(money) then
|
||||
error(("Tried To Set Account ^5%s^0 For Player ^5%s^0 To An Invalid Number -> ^5%s^7"):format(accountName, self.playerId, money))
|
||||
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
|
||||
@@ -316,23 +344,23 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
self.accounts[account.index].money = self.accounts[account.index].money + money
|
||||
|
||||
self.triggerEvent("esx:setAccountMoney", account)
|
||||
_TriggerEvent("esx:addAccountMoney", self.source, accountName, money, reason)
|
||||
TriggerEvent("esx:addAccountMoney", self.source, accountName, money, reason)
|
||||
else
|
||||
error(("Tried To Set Add To Invalid Account ^5%s^0 For Player ^5%s^0!"):format(accountName, self.playerId))
|
||||
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^0 For Player ^5%s^0 To An Invalid Number -> ^5%s^7"):format(accountName, self.playerId, money))
|
||||
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
|
||||
|
||||
---@param accountName string
|
||||
---@param money number
|
||||
---@param reason string
|
||||
---@return void
|
||||
---@param reason string | nil
|
||||
---@return nil
|
||||
function self.removeAccountMoney(accountName, money, reason)
|
||||
reason = reason or "Unknown"
|
||||
if not tonumber(money) then
|
||||
error(("Tried To Set Account ^5%s^0 For Player ^5%s^0 To An Invalid Number -> ^5%s^7"):format(accountName, self.playerId, money))
|
||||
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
|
||||
@@ -341,18 +369,18 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
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^0 For Player ^5%s^0!"):format(accountName, self.playerId))
|
||||
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)
|
||||
TriggerEvent("esx:removeAccountMoney", self.source, accountName, money, reason)
|
||||
else
|
||||
error(("Tried To Set Add To Invalid Account ^5%s^0 For Player ^5%s^0!"):format(accountName, self.playerId))
|
||||
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^0 For Player ^5%s^0 To An Invalid Number -> ^5%s^7"):format(accountName, self.playerId, money))
|
||||
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
|
||||
|
||||
@@ -369,7 +397,7 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
|
||||
---@param itemName string
|
||||
---@param count number
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.addInventoryItem(itemName, count)
|
||||
local item = self.getInventoryItem(itemName)
|
||||
|
||||
@@ -378,14 +406,14 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
item.count = item.count + count
|
||||
self.weight = self.weight + (item.weight * count)
|
||||
|
||||
_TriggerEvent("esx:onAddInventoryItem", self.source, item.name, item.count)
|
||||
TriggerEvent("esx:onAddInventoryItem", self.source, item.name, item.count)
|
||||
self.triggerEvent("esx:addInventoryItem", item.name, item.count)
|
||||
end
|
||||
end
|
||||
|
||||
---@param itemName string
|
||||
---@param count number
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.removeInventoryItem(itemName, count)
|
||||
local item = self.getInventoryItem(itemName)
|
||||
|
||||
@@ -398,7 +426,7 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
item.count = newCount
|
||||
self.weight = self.weight - (item.weight * count)
|
||||
|
||||
_TriggerEvent("esx:onRemoveInventoryItem", self.source, item.name, item.count)
|
||||
TriggerEvent("esx:onRemoveInventoryItem", self.source, item.name, item.count)
|
||||
self.triggerEvent("esx:removeInventoryItem", item.name, item.count)
|
||||
end
|
||||
else
|
||||
@@ -409,7 +437,7 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
|
||||
---@param itemName string
|
||||
---@param count number
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.setInventoryItem(itemName, count)
|
||||
local item = self.getInventoryItem(itemName)
|
||||
|
||||
@@ -445,6 +473,7 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
return newWeight <= self.maxWeight
|
||||
else
|
||||
print(('[^3WARNING^7] Item ^5"%s"^7 was used but does not exist!'):format(itemName))
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
@@ -455,7 +484,13 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
---@return boolean
|
||||
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))
|
||||
@@ -468,7 +503,7 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
end
|
||||
|
||||
---@param newWeight number
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.setMaxWeight(newWeight)
|
||||
self.maxWeight = newWeight
|
||||
self.triggerEvent("esx:setMaxWeight", self.maxWeight)
|
||||
@@ -476,13 +511,13 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
|
||||
---@param newJob string
|
||||
---@param grade string
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.setJob(newJob, grade)
|
||||
grade = tostring(grade)
|
||||
local lastJob = self.job
|
||||
|
||||
if not ESX.DoesJobExist(newJob, grade) then
|
||||
return print(("[es_extended] [^3WARNING^7] Ignoring invalid ^5.setJob()^7 usage for ID: ^5%s^7, Job: ^5%s^7"):format(self.source, newJob))
|
||||
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
|
||||
|
||||
local jobObject, gradeObject = ESX.Jobs[newJob], ESX.Jobs[newJob].grades[grade]
|
||||
@@ -504,7 +539,7 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
|
||||
self.metadata["jobduty"] = self.job.duty
|
||||
|
||||
_TriggerEvent("esx:setJob", self.source, self.job, lastJob)
|
||||
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
|
||||
@@ -531,7 +566,7 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
|
||||
---@param weaponName string
|
||||
---@param ammo number
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.addWeapon(weaponName, ammo)
|
||||
if not self.hasWeapon(weaponName) then
|
||||
local weaponLabel <const> = ESX.GetWeaponLabel(weaponName)
|
||||
@@ -544,14 +579,14 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
tintIndex = 0,
|
||||
})
|
||||
|
||||
_GiveWeaponToPed(_GetPlayerPed(self.source), joaat(weaponName), ammo, false, false)
|
||||
GiveWeaponToPed(GetPlayerPed(self.source), joaat(weaponName), ammo, false, false)
|
||||
self.triggerEvent("esx:addInventoryItem", weaponLabel, false, true)
|
||||
end
|
||||
end
|
||||
|
||||
---@param weaponName string
|
||||
---@param weaponComponent string
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.addWeaponComponent(weaponName, weaponComponent)
|
||||
local loadoutNum <const>, weapon <const> = self.getWeapon(weaponName)
|
||||
|
||||
@@ -571,19 +606,19 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
|
||||
---@param weaponName string
|
||||
---@param ammoCount number
|
||||
---@return void
|
||||
---@return nil
|
||||
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)
|
||||
SetPedAmmo(GetPlayerPed(self.source), joaat(weaponName), weapon.ammo)
|
||||
end
|
||||
end
|
||||
|
||||
---@param weaponName string
|
||||
---@param ammoCount number
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.updateWeaponAmmo(weaponName, ammoCount)
|
||||
local _, weapon = self.getWeapon(weaponName)
|
||||
|
||||
@@ -594,7 +629,7 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
|
||||
---@param weaponName string
|
||||
---@param weaponTintIndex number
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.setWeaponTint(weaponName, weaponTintIndex)
|
||||
local loadoutNum <const>, weapon <const> = self.getWeapon(weaponName)
|
||||
|
||||
@@ -622,12 +657,12 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
end
|
||||
|
||||
---@param weaponName string
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.removeWeapon(weaponName)
|
||||
local weaponLabel, playerPed <const> = nil, _GetPlayerPed(self.source)
|
||||
local weaponLabel, playerPed <const> = nil, GetPlayerPed(self.source)
|
||||
|
||||
if not playerPed then
|
||||
return error("xPlayer.removeWeapon ^5invalid^7 player ped!")
|
||||
return error("xPlayer.removeWeapon ^5invalid^1 player ped!")
|
||||
end
|
||||
|
||||
for k, v in ipairs(self.loadout) do
|
||||
@@ -639,8 +674,8 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
end
|
||||
|
||||
local weaponHash = joaat(v.name)
|
||||
_RemoveWeaponFromPed(playerPed, weaponHash)
|
||||
_SetPedAmmo(playerPed, weaponHash, 0)
|
||||
RemoveWeaponFromPed(playerPed, weaponHash)
|
||||
SetPedAmmo(playerPed, weaponHash, 0)
|
||||
|
||||
table.remove(self.loadout, k)
|
||||
break
|
||||
@@ -654,12 +689,11 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
|
||||
---@param weaponName string
|
||||
---@param weaponComponent string
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.removeWeaponComponent(weaponName, weaponComponent)
|
||||
local loadoutNum <const>, weapon <const> = self.getWeapon(weaponName)
|
||||
|
||||
if weapon then
|
||||
---@type table
|
||||
local component <const> = ESX.GetWeaponComponent(weaponName, weaponComponent)
|
||||
|
||||
if component then
|
||||
@@ -680,13 +714,13 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
|
||||
---@param weaponName string
|
||||
---@param ammoCount number
|
||||
---@return void
|
||||
---@return nil
|
||||
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)
|
||||
SetPedAmmo(GetPlayerPed(self.source), joaat(weaponName), weapon.ammo)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -722,7 +756,7 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
end
|
||||
|
||||
---@param item string
|
||||
---@return table, number | false
|
||||
---@return table | false, number | nil
|
||||
function self.hasItem(item)
|
||||
for _, v in ipairs(self.inventory) do
|
||||
if v.name == item and v.count >= 1 then
|
||||
@@ -734,7 +768,7 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
end
|
||||
|
||||
---@param weaponName string
|
||||
---@return number, table | nil
|
||||
---@return number | nil, table | nil
|
||||
function self.getWeapon(weaponName)
|
||||
for k, v in ipairs(self.loadout) do
|
||||
if v.name == weaponName then
|
||||
@@ -742,13 +776,13 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
---@param msg string
|
||||
---@param type string
|
||||
---@param notifyType string
|
||||
---@param length number
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.showNotification(msg, notifyType, length)
|
||||
self.triggerEvent("esx:showNotification", msg, notifyType, length)
|
||||
end
|
||||
@@ -761,7 +795,7 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
---@param flash boolean
|
||||
---@param saveToBrief boolean
|
||||
---@param hudColorIndex number
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.showAdvancedNotification(sender, subject, msg, textureDict, iconType, flash, saveToBrief, hudColorIndex)
|
||||
self.triggerEvent("esx:showAdvancedNotification", sender, subject, msg, textureDict, iconType, flash, saveToBrief, hudColorIndex)
|
||||
end
|
||||
@@ -770,26 +804,27 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
---@param thisFrame boolean
|
||||
---@param beep boolean
|
||||
---@param duration number
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.showHelpNotification(msg, thisFrame, beep, duration)
|
||||
self.triggerEvent("esx:showHelpNotification", msg, thisFrame, beep, duration)
|
||||
end
|
||||
|
||||
---@param index any
|
||||
---@param subIndex any
|
||||
---@return table
|
||||
---@return table | nil
|
||||
function self.getMeta(index, subIndex)
|
||||
if not index then
|
||||
return self.metadata
|
||||
end
|
||||
|
||||
if type(index) ~= "string" then
|
||||
return error("xPlayer.getMeta ^5index^7 should be ^5string^7!")
|
||||
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^7 not exist!"):format(index)) or nil
|
||||
return Config.EnableDebug and error(("xPlayer.getMeta ^5%s^1 not exist!"):format(index)) or nil
|
||||
end
|
||||
|
||||
if subIndex and type(metaData) == "table" then
|
||||
@@ -808,14 +843,15 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
if type(key) == "string" then
|
||||
returnValues[key] = self.getMeta(index, key)
|
||||
else
|
||||
error(("xPlayer.getMeta subIndex should be ^5string^7 or ^5table^7! that contains ^5string^7, received ^5%s^7!, skipping..."):format(type(key)))
|
||||
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
|
||||
|
||||
return error(("xPlayer.getMeta subIndex should be ^5string^7 or ^5table^7!, received ^5%s^7!"):format(_type))
|
||||
error(("xPlayer.getMeta subIndex should be ^5string^1 or ^5table^1!, received ^5%s^1!"):format(_type))
|
||||
return
|
||||
end
|
||||
|
||||
return metaData
|
||||
@@ -824,14 +860,14 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
---@param index any
|
||||
---@param value any
|
||||
---@param subValue any
|
||||
---@return void
|
||||
---@return nil
|
||||
function self.setMeta(index, value, subValue)
|
||||
if not index then
|
||||
return error("xPlayer.setMeta ^5index^7 is Missing!")
|
||||
return error("xPlayer.setMeta ^5index^1 is Missing!")
|
||||
end
|
||||
|
||||
if type(index) ~= "string" then
|
||||
return error("xPlayer.setMeta ^5index^7 should be ^5string^7!")
|
||||
return error("xPlayer.setMeta ^5index^1 should be ^5string^1!")
|
||||
end
|
||||
|
||||
if value == nil then
|
||||
@@ -842,13 +878,13 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
|
||||
if not subValue then
|
||||
if _type ~= "number" and _type ~= "string" and _type ~= "table" then
|
||||
return error(("xPlayer.setMeta ^5%s^7 should be ^5number^7 or ^5string^7 or ^5table^7!"):format(value))
|
||||
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^7 should be ^5string^7 as a subIndex!"):format(value))
|
||||
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
|
||||
@@ -863,16 +899,16 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
|
||||
function self.clearMeta(index, subValues)
|
||||
if not index then
|
||||
return error("xPlayer.clearMeta ^5index^7 is Missing!")
|
||||
return error("xPlayer.clearMeta ^5index^1 is Missing!")
|
||||
end
|
||||
|
||||
if type(index) ~= "string" then
|
||||
return error("xPlayer.clearMeta ^5index^7 should be ^5string^7!")
|
||||
return error("xPlayer.clearMeta ^5index^1 should be ^5string^1!")
|
||||
end
|
||||
|
||||
local metaData = self.metadata[index]
|
||||
if metaData == nil then
|
||||
return Config.EnableDebug and error(("xPlayer.clearMeta ^5%s^7 does not exist!"):format(index)) or nil
|
||||
return Config.EnableDebug and error(("xPlayer.clearMeta ^5%s^1 does not exist!"):format(index)) or nil
|
||||
end
|
||||
|
||||
if not subValues then
|
||||
@@ -883,7 +919,7 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
if type(metaData) == "table" then
|
||||
metaData[subValues] = nil
|
||||
else
|
||||
return error(("xPlayer.clearMeta ^5%s^7 is not a table! Cannot clear subValue ^5%s^7."):format(index, subValues))
|
||||
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
|
||||
@@ -893,14 +929,14 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory,
|
||||
if type(metaData) == "table" then
|
||||
metaData[subValue] = nil
|
||||
else
|
||||
error(("xPlayer.clearMeta ^5%s^7 is not a table! Cannot clear subValue ^5%s^7."):format(index, subValue))
|
||||
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^7, received ^5%s^7, skipping..."):format(type(subValue)))
|
||||
error(("xPlayer.clearMeta subValues should contain ^5string^1, received ^5%s^1, skipping..."):format(type(subValue)))
|
||||
end
|
||||
end
|
||||
else
|
||||
return error(("xPlayer.clearMeta ^5subValues^7 should be ^5string^7 or ^5table^7, received ^5%s^7!"):format(type(subValues)))
|
||||
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
|
||||
|
||||
@@ -77,7 +77,7 @@ ESX.RegisterCommand(
|
||||
local playerPed = GetPlayerPed(xPlayer.source)
|
||||
local playerCoords = GetEntityCoords(playerPed)
|
||||
local playerHeading = GetEntityHeading(playerPed)
|
||||
local playerVehicle = GetVehiclePedIsIn(playerPed)
|
||||
local playerVehicle = GetVehiclePedIsIn(playerPed, false)
|
||||
|
||||
if not args.car or type(args.car) ~= "string" then
|
||||
args.car = "adder"
|
||||
@@ -126,11 +126,15 @@ ESX.RegisterCommand(
|
||||
{ "cardel", "dv" },
|
||||
"admin",
|
||||
function(xPlayer, args)
|
||||
local PedVehicle = GetVehiclePedIsIn(GetPlayerPed(xPlayer.source), false)
|
||||
if DoesEntityExist(PedVehicle) then
|
||||
DeleteEntity(PedVehicle)
|
||||
local ped = GetPlayerPed(xPlayer.source)
|
||||
local pedVehicle = GetVehiclePedIsIn(ped, false)
|
||||
|
||||
if DoesEntityExist(pedVehicle) then
|
||||
DeleteEntity(pedVehicle)
|
||||
end
|
||||
local Vehicles = ESX.OneSync.GetVehiclesInArea(GetEntityCoords(GetPlayerPed(xPlayer.source)), tonumber(args.radius) or 5.0)
|
||||
|
||||
local coords = GetEntityCoords(ped)
|
||||
local Vehicles = ESX.OneSync.GetVehiclesInArea(coords, tonumber(args.radius) or 5.0)
|
||||
for i = 1, #Vehicles do
|
||||
local Vehicle = NetworkGetEntityFromNetworkId(Vehicles[i])
|
||||
if DoesEntityExist(Vehicle) then
|
||||
@@ -549,6 +553,14 @@ ESX.RegisterCommand("info", { "user", "admin" }, function(xPlayer)
|
||||
print(("^2ID: ^5%s^0 | ^2Name: ^5%s^0 | ^2Group: ^5%s^0 | ^2Job: ^5%s^0"):format(xPlayer.source, xPlayer.getName(), xPlayer.getGroup(), job))
|
||||
end, false)
|
||||
|
||||
ESX.RegisterCommand("playtime", { "user", "admin" }, function(xPlayer)
|
||||
local playtime = xPlayer.getPlayTime()
|
||||
local days = math.floor(playtime / 86400)
|
||||
local hours = math.floor((playtime % 86400) / 3600)
|
||||
local minutes = math.floor((playtime % 3600) / 60)
|
||||
print(("Playtime: ^5%s^0 Days | ^5%s^0 Hours | ^5%s^0 Minutes"):format(days, hours, minutes))
|
||||
end, false)
|
||||
|
||||
ESX.RegisterCommand("coords", "admin", function(xPlayer)
|
||||
local ped = GetPlayerPed(xPlayer.source)
|
||||
local coords = GetEntityCoords(ped, false)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
ESX = {}
|
||||
ESX.Players = {}
|
||||
ESX.Jobs = {}
|
||||
ESX.JobsPlayerCount = {}
|
||||
@@ -18,19 +17,10 @@ RegisterNetEvent("esx:onPlayerSpawn", function()
|
||||
ESX.Players[source].spawned = true
|
||||
end)
|
||||
|
||||
AddEventHandler("esx:getSharedObject", function()
|
||||
local Invoke = GetInvokingResource()
|
||||
error(("Resource ^5%s^7 Used the ^5getSharedObject^7 Event, this event ^1no longer exists!^7 Visit https://documentation.esx-framework.org/tutorials/tutorials-esx/sharedevent for how to fix!"):format(Invoke))
|
||||
end)
|
||||
|
||||
exports("getSharedObject", function()
|
||||
return ESX
|
||||
end)
|
||||
|
||||
if Config.OxInventory then
|
||||
Config.PlayerFunctionOverride = "OxInventory"
|
||||
SetConvarReplicated("inventory:framework", "esx")
|
||||
SetConvarReplicated("inventory:weight", Config.MaxWeight * 1000)
|
||||
SetConvarReplicated("inventory:weight", tostring(Config.MaxWeight * 1000))
|
||||
end
|
||||
|
||||
local function StartDBSync()
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
---@param msg string
|
||||
---@return nil
|
||||
function ESX.Trace(msg)
|
||||
if Config.EnableDebug then
|
||||
print(("[^2TRACE^7] %s^7"):format(msg))
|
||||
@@ -22,12 +24,16 @@ function ESX.TriggerClientEvent(eventName, playerIds, ...)
|
||||
end
|
||||
end
|
||||
|
||||
---@param name string | table
|
||||
---@param group string | table
|
||||
---@param cb function
|
||||
---@param allowConsole? boolean
|
||||
---@param suggestion? table
|
||||
function ESX.RegisterCommand(name, group, cb, allowConsole, suggestion)
|
||||
if type(name) == "table" then
|
||||
for _, v in ipairs(name) do
|
||||
ESX.RegisterCommand(v, group, cb, allowConsole, suggestion)
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
@@ -186,8 +192,12 @@ local function updateHealthAndArmorInMetadata(xPlayer)
|
||||
local ped = GetPlayerPed(xPlayer.source)
|
||||
xPlayer.setMeta("health", GetEntityHealth(ped))
|
||||
xPlayer.setMeta("armor", GetPedArmour(ped))
|
||||
xPlayer.setMeta("lastPlaytime", xPlayer.getPlayTime())
|
||||
end
|
||||
|
||||
---@param xPlayer table
|
||||
---@param cb? function
|
||||
---@return nil
|
||||
function Core.SavePlayer(xPlayer, cb)
|
||||
if not xPlayer.spawned then
|
||||
return cb and cb()
|
||||
@@ -221,6 +231,8 @@ function Core.SavePlayer(xPlayer, cb)
|
||||
)
|
||||
end
|
||||
|
||||
---@param cb? function
|
||||
---@return nil
|
||||
function Core.SavePlayers(cb)
|
||||
local xPlayers <const> = ESX.Players
|
||||
if not next(xPlayers) then
|
||||
@@ -277,6 +289,9 @@ local function checkTable(key, val, player, xPlayers)
|
||||
end
|
||||
end
|
||||
|
||||
---@param key? string
|
||||
---@param val? string|table
|
||||
---@return table
|
||||
function ESX.GetExtendedPlayers(key, val)
|
||||
local xPlayers = {}
|
||||
if type(val) == "table" then
|
||||
@@ -298,6 +313,9 @@ function ESX.GetExtendedPlayers(key, val)
|
||||
return xPlayers
|
||||
end
|
||||
|
||||
---@param key? string
|
||||
---@param val? string|table
|
||||
---@return number | table
|
||||
function ESX.GetNumPlayers(key, val)
|
||||
if not key then
|
||||
return #GetPlayers()
|
||||
@@ -326,28 +344,36 @@ function ESX.GetNumPlayers(key, val)
|
||||
return #ESX.GetExtendedPlayers(key, val)
|
||||
end
|
||||
|
||||
---@param source number
|
||||
---@return table
|
||||
function ESX.GetPlayerFromId(source)
|
||||
return ESX.Players[tonumber(source)]
|
||||
end
|
||||
|
||||
---@param identifier string
|
||||
---@return table
|
||||
function ESX.GetPlayerFromIdentifier(identifier)
|
||||
return Core.playersByIdentifier[identifier]
|
||||
end
|
||||
|
||||
---@param playerId number | string
|
||||
---@return string
|
||||
function ESX.GetIdentifier(playerId)
|
||||
local fxDk = GetConvarInt("sv_fxdkMode", 0)
|
||||
if fxDk == 1 then
|
||||
return "ESX-DEBUG-LICENCE"
|
||||
end
|
||||
|
||||
playerId = tostring(playerId)
|
||||
|
||||
local identifier = GetPlayerIdentifierByType(playerId, "license")
|
||||
return identifier and identifier:gsub("license:", "")
|
||||
end
|
||||
|
||||
---@param model string|number
|
||||
---@param player number playerId
|
||||
---@param player number
|
||||
---@param cb function
|
||||
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function ESX.GetVehicleType(model, player, cb)
|
||||
model = type(model) == "string" and joaat(model) or model
|
||||
|
||||
@@ -361,6 +387,11 @@ function ESX.GetVehicleType(model, player, cb)
|
||||
end, model)
|
||||
end
|
||||
|
||||
---@param name string
|
||||
---@param title string
|
||||
---@param color string
|
||||
---@param message string
|
||||
---@return nil
|
||||
function ESX.DiscordLog(name, title, color, message)
|
||||
local webHook = Config.DiscordLogs.Webhooks[name] or Config.DiscordLogs.Webhooks.default
|
||||
local embedData = {
|
||||
@@ -380,7 +411,9 @@ function ESX.DiscordLog(name, title, color, message)
|
||||
}
|
||||
PerformHttpRequest(
|
||||
webHook,
|
||||
nil,
|
||||
function ()
|
||||
return
|
||||
end,
|
||||
"POST",
|
||||
json.encode({
|
||||
username = "Logs",
|
||||
@@ -392,6 +425,11 @@ function ESX.DiscordLog(name, title, color, message)
|
||||
)
|
||||
end
|
||||
|
||||
---@param name string
|
||||
---@param title string
|
||||
---@param color string
|
||||
---@param fields table
|
||||
---@return nil
|
||||
function ESX.DiscordLogFields(name, title, color, fields)
|
||||
local webHook = Config.DiscordLogs.Webhooks[name] or Config.DiscordLogs.Webhooks.default
|
||||
local embedData = {
|
||||
@@ -412,7 +450,9 @@ function ESX.DiscordLogFields(name, title, color, fields)
|
||||
}
|
||||
PerformHttpRequest(
|
||||
webHook,
|
||||
nil,
|
||||
function ()
|
||||
return
|
||||
end,
|
||||
"POST",
|
||||
json.encode({
|
||||
username = "Logs",
|
||||
@@ -424,6 +464,7 @@ function ESX.DiscordLogFields(name, title, color, fields)
|
||||
)
|
||||
end
|
||||
|
||||
---@return nil
|
||||
function ESX.RefreshJobs()
|
||||
local Jobs = {}
|
||||
local jobs = MySQL.query.await("SELECT * FROM jobs")
|
||||
@@ -458,10 +499,17 @@ function ESX.RefreshJobs()
|
||||
end
|
||||
end
|
||||
|
||||
---@param item string
|
||||
---@param cb function
|
||||
---@return nil
|
||||
function ESX.RegisterUsableItem(item, cb)
|
||||
Core.UsableItemsCallbacks[item] = cb
|
||||
end
|
||||
|
||||
---@param source number
|
||||
---@param item string
|
||||
---@param ... any
|
||||
---@return nil
|
||||
function ESX.UseItem(source, item, ...)
|
||||
if ESX.Items[item] then
|
||||
local itemCallback = Core.UsableItemsCallbacks[item]
|
||||
@@ -478,10 +526,15 @@ function ESX.UseItem(source, item, ...)
|
||||
end
|
||||
end
|
||||
|
||||
---@param index string
|
||||
---@param overrides table
|
||||
---@return nil
|
||||
function ESX.RegisterPlayerFunctionOverrides(index, overrides)
|
||||
Core.PlayerFunctionOverrides[index] = overrides
|
||||
end
|
||||
|
||||
---@param index string
|
||||
---@return nil
|
||||
function ESX.SetPlayerFunctionOverride(index)
|
||||
if not index or not Core.PlayerFunctionOverrides[index] then
|
||||
return print("[^3WARNING^7] No valid index provided.")
|
||||
@@ -490,6 +543,8 @@ function ESX.SetPlayerFunctionOverride(index)
|
||||
Config.PlayerFunctionOverride = index
|
||||
end
|
||||
|
||||
---@param item string
|
||||
---@return string?
|
||||
function ESX.GetItemLabel(item)
|
||||
if Config.OxInventory then
|
||||
item = exports.ox_inventory:Items(item)
|
||||
@@ -505,10 +560,12 @@ function ESX.GetItemLabel(item)
|
||||
end
|
||||
end
|
||||
|
||||
---@return table
|
||||
function ESX.GetJobs()
|
||||
return ESX.Jobs
|
||||
end
|
||||
|
||||
---@return table
|
||||
function ESX.GetUsableItems()
|
||||
local Usables = {}
|
||||
for k in pairs(Core.UsableItemsCallbacks) do
|
||||
@@ -518,6 +575,15 @@ function ESX.GetUsableItems()
|
||||
end
|
||||
|
||||
if not Config.OxInventory then
|
||||
---@param itemType string
|
||||
---@param name string
|
||||
---@param count integer
|
||||
---@param label string
|
||||
---@param playerId number
|
||||
---@param components? string | table
|
||||
---@param tintIndex? integer
|
||||
---@param coords? table | vector3
|
||||
---@return nil
|
||||
function ESX.CreatePickup(itemType, name, count, label, playerId, components, tintIndex, coords)
|
||||
local pickupId = (Core.PickupId == 65635 and 0 or Core.PickupId + 1)
|
||||
local xPlayer = ESX.Players[playerId]
|
||||
@@ -535,11 +601,17 @@ if not Config.OxInventory then
|
||||
end
|
||||
end
|
||||
|
||||
---@param job string
|
||||
---@param grade string
|
||||
---@return boolean
|
||||
function ESX.DoesJobExist(job, grade)
|
||||
return (ESX.Jobs[job] and ESX.Jobs[job].grades[tostring(grade)] ~= nil) or false
|
||||
end
|
||||
|
||||
---@param playerId string | number
|
||||
---@return boolean
|
||||
function Core.IsPlayerAdmin(playerId)
|
||||
playerId = tostring(playerId)
|
||||
if (IsPlayerAceAllowed(playerId, "command") or GetConvar("sv_lan", "") == "true") and true or false then
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -19,6 +19,54 @@ end
|
||||
|
||||
loadPlayer = loadPlayer .. " FROM `users` WHERE identifier = ?"
|
||||
|
||||
local function createESXPlayer(identifier, playerId, data)
|
||||
local accounts = {}
|
||||
|
||||
for account, money in pairs(Config.StartingAccountMoney) do
|
||||
accounts[account] = money
|
||||
end
|
||||
|
||||
local defaultGroup = "user"
|
||||
if Core.IsPlayerAdmin(playerId) then
|
||||
print(("[^2INFO^0] Player ^5%s^0 Has been granted admin permissions via ^5Ace Perms^7."):format(playerId))
|
||||
defaultGroup = "admin"
|
||||
end
|
||||
|
||||
local parameters = Config.Multichar and { json.encode(accounts), identifier, defaultGroup, data.firstname, data.lastname, data.dateofbirth, data.sex, data.height } or { json.encode(accounts), identifier, defaultGroup }
|
||||
|
||||
if Config.StartingInventoryItems then
|
||||
table.insert(parameters, json.encode(Config.StartingInventoryItems))
|
||||
end
|
||||
|
||||
MySQL.prepare(newPlayer, parameters, function()
|
||||
loadESXPlayer(identifier, playerId, true)
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
local function onPlayerJoined(playerId)
|
||||
local identifier = ESX.GetIdentifier(playerId)
|
||||
if identifier then
|
||||
if ESX.GetPlayerFromIdentifier(identifier) then
|
||||
DropPlayer(
|
||||
playerId,
|
||||
("there was an error loading your character!\nError code: identifier-active-ingame\n\nThis error is caused by a player on this server who has the same identifier as you have. Make sure you are not playing on the same Rockstar account.\n\nYour Rockstar identifier: %s"):format(
|
||||
identifier
|
||||
)
|
||||
)
|
||||
else
|
||||
local result = MySQL.scalar.await("SELECT 1 FROM users WHERE identifier = ?", { identifier })
|
||||
if result then
|
||||
loadESXPlayer(identifier, playerId, false)
|
||||
else
|
||||
createESXPlayer(identifier, playerId)
|
||||
end
|
||||
end
|
||||
else
|
||||
DropPlayer(playerId, "there was an error loading your character!\nError code: identifier-missing-ingame\n\nThe cause of this error is not known, your identifier could not be found. Please come back later or report this problem to the server administration team.")
|
||||
end
|
||||
end
|
||||
|
||||
if Config.Multichar then
|
||||
AddEventHandler("esx:onPlayerJoined", function(src, char, data)
|
||||
while not next(ESX.Jobs) do
|
||||
@@ -48,59 +96,17 @@ else
|
||||
end)
|
||||
end
|
||||
|
||||
function onPlayerJoined(playerId)
|
||||
local identifier = ESX.GetIdentifier(playerId)
|
||||
if identifier then
|
||||
if ESX.GetPlayerFromIdentifier(identifier) then
|
||||
DropPlayer(
|
||||
playerId,
|
||||
("there was an error loading your character!\nError code: identifier-active-ingame\n\nThis error is caused by a player on this server who has the same identifier as you have. Make sure you are not playing on the same Rockstar account.\n\nYour Rockstar identifier: %s"):format(
|
||||
identifier
|
||||
)
|
||||
)
|
||||
else
|
||||
local result = MySQL.scalar.await("SELECT 1 FROM users WHERE identifier = ?", { identifier })
|
||||
if result then
|
||||
loadESXPlayer(identifier, playerId, false)
|
||||
else
|
||||
createESXPlayer(identifier, playerId)
|
||||
end
|
||||
end
|
||||
else
|
||||
DropPlayer(playerId, "there was an error loading your character!\nError code: identifier-missing-ingame\n\nThe cause of this error is not known, your identifier could not be found. Please come back later or report this problem to the server administration team.")
|
||||
end
|
||||
end
|
||||
|
||||
function createESXPlayer(identifier, playerId, data)
|
||||
local accounts = {}
|
||||
|
||||
for account, money in pairs(Config.StartingAccountMoney) do
|
||||
accounts[account] = money
|
||||
end
|
||||
|
||||
local defaultGroup = "user"
|
||||
if Core.IsPlayerAdmin(playerId) then
|
||||
print(("[^2INFO^0] Player ^5%s^0 Has been granted admin permissions via ^5Ace Perms^7."):format(playerId))
|
||||
defaultGroup = "admin"
|
||||
end
|
||||
|
||||
local parameters = Config.Multichar and { json.encode(accounts), identifier, defaultGroup, data.firstname, data.lastname, data.dateofbirth, data.sex, data.height } or { json.encode(accounts), identifier, defaultGroup }
|
||||
|
||||
if Config.StartingInventoryItems then
|
||||
table.insert(parameters, json.encode(Config.StartingInventoryItems))
|
||||
end
|
||||
|
||||
MySQL.prepare(newPlayer, parameters, function()
|
||||
loadESXPlayer(identifier, playerId, true)
|
||||
end)
|
||||
end
|
||||
|
||||
if not Config.Multichar then
|
||||
AddEventHandler("playerConnecting", function(_, _, deferrals)
|
||||
deferrals.defer()
|
||||
Wait(0) -- Required
|
||||
local playerId = source
|
||||
local identifier = ESX.GetIdentifier(playerId)
|
||||
|
||||
if not GetPlayerTimeOnline then
|
||||
return deferrals.done(("[ESX] ESX Requires a minimum Artifact version of 9515, Please update your server."):format(oneSyncState))
|
||||
end
|
||||
|
||||
if oneSyncState == "off" or oneSyncState == "legacy" then
|
||||
return deferrals.done(("[ESX] ESX Requires Onesync Infinity to work. This server currently has Onesync set to: %s"):format(oneSyncState))
|
||||
end
|
||||
@@ -608,7 +614,7 @@ ESX.RegisterServerCallback("esx:isUserAdmin", function(source, cb)
|
||||
end)
|
||||
|
||||
ESX.RegisterServerCallback("esx:getGameBuild", function(_, cb)
|
||||
cb(tonumber(GetConvar("sv_enforceGameBuild", 1604)))
|
||||
cb(tonumber(GetConvar("sv_enforceGameBuild", "1604")))
|
||||
end)
|
||||
|
||||
ESX.RegisterServerCallback("esx:getOtherPlayerData", function(_, cb, target)
|
||||
@@ -648,7 +654,7 @@ ESX.RegisterServerCallback("esx:spawnVehicle", function(source, cb, vehData)
|
||||
if vehData.warp then
|
||||
local vehicle = NetworkGetEntityFromNetworkId(id)
|
||||
local timeout = 0
|
||||
while GetVehiclePedIsIn(ped) ~= vehicle and timeout <= 15 do
|
||||
while GetVehiclePedIsIn(ped, false) ~= vehicle and timeout <= 15 do
|
||||
Wait(0)
|
||||
TaskWarpPedIntoVehicle(ped, vehicle, -1)
|
||||
timeout += 1
|
||||
@@ -689,13 +695,13 @@ AddEventHandler("onResourceStart", function(key)
|
||||
end
|
||||
|
||||
StopResource(key)
|
||||
error(("WE STOPPED A RESOURCE THAT WILL BREAK ^1ESX^7, PLEASE REMOVE ^5%s^7"):format(key))
|
||||
error(("WE STOPPED A RESOURCE THAT WILL BREAK ^1ESX^1, PLEASE REMOVE ^5%s^1"):format(key))
|
||||
end
|
||||
end)
|
||||
|
||||
for key in pairs(DoNotUse) do
|
||||
if GetResourceState(key) == "started" or GetResourceState(key) == "starting" then
|
||||
StopResource(key)
|
||||
error(("WE STOPPED A RESOURCE THAT WILL BREAK ^1ESX^7, PLEASE REMOVE ^5%s^7"):format(key))
|
||||
error(("WE STOPPED A RESOURCE THAT WILL BREAK ^1ESX^1, PLEASE REMOVE ^5%s^1"):format(key))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,10 +10,6 @@ if Config.EnableDebug then
|
||||
print("esx:playerPedChanged", source, netId)
|
||||
end)
|
||||
|
||||
AddEventHandler("esx:playerJumping", function()
|
||||
print("esx:playerJumping", source)
|
||||
end)
|
||||
|
||||
AddEventHandler("esx:enteringVehicle", function(plate, seat, netId)
|
||||
print("esx:enteringVehicle", "source", source, "plate", plate, "seat", seat, "netId", netId)
|
||||
end)
|
||||
|
||||
@@ -1,43 +1,86 @@
|
||||
local serverCallbacks = {}
|
||||
---@diagnostic disable: duplicate-set-field
|
||||
|
||||
local clientRequests = {}
|
||||
local RequestId = 0
|
||||
Callbacks = {}
|
||||
|
||||
---@param eventName string
|
||||
---@param callback function
|
||||
ESX.RegisterServerCallback = function(eventName, callback)
|
||||
serverCallbacks[eventName] = callback
|
||||
Callbacks.requests = {}
|
||||
Callbacks.storage = {}
|
||||
Callbacks.id = 0
|
||||
|
||||
function Callbacks:Register(name, cb)
|
||||
self.storage[name] = cb
|
||||
end
|
||||
|
||||
RegisterNetEvent("esx:triggerServerCallback", function(eventName, requestId, invoker, ...)
|
||||
if not serverCallbacks[eventName] then
|
||||
return error(("Server Callback not registered, name: ^5%s^7, invoker resource: ^5%s^7"):format(eventName, invoker))
|
||||
function Callbacks:Execute(cb, ...)
|
||||
local success, errorString = pcall(cb, ...)
|
||||
|
||||
if not success then
|
||||
print(("[^1ERROR^7] Failed to execute Callback with RequestId: ^5%s^7"):format(self.currentId))
|
||||
error(errorString)
|
||||
return
|
||||
end
|
||||
self.currentId = nil
|
||||
end
|
||||
|
||||
function Callbacks:Trigger(player, event, cb, invoker, ...)
|
||||
self.requests[self.id] = cb
|
||||
|
||||
TriggerClientEvent("esx:triggerClientCallback", player, event, self.id, invoker, ...)
|
||||
|
||||
self.id += 1
|
||||
end
|
||||
|
||||
function Callbacks:ServerRecieve(player, event, requestId, invoker, ...)
|
||||
self.currentId = requestId
|
||||
|
||||
if not self.storage[event] then
|
||||
return error(("Server Callback with requestId ^5%s^1 Was Called by ^5%s^1 but does not exist."):format(event, invoker))
|
||||
end
|
||||
|
||||
local source = source
|
||||
local returnCb = function(...)
|
||||
TriggerClientEvent("esx:serverCallback", player, requestId, invoker, ...)
|
||||
end
|
||||
local callback = self.storage[event]
|
||||
|
||||
serverCallbacks[eventName](source, function(...)
|
||||
TriggerClientEvent("esx:serverCallback", source, requestId, invoker, ...)
|
||||
end, ...)
|
||||
self:Execute(callback, player, returnCb, ...)
|
||||
end
|
||||
|
||||
function Callbacks:RecieveClient(requestId, invoker, ...)
|
||||
self.currentId = requestId
|
||||
|
||||
if not self.requests[self.currentId] then
|
||||
return error(("Client Callback with requestId ^5%s^1 Was Called by ^5%s^1 but does not exist."):format(self.currentId, invoker))
|
||||
end
|
||||
|
||||
local callback = self.requests[self.currentId]
|
||||
|
||||
self:Execute(callback, ...)
|
||||
self.requests[requestId] = nil
|
||||
end
|
||||
|
||||
RegisterNetEvent("esx:clientCallback", function(requestId, invoker, ...)
|
||||
Callbacks:RecieveClient(requestId, invoker, ...)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx:triggerServerCallback", function(eventName, requestId, invoker, ...)
|
||||
local source = source
|
||||
Callbacks:ServerRecieve(source, eventName, requestId, invoker, ...)
|
||||
end)
|
||||
|
||||
|
||||
---@param player number playerId
|
||||
---@param eventName string
|
||||
---@param callback function
|
||||
---@param ... any
|
||||
ESX.TriggerClientCallback = function(player, eventName, callback, ...)
|
||||
clientRequests[RequestId] = callback
|
||||
function ESX.TriggerClientCallback(player, eventName, callback, ...)
|
||||
local invokingResource = GetInvokingResource()
|
||||
local invoker = (invokingResource and invokingResource ~= "unknown") and invokingResource or "es_extended"
|
||||
|
||||
TriggerClientEvent("esx:triggerClientCallback", player, eventName, RequestId, GetInvokingResource() or "unknown", ...)
|
||||
|
||||
RequestId = RequestId + 1
|
||||
Callbacks:Trigger(player, eventName, callback, invoker, ...)
|
||||
end
|
||||
|
||||
RegisterNetEvent("esx:clientCallback", function(requestId, invoker, ...)
|
||||
if not clientRequests[requestId] then
|
||||
return error(("Client Callback with requestId ^5%s^7 Was Called by ^5%s^7 but does not exist."):format(requestId, invoker))
|
||||
end
|
||||
---@param eventName string
|
||||
---@param callback function
|
||||
ESX.RegisterServerCallback = function(eventName, callback)
|
||||
Callbacks:Register(eventName, callback)
|
||||
end
|
||||
|
||||
clientRequests[requestId](...)
|
||||
clientRequests[requestId] = nil
|
||||
end)
|
||||
|
||||
@@ -22,7 +22,7 @@ end
|
||||
local function generateNewJobTable(name, label, grades)
|
||||
local job = { name = name, label = label, grades = {} }
|
||||
for _, v in pairs(grades) do
|
||||
job.grades[tostring(v.grade)] = { job_name = name, grade = v.grade, name = v.name, label = v.label, salary = v.salary, skin_male = {}, skin_female = {} }
|
||||
job.grades[tostring(v.grade)] = { job_name = name, grade = v.grade, name = v.name, label = v.label, salary = v.salary, skin_male = v.skin_male or '{}', skin_female = v.skin_female or '{}' }
|
||||
end
|
||||
|
||||
return job
|
||||
|
||||
@@ -7,6 +7,9 @@ AddEventHandler("onServerResourceStart", function(resource)
|
||||
|
||||
npwd = GetResourceState("npwd"):find("start") and exports.npwd or nil
|
||||
|
||||
if not npwd then
|
||||
return
|
||||
end
|
||||
for _, xPlayer in pairs(ESX.Players) do
|
||||
npwd:newPlayer({
|
||||
source = xPlayer.source,
|
||||
|
||||
@@ -91,16 +91,21 @@ function ESX.OneSync.SpawnVehicle(model, coords, heading, properties, cb)
|
||||
local xPlayer = ESX.OneSync.GetClosestPlayer(coords, 300)
|
||||
ESX.GetVehicleType(vehicleModel, xPlayer.id, function(vehicleType)
|
||||
if vehicleType then
|
||||
local createdVehicle = CreateVehicleServerSetter(vehicleModel, vehicleType, coords, heading)
|
||||
if not DoesEntityExist(createdVehicle) then
|
||||
return error("Unfortunately, this vehicle has not spawned")
|
||||
local createdVehicle = CreateVehicleServerSetter(vehicleModel, vehicleType, coords.x, coords.y, coords.z, heading)
|
||||
local tries = 0
|
||||
while not DoesEntityExist(createdVehicle) do
|
||||
Wait(200)
|
||||
tries = tries + 1
|
||||
if tries > 10 then
|
||||
return print("[^1ERROR^7] Unfortunately, this vehicle has not spawned")
|
||||
end
|
||||
end
|
||||
|
||||
local networkId = NetworkGetNetworkIdFromEntity(createdVehicle)
|
||||
Entity(createdVehicle).state:set("VehicleProperties", vehicleProperties, true)
|
||||
cb(networkId)
|
||||
else
|
||||
error(("Tried to spawn invalid vehicle - ^5%s^7!"):format(model))
|
||||
print(("[^1ERROR^7] Tried to spawn invalid vehicle - ^5%s^7!"):format(model))
|
||||
end
|
||||
end)
|
||||
end)
|
||||
@@ -116,7 +121,7 @@ function ESX.OneSync.SpawnObject(model, coords, heading, cb)
|
||||
end
|
||||
local objectCoords = type(coords) == "vector3" and coords or vector3(coords.x, coords.y, coords.z)
|
||||
CreateThread(function()
|
||||
local entity = CreateObject(model, objectCoords, true, true)
|
||||
local entity = CreateObject(model, objectCoords.x, objectCoords.y, objectCoords.z, true, true, false)
|
||||
while not DoesEntityExist(entity) do
|
||||
Wait(50)
|
||||
end
|
||||
@@ -194,14 +199,14 @@ end
|
||||
|
||||
---@param coords vector3
|
||||
---@param maxDistance number
|
||||
---@param modelFilter table models to ignore, where the key is the model hash and the value is true
|
||||
---@param modelFilter table | nil models to ignore, where the key is the model hash and the value is true
|
||||
---@return table
|
||||
function ESX.OneSync.GetVehiclesInArea(coords, maxDistance, modelFilter)
|
||||
return getNearbyEntities(GetAllVehicles(), coords, modelFilter, maxDistance)
|
||||
end
|
||||
|
||||
local function getClosestEntity(entities, coords, modelFilter, isPed)
|
||||
local distance, closestEntity, closestCoords = 100, nil, nil
|
||||
local distance, closestEntity, closestCoords = 100, 0, vector3(0, 0, 0)
|
||||
coords = type(coords) == "number" and GetEntityCoords(GetPlayerPed(coords)) or vector3(coords.x, coords.y, coords.z)
|
||||
|
||||
for _, entity in pairs(entities) do
|
||||
@@ -215,6 +220,7 @@ local function getClosestEntity(entities, coords, modelFilter, isPed)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return NetworkGetNetworkIdFromEntity(closestEntity), distance, closestCoords
|
||||
end
|
||||
|
||||
|
||||
@@ -7,59 +7,61 @@ function StartPayCheck()
|
||||
local job = xPlayer.job.grade_name
|
||||
local salary = xPlayer.job.grade_salary
|
||||
|
||||
if salary > 0 then
|
||||
if job == "unemployed" then -- unemployed
|
||||
xPlayer.addAccountMoney("bank", salary, "Welfare Check")
|
||||
TriggerClientEvent("esx:showAdvancedNotification", player, TranslateCap("bank"), TranslateCap("received_paycheck"), TranslateCap("received_help", salary), "CHAR_BANK_MAZE", 9)
|
||||
if Config.LogPaycheck then
|
||||
ESX.DiscordLogFields("Paycheck", "Paycheck - Unemployment Benefits", "green", {
|
||||
{ name = "Player", value = xPlayer.name, inline = true },
|
||||
{ name = "ID", value = xPlayer.source, inline = true },
|
||||
{ name = "Amount", value = salary, inline = true },
|
||||
})
|
||||
end
|
||||
elseif Config.EnableSocietyPayouts then -- possibly a society
|
||||
TriggerEvent("esx_society:getSociety", xPlayer.job.name, function(society)
|
||||
if society ~= nil then -- verified society
|
||||
TriggerEvent("esx_addonaccount:getSharedAccount", society.account, function(account)
|
||||
if account.money >= salary then -- does the society money to pay its employees?
|
||||
xPlayer.addAccountMoney("bank", salary, "Paycheck")
|
||||
account.removeMoney(salary)
|
||||
if Config.LogPaycheck then
|
||||
ESX.DiscordLogFields("Paycheck", "Paycheck - " .. jobLabel, "green", {
|
||||
{ name = "Player", value = xPlayer.name, inline = true },
|
||||
{ name = "ID", value = xPlayer.source, inline = true },
|
||||
{ name = "Amount", value = salary, inline = true },
|
||||
})
|
||||
end
|
||||
|
||||
TriggerClientEvent("esx:showAdvancedNotification", player, TranslateCap("bank"), TranslateCap("received_paycheck"), TranslateCap("received_salary", salary), "CHAR_BANK_MAZE", 9)
|
||||
else
|
||||
TriggerClientEvent("esx:showAdvancedNotification", player, TranslateCap("bank"), "", TranslateCap("company_nomoney"), "CHAR_BANK_MAZE", 1)
|
||||
end
|
||||
end)
|
||||
else -- not a society
|
||||
xPlayer.addAccountMoney("bank", salary, "Paycheck")
|
||||
if Config.LogPaycheck then
|
||||
ESX.DiscordLogFields("Paycheck", "Paycheck - " .. jobLabel, "green", {
|
||||
{ name = "Player", value = xPlayer.name, inline = true },
|
||||
{ name = "ID", value = xPlayer.source, inline = true },
|
||||
{ name = "Amount", value = salary, inline = true },
|
||||
})
|
||||
end
|
||||
TriggerClientEvent("esx:showAdvancedNotification", player, TranslateCap("bank"), TranslateCap("received_paycheck"), TranslateCap("received_salary", salary), "CHAR_BANK_MAZE", 9)
|
||||
if xPlayer.paycheckEnabled then
|
||||
if salary > 0 then
|
||||
if job == "unemployed" then -- unemployed
|
||||
xPlayer.addAccountMoney("bank", salary, "Welfare Check")
|
||||
TriggerClientEvent("esx:showAdvancedNotification", player, TranslateCap("bank"), TranslateCap("received_paycheck"), TranslateCap("received_help", salary), "CHAR_BANK_MAZE", 9)
|
||||
if Config.LogPaycheck then
|
||||
ESX.DiscordLogFields("Paycheck", "Paycheck - Unemployment Benefits", "green", {
|
||||
{ name = "Player", value = xPlayer.name, inline = true },
|
||||
{ name = "ID", value = xPlayer.source, inline = true },
|
||||
{ name = "Amount", value = salary, inline = true },
|
||||
})
|
||||
end
|
||||
end)
|
||||
else -- generic job
|
||||
xPlayer.addAccountMoney("bank", salary, "Paycheck")
|
||||
if Config.LogPaycheck then
|
||||
ESX.DiscordLogFields("Paycheck", "Paycheck - Generic", "green", {
|
||||
{ name = "Player", value = xPlayer.name, inline = true },
|
||||
{ name = "ID", value = xPlayer.source, inline = true },
|
||||
{ name = "Amount", value = salary, inline = true },
|
||||
})
|
||||
elseif Config.EnableSocietyPayouts then -- possibly a society
|
||||
TriggerEvent("esx_society:getSociety", xPlayer.job.name, function(society)
|
||||
if society ~= nil then -- verified society
|
||||
TriggerEvent("esx_addonaccount:getSharedAccount", society.account, function(account)
|
||||
if account.money >= salary then -- does the society money to pay its employees?
|
||||
xPlayer.addAccountMoney("bank", salary, "Paycheck")
|
||||
account.removeMoney(salary)
|
||||
if Config.LogPaycheck then
|
||||
ESX.DiscordLogFields("Paycheck", "Paycheck - " .. jobLabel, "green", {
|
||||
{ name = "Player", value = xPlayer.name, inline = true },
|
||||
{ name = "ID", value = xPlayer.source, inline = true },
|
||||
{ name = "Amount", value = salary, inline = true },
|
||||
})
|
||||
end
|
||||
|
||||
TriggerClientEvent("esx:showAdvancedNotification", player, TranslateCap("bank"), TranslateCap("received_paycheck"), TranslateCap("received_salary", salary), "CHAR_BANK_MAZE", 9)
|
||||
else
|
||||
TriggerClientEvent("esx:showAdvancedNotification", player, TranslateCap("bank"), "", TranslateCap("company_nomoney"), "CHAR_BANK_MAZE", 1)
|
||||
end
|
||||
end)
|
||||
else -- not a society
|
||||
xPlayer.addAccountMoney("bank", salary, "Paycheck")
|
||||
if Config.LogPaycheck then
|
||||
ESX.DiscordLogFields("Paycheck", "Paycheck - " .. jobLabel, "green", {
|
||||
{ name = "Player", value = xPlayer.name, inline = true },
|
||||
{ name = "ID", value = xPlayer.source, inline = true },
|
||||
{ name = "Amount", value = salary, inline = true },
|
||||
})
|
||||
end
|
||||
TriggerClientEvent("esx:showAdvancedNotification", player, TranslateCap("bank"), TranslateCap("received_paycheck"), TranslateCap("received_salary", salary), "CHAR_BANK_MAZE", 9)
|
||||
end
|
||||
end)
|
||||
else -- generic job
|
||||
xPlayer.addAccountMoney("bank", salary, "Paycheck")
|
||||
if Config.LogPaycheck then
|
||||
ESX.DiscordLogFields("Paycheck", "Paycheck - Generic", "green", {
|
||||
{ name = "Player", value = xPlayer.name, inline = true },
|
||||
{ name = "ID", value = xPlayer.source, inline = true },
|
||||
{ name = "Amount", value = salary, inline = true },
|
||||
})
|
||||
end
|
||||
TriggerClientEvent("esx:showAdvancedNotification", player, TranslateCap("bank"), TranslateCap("received_paycheck"), TranslateCap("received_salary", salary), "CHAR_BANK_MAZE", 9)
|
||||
end
|
||||
TriggerClientEvent("esx:showAdvancedNotification", player, TranslateCap("bank"), TranslateCap("received_paycheck"), TranslateCap("received_salary", salary), "CHAR_BANK_MAZE", 9)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
esx_clotheshop
|
||||
Copyright (C) 2015-2023 Jérémie N'gadi
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
esx_clotheshop Copyright (C) 2015-2023 Jérémie N'gadi
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
@@ -0,0 +1,15 @@
|
||||
<h1 align='center'>[ESX] Chat Theme</a></h1><p align='center'><b><a href='https://discord.esx-framework.org/'>Discord</a> - <a href='https://documentation.esx-framework.org/legacy/installation'>Documentation</a></b></h5>
|
||||
|
||||
A ESX-Based Chat-theme for your server
|
||||
|
||||
# License
|
||||
|
||||
esx_chat_theme - ESX Chat Theme
|
||||
|
||||
Copyright (C) 2024 Jérémie N'gadi
|
||||
|
||||
This program Is free software: you can redistribute it And/Or modify it under the terms Of the GNU General Public License As published by the Free Software Foundation, either version 3 Of the License, Or (at your option) any later version.
|
||||
|
||||
This program Is distributed In the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty Of MERCHANTABILITY Or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License For more details.
|
||||
|
||||
You should have received a copy Of the GNU General Public License along with this program. If Not, see http://www.gnu.org/licenses/.
|
||||
@@ -0,0 +1,17 @@
|
||||
version '1.10.10'
|
||||
author 'ESX-Framework'
|
||||
description 'A ESX Stylised theme for the chat resource.'
|
||||
|
||||
file 'style.css'
|
||||
file 'shadow.js'
|
||||
|
||||
chat_theme 'esx' {
|
||||
styleSheet = 'style.css',
|
||||
script = 'shadow.js',
|
||||
msgTemplates = {
|
||||
default = '<b>{0}</b><span>{1}</span>'
|
||||
}
|
||||
}
|
||||
|
||||
game 'common'
|
||||
fx_version 'adamant'
|
||||
@@ -0,0 +1,74 @@
|
||||
(function() {
|
||||
var Filters = {}
|
||||
|
||||
var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
svg.setAttribute("style", "display:block;width:0px;height:0px");
|
||||
var defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
|
||||
|
||||
var blurFilter = document.createElementNS("http://www.w3.org/2000/svg", "filter");
|
||||
blurFilter.setAttribute("id", "svgBlurFilter");
|
||||
var feGaussianFilter = document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur");
|
||||
feGaussianFilter.setAttribute("stdDeviation", "0 0");
|
||||
blurFilter.appendChild(feGaussianFilter);
|
||||
defs.appendChild(blurFilter);
|
||||
Filters._svgBlurFilter = feGaussianFilter;
|
||||
|
||||
// Drop Shadow Filter
|
||||
var dropShadowFilter = document.createElementNS("http://www.w3.org/2000/svg", "filter");
|
||||
dropShadowFilter.setAttribute("id", "svgDropShadowFilter");
|
||||
var feGaussianFilter = document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur");
|
||||
feGaussianFilter.setAttribute("in", "SourceAlpha");
|
||||
feGaussianFilter.setAttribute("stdDeviation", "3");
|
||||
dropShadowFilter.appendChild(feGaussianFilter);
|
||||
Filters._svgDropshadowFilterBlur = feGaussianFilter;
|
||||
|
||||
var feOffset = document.createElementNS("http://www.w3.org/2000/svg", "feOffset");
|
||||
feOffset.setAttribute("dx", "0");
|
||||
feOffset.setAttribute("dy", "0");
|
||||
feOffset.setAttribute("result", "offsetblur");
|
||||
dropShadowFilter.appendChild(feOffset);
|
||||
Filters._svgDropshadowFilterOffset = feOffset;
|
||||
|
||||
var feFlood = document.createElementNS("http://www.w3.org/2000/svg", "feFlood");
|
||||
feFlood.setAttribute("flood-color", "rgba(0,0,0,1)");
|
||||
dropShadowFilter.appendChild(feFlood);
|
||||
Filters._svgDropshadowFilterFlood = feFlood;
|
||||
|
||||
var feComposite = document.createElementNS("http://www.w3.org/2000/svg", "feComposite");
|
||||
feComposite.setAttribute("in2", "offsetblur");
|
||||
feComposite.setAttribute("operator", "in");
|
||||
dropShadowFilter.appendChild(feComposite);
|
||||
var feComposite = document.createElementNS("http://www.w3.org/2000/svg", "feComposite");
|
||||
feComposite.setAttribute("in2", "SourceAlpha");
|
||||
feComposite.setAttribute("operator", "out");
|
||||
feComposite.setAttribute("result", "outer");
|
||||
dropShadowFilter.appendChild(feComposite);
|
||||
|
||||
var feMerge = document.createElementNS("http://www.w3.org/2000/svg", "feMerge");
|
||||
var feMergeNode = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
|
||||
feMerge.appendChild(feMergeNode);
|
||||
var feMergeNode = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
|
||||
feMerge.appendChild(feMergeNode);
|
||||
Filters._svgDropshadowMergeNode = feMergeNode;
|
||||
dropShadowFilter.appendChild(feMerge);
|
||||
defs.appendChild(dropShadowFilter);
|
||||
svg.appendChild(defs);
|
||||
document.documentElement.appendChild(svg);
|
||||
|
||||
const blurScale = 4;
|
||||
const scale = (document.body.clientWidth / 1280);
|
||||
|
||||
Filters._svgDropshadowFilterBlur.setAttribute("stdDeviation",
|
||||
1 * blurScale + " " +
|
||||
1 * blurScale
|
||||
);
|
||||
Filters._svgDropshadowFilterOffset.setAttribute("dx",
|
||||
String(Math.cos(45 * Math.PI / 180) * 1 * scale));
|
||||
Filters._svgDropshadowFilterOffset.setAttribute("dy",
|
||||
String(Math.sin(45 * Math.PI / 180) * 1 * scale));
|
||||
Filters._svgDropshadowFilterFlood.setAttribute("flood-color",
|
||||
'rgba(0, 0, 0, 1)');
|
||||
Filters._svgDropshadowMergeNode.setAttribute("in",
|
||||
"SourceGraphic");
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,103 @@
|
||||
* {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.chat-window {
|
||||
background: inherit !important;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font2';
|
||||
src: url(https://runtime.fivem.net/temp/ChaletLondonNineteenSixty.otf?a);
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font2_cond';
|
||||
src: url(https://runtime.fivem.net/temp/chaletcomprime-colognesixty-webfont.ttf?a);
|
||||
}
|
||||
|
||||
.msg {
|
||||
font-family: Font2, sans-serif;
|
||||
color: #ffffff;
|
||||
|
||||
font-size: calc(1.7vh); /* 13px in 720p, calc'd by width */
|
||||
filter: url(#svgDropShadowFilter);
|
||||
|
||||
line-height: calc(2.7vh * 1.2);
|
||||
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.msg > span > span > b {
|
||||
font-family: Font2_cond, sans-serif;
|
||||
font-weight: normal;
|
||||
|
||||
vertical-align: baseline;
|
||||
padding-right: 11px;
|
||||
|
||||
line-height: calc(2.7vh * 1.2);
|
||||
|
||||
font-size: calc(2.4vh);
|
||||
}
|
||||
|
||||
.msg > span > span > span {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.msg i:first-of-type {
|
||||
font-style: normal;
|
||||
color: #c0c0c0;
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
background: inherit !important;
|
||||
height: auto;
|
||||
|
||||
font-family: Font2, sans-serif;
|
||||
}
|
||||
|
||||
.chat-input > div {
|
||||
background-color: rgba(30, 30, 30, 0.7) !important;
|
||||
border: calc(0.24vh / 2) solid #fb9c04ce;
|
||||
outline: calc(0.24vh / 2) solid #fb9c04ce; /* to replace margin-background */
|
||||
padding: calc(0.2vh / 2);
|
||||
border-radius: 5px;
|
||||
filter: url(#svgDropShadowFilter);
|
||||
}
|
||||
|
||||
.chat-input .prefix {
|
||||
margin: 0;
|
||||
margin-left: 0.7%;
|
||||
margin-right: 0.7%;
|
||||
margin-top: -0.1%;
|
||||
line-height: 2.8vh;
|
||||
}
|
||||
|
||||
.chat-input .prefix.any {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.chat-input .prefix.any:before {
|
||||
content: '[';
|
||||
color: #fb9b04;
|
||||
}
|
||||
|
||||
.chat-input .prefix.any:after {
|
||||
content: ']';
|
||||
color: #fb9b04;
|
||||
margin-right: 1%;
|
||||
}
|
||||
|
||||
.suggestions {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
textarea {
|
||||
background: transparent;
|
||||
padding: 0.5vh;
|
||||
}
|
||||
@@ -144,7 +144,7 @@ local function focusPreview()
|
||||
end
|
||||
|
||||
if PREVIEW_KEYBIND then
|
||||
RegisterCommand("previewContext", focusPreview)
|
||||
RegisterCommand("previewContext", focusPreview, false)
|
||||
|
||||
RegisterKeyMapping("previewContext", "Preview Active Context", "keyboard", PREVIEW_KEYBIND)
|
||||
end
|
||||
@@ -208,15 +208,15 @@ if Debug then
|
||||
|
||||
RegisterCommand("ctx:preview", function()
|
||||
exports["esx_context"]:Preview(position, eles)
|
||||
end)
|
||||
end, false)
|
||||
|
||||
RegisterCommand("ctx:open", function()
|
||||
exports["esx_context"]:Open(position, eles, onSelect, onClose)
|
||||
end)
|
||||
end, false)
|
||||
|
||||
RegisterCommand("ctx:close", function()
|
||||
exports["esx_context"]:Close()
|
||||
end)
|
||||
end, false)
|
||||
|
||||
RegisterCommand("ctx:form", function()
|
||||
local formMenu = {
|
||||
@@ -260,5 +260,5 @@ if Debug then
|
||||
}
|
||||
|
||||
exports["esx_context"]:Open(position, formMenu, onSelect, onClose)
|
||||
end)
|
||||
end, false)
|
||||
end
|
||||
|
||||
@@ -37,6 +37,11 @@ ESX.UI.Menu.RegisterType(MenuType, openMenu, closeMenu)
|
||||
|
||||
RegisterNUICallback("menu_submit", function(data, cb)
|
||||
local menu = ESX.UI.Menu.GetOpened(MenuType, data._namespace, data._name)
|
||||
|
||||
if not menu then
|
||||
return
|
||||
end
|
||||
|
||||
if menu.submit ~= nil then
|
||||
menu.submit(data, menu)
|
||||
end
|
||||
@@ -46,6 +51,10 @@ end)
|
||||
RegisterNUICallback("menu_cancel", function(data, cb)
|
||||
local menu = ESX.UI.Menu.GetOpened(MenuType, data._namespace, data._name)
|
||||
|
||||
if not menu then
|
||||
return
|
||||
end
|
||||
|
||||
if menu.cancel ~= nil then
|
||||
menu.cancel(data, menu)
|
||||
end
|
||||
@@ -55,6 +64,10 @@ end)
|
||||
RegisterNUICallback("menu_change", function(data, cb)
|
||||
local menu = ESX.UI.Menu.GetOpened(MenuType, data._namespace, data._name)
|
||||
|
||||
if not menu then
|
||||
return
|
||||
end
|
||||
|
||||
for i = 1, #data.elements, 1 do
|
||||
menu.setElement(i, "value", data.elements[i].value)
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ local function closeMenu(namespace, name)
|
||||
})
|
||||
|
||||
if not next(OpenedMenus) then
|
||||
SetNuiFocus(false)
|
||||
SetNuiFocus(false, false)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -41,10 +41,15 @@ AddEventHandler("esx_menu_dialog:message:menu_submit", function(data)
|
||||
local menu = ESX.UI.Menu.GetOpened(MenuType, data._namespace, data._name)
|
||||
local cancel = false
|
||||
|
||||
if not menu then
|
||||
return
|
||||
end
|
||||
|
||||
if menu.submit then
|
||||
-- is the submitted data a number?
|
||||
if tonumber(data.value) then
|
||||
data.value = ESX.Math.Round(tonumber(data.value))
|
||||
local value = tonumber(data.value)
|
||||
if value then
|
||||
data.value = ESX.Math.Round(value)
|
||||
|
||||
-- check for negative value
|
||||
if tonumber(data.value) <= 0 then
|
||||
@@ -66,6 +71,10 @@ end)
|
||||
AddEventHandler("esx_menu_dialog:message:menu_cancel", function(data)
|
||||
local menu = ESX.UI.Menu.GetOpened(MenuType, data._namespace, data._name)
|
||||
|
||||
if not menu then
|
||||
return
|
||||
end
|
||||
|
||||
if menu.cancel ~= nil then
|
||||
menu.cancel(data, menu)
|
||||
end
|
||||
@@ -74,6 +83,10 @@ end)
|
||||
AddEventHandler("esx_menu_dialog:message:menu_change", function(data)
|
||||
local menu = ESX.UI.Menu.GetOpened(MenuType, data._namespace, data._name)
|
||||
|
||||
if not menu then
|
||||
return
|
||||
end
|
||||
|
||||
if menu.change ~= nil then
|
||||
menu.change(data, menu)
|
||||
end
|
||||
|
||||
@@ -33,7 +33,7 @@ CreateThread(function()
|
||||
end
|
||||
|
||||
if OpenedMenuCount == 0 then
|
||||
SetNuiFocus(false)
|
||||
SetNuiFocus(false, false)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -41,6 +41,11 @@ CreateThread(function()
|
||||
|
||||
RegisterNUICallback("menu_submit", function(data, cb)
|
||||
local menu = ESX.UI.Menu.GetOpened(MenuType, data._namespace, data._name)
|
||||
|
||||
if not menu then
|
||||
return
|
||||
end
|
||||
|
||||
if menu.submit then
|
||||
menu.submit(data, menu)
|
||||
end
|
||||
@@ -50,6 +55,10 @@ CreateThread(function()
|
||||
RegisterNUICallback("menu_cancel", function(data, cb)
|
||||
local menu = ESX.UI.Menu.GetOpened(MenuType, data._namespace, data._name)
|
||||
|
||||
if not menu then
|
||||
return
|
||||
end
|
||||
|
||||
if menu.cancel ~= nil then
|
||||
menu.cancel(data, menu)
|
||||
end
|
||||
|
||||
@@ -1,364 +1,64 @@
|
||||
local mp_m_freemode_01 = `mp_m_freemode_01`
|
||||
local mp_f_freemode_01 = `mp_f_freemode_01`
|
||||
|
||||
local SpawnCoords = Config.Spawn[ESX.Math.Random(1,#Config.Spawn)]
|
||||
-- Connection Logic
|
||||
|
||||
if ESX.GetConfig().Multichar then
|
||||
CreateThread(function()
|
||||
while not ESX.PlayerLoaded do
|
||||
Wait(100)
|
||||
local function AwaitContext()
|
||||
while GetResourceState("esx_context") ~= "started" do
|
||||
Wait(100)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
if NetworkIsPlayerActive(PlayerId()) then
|
||||
exports.spawnmanager:setAutoSpawn(false)
|
||||
DoScreenFadeOut(0)
|
||||
while GetResourceState("esx_context") ~= "started" do
|
||||
Wait(100)
|
||||
end
|
||||
TriggerEvent("esx_multicharacter:SetupCharacters")
|
||||
CreateThread(function()
|
||||
|
||||
while not ESX.PlayerLoaded do
|
||||
Wait(100)
|
||||
|
||||
if NetworkIsPlayerActive(ESX.playerId) then
|
||||
ESX.DisableSpawnManager()
|
||||
DoScreenFadeOut(0)
|
||||
|
||||
local ready = AwaitContext()
|
||||
if ready then
|
||||
|
||||
Multicharacter:SetupCharacters()
|
||||
break
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
local canRelog, cam, spawned = true, nil, nil
|
||||
local Characters , hidePlayers = {} , false
|
||||
|
||||
RegisterNetEvent("esx_multicharacter:SetupCharacters")
|
||||
AddEventHandler("esx_multicharacter:SetupCharacters", function()
|
||||
ESX.PlayerLoaded = false
|
||||
ESX.PlayerData = {}
|
||||
spawned = false
|
||||
cam = CreateCam("DEFAULT_SCRIPTED_CAMERA", true)
|
||||
local playerPed = PlayerPedId()
|
||||
SetEntityCoords(playerPed, SpawnCoords.x, SpawnCoords.y, SpawnCoords.z, true, false, false, false)
|
||||
SetEntityHeading(playerPed, SpawnCoords.w)
|
||||
local offset = GetOffsetFromEntityInWorldCoords(playerPed, 0, 1.7, 0.4)
|
||||
DoScreenFadeOut(0)
|
||||
SetCamActive(cam, true)
|
||||
RenderScriptCams(true, false, 1, true, true)
|
||||
SetCamCoord(cam, offset.x, offset.y, offset.z)
|
||||
PointCamAtCoord(cam, SpawnCoords.x, SpawnCoords.y, SpawnCoords.z + 1.3)
|
||||
StartLoop()
|
||||
ShutdownLoadingScreen()
|
||||
ShutdownLoadingScreenNui()
|
||||
TriggerEvent("esx:loadingScreenOff")
|
||||
Wait(200)
|
||||
TriggerServerEvent("esx_multicharacter:SetupCharacters")
|
||||
end)
|
||||
|
||||
StartLoop = function()
|
||||
hidePlayers = true
|
||||
MumbleSetVolumeOverride(PlayerId(), 0.0)
|
||||
CreateThread(function()
|
||||
local keys = { 18, 27, 172, 173, 174, 175, 176, 177, 187, 188, 191, 201, 108, 109, 209, 19 }
|
||||
while hidePlayers do
|
||||
DisableAllControlActions(0)
|
||||
for i = 1, #keys do
|
||||
EnableControlAction(0, keys[i], true)
|
||||
end
|
||||
SetEntityVisible(PlayerPedId(), 0, 0)
|
||||
SetLocalPlayerVisibleLocally(1)
|
||||
SetPlayerInvincible(PlayerId(), 1)
|
||||
ThefeedHideThisFrame()
|
||||
HideHudComponentThisFrame(11)
|
||||
HideHudComponentThisFrame(12)
|
||||
HideHudComponentThisFrame(21)
|
||||
HideHudAndRadarThisFrame()
|
||||
Wait(0)
|
||||
local vehicles = GetGamePool("CVehicle")
|
||||
for i = 1, #vehicles do
|
||||
SetEntityLocallyInvisible(vehicles[i])
|
||||
end
|
||||
end
|
||||
local playerId, playerPed = PlayerId(), PlayerPedId()
|
||||
MumbleSetVolumeOverride(playerId, -1.0)
|
||||
SetEntityVisible(playerPed, 1, 0)
|
||||
SetPlayerInvincible(playerId, 0)
|
||||
FreezeEntityPosition(playerPed, false)
|
||||
Wait(10000)
|
||||
canRelog = true
|
||||
end)
|
||||
CreateThread(function()
|
||||
local playerPool = {}
|
||||
while hidePlayers do
|
||||
local players = GetActivePlayers()
|
||||
for i = 1, #players do
|
||||
local player = players[i]
|
||||
if player ~= PlayerId() and not playerPool[player] then
|
||||
playerPool[player] = true
|
||||
NetworkConcealPlayer(player, true, true)
|
||||
end
|
||||
end
|
||||
Wait(500)
|
||||
end
|
||||
for k in pairs(playerPool) do
|
||||
NetworkConcealPlayer(k, false, false)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
SetupCharacter = function(index)
|
||||
if not spawned then
|
||||
exports.spawnmanager:spawnPlayer({
|
||||
x = SpawnCoords.x,
|
||||
y = SpawnCoords.y,
|
||||
z = SpawnCoords.z,
|
||||
heading = SpawnCoords.w,
|
||||
model = Characters[index].model or mp_m_freemode_01,
|
||||
skipFade = true,
|
||||
}, function()
|
||||
canRelog = false
|
||||
if Characters[index] then
|
||||
local skin = Characters[index].skin or Config.Default
|
||||
if not Characters[index].model then
|
||||
if Characters[index].sex == TranslateCap("female") then
|
||||
skin.sex = 1
|
||||
else
|
||||
skin.sex = 0
|
||||
end
|
||||
end
|
||||
TriggerEvent("skinchanger:loadSkin", skin)
|
||||
end
|
||||
DoScreenFadeIn(600)
|
||||
-- Events
|
||||
|
||||
RegisterNetEvent("esx_multicharacter:SetupUI", function(data, slots)
|
||||
Multicharacter:SetupUI(data, slots)
|
||||
end)
|
||||
|
||||
RegisterNetEvent('esx:playerLoaded', function(playerData, isNew, skin)
|
||||
Multicharacter:PlayerLoaded(playerData, isNew, skin)
|
||||
end)
|
||||
|
||||
RegisterNetEvent('esx:onPlayerLogout', function()
|
||||
DoScreenFadeOut(500)
|
||||
Wait(5000)
|
||||
|
||||
Multicharacter.spawned = false
|
||||
|
||||
Multicharacter:SetupCharacters()
|
||||
TriggerEvent("esx_skin:resetFirstSpawn")
|
||||
end)
|
||||
|
||||
-- Relog
|
||||
|
||||
if Config.Relog then
|
||||
RegisterCommand("relog", function()
|
||||
if Multicharacter.canRelog then
|
||||
Multicharacter.canRelog = false
|
||||
TriggerServerEvent("esx_multicharacter:relog")
|
||||
|
||||
ESX.SetTimeout(10000, function()
|
||||
Multicharacter.canRelog = true
|
||||
end)
|
||||
repeat
|
||||
Wait(200)
|
||||
until not IsScreenFadedOut()
|
||||
elseif Characters[index] and Characters[index].skin then
|
||||
if Characters[spawned] and Characters[spawned].model then
|
||||
RequestModel(Characters[index].model)
|
||||
while not HasModelLoaded(Characters[index].model) do
|
||||
RequestModel(Characters[index].model)
|
||||
Wait(0)
|
||||
end
|
||||
SetPlayerModel(PlayerId(), Characters[index].model)
|
||||
SetModelAsNoLongerNeeded(Characters[index].model)
|
||||
end
|
||||
TriggerEvent("skinchanger:loadSkin", Characters[index].skin)
|
||||
|
||||
end
|
||||
spawned = index
|
||||
local playerPed = PlayerPedId()
|
||||
FreezeEntityPosition(PlayerPedId(), true)
|
||||
SetPedAoBlobRendering(playerPed, true)
|
||||
SetEntityAlpha(playerPed, 255)
|
||||
SendNUIMessage({
|
||||
action = "openui",
|
||||
character = Characters[spawned],
|
||||
})
|
||||
end
|
||||
|
||||
function CharacterDeleteConfirmation(characters, slots, SelectedCharacter, value)
|
||||
local elements = {
|
||||
{ title = TranslateCap("char_delete_confirmation"), icon = "fa-solid fa-users", description = TranslateCap("char_delete_confirmation_description"), unselectable = true },
|
||||
{ title = TranslateCap("char_delete"), icon = "fa-solid fa-xmark", description = TranslateCap("char_delete_yes_description"), action = "delete", value = value },
|
||||
{ title = TranslateCap("return"), unselectable = false, icon = "fa-solid fa-arrow-left", description = TranslateCap("char_delete_no_description"), action = "return" },
|
||||
}
|
||||
|
||||
ESX.OpenContext("left", elements, function(_, Action)
|
||||
if Action.action == "delete" then
|
||||
ESX.CloseContext()
|
||||
TriggerServerEvent("esx_multicharacter:DeleteCharacter", Action.value)
|
||||
spawned = false
|
||||
elseif Action.action == "return" then
|
||||
CharacterOptions(characters, slots, SelectedCharacter)
|
||||
end
|
||||
end, nil, false)
|
||||
end
|
||||
|
||||
function CharacterOptions(characters, slots, SelectedCharacter)
|
||||
local elements = {
|
||||
{ title = TranslateCap("character", characters[SelectedCharacter.value].firstname .. " " .. characters[SelectedCharacter.value].lastname), icon = "fa-regular fa-user", unselectable = true },
|
||||
{ title = TranslateCap("return"), unselectable = false, icon = "fa-solid fa-arrow-left", description = TranslateCap("return_description"), action = "return" },
|
||||
}
|
||||
if not characters[SelectedCharacter.value].disabled then
|
||||
elements[3] = { title = TranslateCap("char_play"), description = TranslateCap("char_play_description"), icon = "fa-solid fa-play", action = "play", value = SelectedCharacter.value }
|
||||
else
|
||||
elements[3] = { title = TranslateCap("char_disabled"), value = SelectedCharacter.value, icon = "fa-solid fa-xmark", description = TranslateCap("char_disabled_description") }
|
||||
end
|
||||
if Config.CanDelete then
|
||||
elements[4] = { title = TranslateCap("char_delete"), icon = "fa-solid fa-xmark", description = TranslateCap("char_delete_description"), action = "delete", value = SelectedCharacter.value }
|
||||
end
|
||||
ESX.OpenContext("left", elements, function(_, Action)
|
||||
if Action.action == "play" then
|
||||
SendNUIMessage({
|
||||
action = "closeui",
|
||||
})
|
||||
ESX.CloseContext()
|
||||
TriggerServerEvent("esx_multicharacter:CharacterChosen", Action.value, false)
|
||||
elseif Action.action == "delete" then
|
||||
CharacterDeleteConfirmation(characters, slots, SelectedCharacter, Action.value)
|
||||
elseif Action.action == "return" then
|
||||
SelectCharacterMenu(characters, slots)
|
||||
end
|
||||
end, nil, false)
|
||||
end
|
||||
|
||||
function SelectCharacterMenu(characters, slots)
|
||||
local Character = next(characters)
|
||||
local elements = { { title = TranslateCap("select_char"), icon = "fa-solid fa-users", description = TranslateCap("select_char_description"), unselectable = true } }
|
||||
for k, v in pairs(characters) do
|
||||
if not v.model and v.skin then
|
||||
if v.skin.model then
|
||||
v.model = v.skin.model
|
||||
elseif v.skin.sex == 1 then
|
||||
v.model = mp_f_freemode_01
|
||||
else
|
||||
v.model = mp_m_freemode_01
|
||||
end
|
||||
end
|
||||
if not spawned then
|
||||
SetupCharacter(Character)
|
||||
end
|
||||
local label = v.firstname .. " " .. v.lastname
|
||||
if characters[k].disabled then
|
||||
elements[#elements + 1] = { title = label, icon = "fa-regular fa-user", value = v.id }
|
||||
else
|
||||
elements[#elements + 1] = { title = label, icon = "fa-regular fa-user", value = v.id }
|
||||
end
|
||||
end
|
||||
if #elements - 1 < slots then
|
||||
elements[#elements + 1] = { title = TranslateCap("create_char"), icon = "fa-solid fa-plus", value = (#elements + 1), new = true }
|
||||
end
|
||||
|
||||
ESX.OpenContext("left", elements, function(_, SelectedCharacter)
|
||||
if SelectedCharacter.new then
|
||||
ESX.CloseContext()
|
||||
local GetSlot = function()
|
||||
for i = 1, slots do
|
||||
if not characters[i] then
|
||||
return i
|
||||
end
|
||||
end
|
||||
end
|
||||
local slot = GetSlot()
|
||||
TriggerServerEvent("esx_multicharacter:CharacterChosen", slot, true)
|
||||
TriggerEvent("esx_identity:showRegisterIdentity")
|
||||
local playerPed = PlayerPedId()
|
||||
SetPedAoBlobRendering(playerPed, false)
|
||||
SetEntityAlpha(playerPed, 0)
|
||||
SendNUIMessage({
|
||||
action = "closeui",
|
||||
})
|
||||
else
|
||||
CharacterOptions(characters, slots, SelectedCharacter)
|
||||
SetupCharacter(SelectedCharacter.value)
|
||||
local playerPed = PlayerPedId()
|
||||
SetPedAoBlobRendering(playerPed, true)
|
||||
ResetEntityAlpha(playerPed)
|
||||
end
|
||||
end, nil, false)
|
||||
end
|
||||
|
||||
RegisterNetEvent("esx_multicharacter:SetupUI")
|
||||
AddEventHandler("esx_multicharacter:SetupUI", function(data, slots)
|
||||
DoScreenFadeOut(0)
|
||||
Characters = data
|
||||
slots = slots
|
||||
local Character = next(Characters)
|
||||
exports.spawnmanager:forceRespawn()
|
||||
|
||||
if not Character then
|
||||
SendNUIMessage({
|
||||
action = "closeui",
|
||||
})
|
||||
exports.spawnmanager:spawnPlayer({
|
||||
x = SpawnCoords.x,
|
||||
y = SpawnCoords.y,
|
||||
z = SpawnCoords.z,
|
||||
heading = SpawnCoords.w,
|
||||
model = mp_m_freemode_01,
|
||||
skipFade = true,
|
||||
}, function()
|
||||
canRelog = false
|
||||
DoScreenFadeIn(400)
|
||||
Wait(400)
|
||||
local playerPed = PlayerPedId()
|
||||
SetPedAoBlobRendering(playerPed, false)
|
||||
SetEntityAlpha(playerPed, 0)
|
||||
TriggerServerEvent("esx_multicharacter:CharacterChosen", 1, true)
|
||||
TriggerEvent("esx_identity:showRegisterIdentity")
|
||||
end)
|
||||
else
|
||||
SelectCharacterMenu(Characters, slots)
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx:playerLoaded")
|
||||
AddEventHandler("esx:playerLoaded", function(playerData, isNew, skin)
|
||||
local spawn = playerData.coords or Config.Spawn
|
||||
if isNew or not skin or #skin == 1 then
|
||||
local finished = false
|
||||
skin = Config.Default[playerData.sex]
|
||||
skin.sex = playerData.sex == "m" and 0 or 1
|
||||
local model = skin.sex == 0 and mp_m_freemode_01 or mp_f_freemode_01
|
||||
RequestModel(model)
|
||||
while not HasModelLoaded(model) do
|
||||
RequestModel(model)
|
||||
Wait(0)
|
||||
end
|
||||
SetPlayerModel(PlayerId(), model)
|
||||
SetModelAsNoLongerNeeded(model)
|
||||
TriggerEvent("skinchanger:loadSkin", skin, function()
|
||||
local playerPed = PlayerPedId()
|
||||
SetPedAoBlobRendering(playerPed, true)
|
||||
ResetEntityAlpha(playerPed)
|
||||
TriggerEvent("esx_skin:openSaveableMenu", function()
|
||||
finished = true
|
||||
end, function()
|
||||
finished = true
|
||||
end)
|
||||
end)
|
||||
repeat
|
||||
Wait(200)
|
||||
until finished
|
||||
end
|
||||
|
||||
DoScreenFadeOut(750)
|
||||
Wait(750)
|
||||
|
||||
SetCamActive(cam, false)
|
||||
RenderScriptCams(false, false, 0, true, true)
|
||||
cam = nil
|
||||
local playerPed = PlayerPedId()
|
||||
FreezeEntityPosition(playerPed, true)
|
||||
SetEntityCoordsNoOffset(playerPed, spawn.x, spawn.y, spawn.z, false, false, false, true)
|
||||
SetEntityHeading(playerPed, spawn.heading)
|
||||
if not isNew then
|
||||
TriggerEvent("skinchanger:loadSkin", skin or Characters[spawned].skin)
|
||||
end
|
||||
Wait(500)
|
||||
|
||||
DoScreenFadeIn(750)
|
||||
Wait(750)
|
||||
|
||||
repeat
|
||||
Wait(200)
|
||||
until not IsScreenFadedOut()
|
||||
TriggerServerEvent("esx:onPlayerSpawn")
|
||||
TriggerEvent("esx:onPlayerSpawn")
|
||||
TriggerEvent("playerSpawned")
|
||||
TriggerEvent("esx:restoreLoadout")
|
||||
Characters, hidePlayers = {}, false
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx:onPlayerLogout")
|
||||
AddEventHandler("esx:onPlayerLogout", function()
|
||||
DoScreenFadeOut(500)
|
||||
Wait(1000)
|
||||
spawned = false
|
||||
TriggerEvent("esx_multicharacter:SetupCharacters")
|
||||
TriggerEvent("esx_skin:resetFirstSpawn")
|
||||
end)
|
||||
|
||||
if Config.Relog then
|
||||
RegisterCommand("relog", function()
|
||||
if canRelog then
|
||||
canRelog = false
|
||||
TriggerServerEvent("esx_multicharacter:relog")
|
||||
ESX.SetTimeout(10000, function()
|
||||
canRelog = true
|
||||
end)
|
||||
end
|
||||
end,false)
|
||||
end
|
||||
end,false)
|
||||
end
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
Menu = {}
|
||||
Menu._index = Menu
|
||||
Menu.currentElements = {}
|
||||
|
||||
function Menu:OpenMenu()
|
||||
ESX.OpenContext("left", self.currentElements, self.onUse, nil, false)
|
||||
end
|
||||
|
||||
function Menu:Close()
|
||||
self.currentElements = {}
|
||||
ESX.CloseContext()
|
||||
end
|
||||
|
||||
function Menu:CheckModel(character)
|
||||
if not character.model and character.skin then
|
||||
if character.skin.model then
|
||||
character.model = character.skin.model
|
||||
elseif character.skin.sex == 1 then
|
||||
character.model = `mp_f_freemode_01`
|
||||
else
|
||||
character.model = `mp_m_freemode_01`
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Menu:AddCharacters()
|
||||
for _, v in pairs(Multicharacter.Characters) do
|
||||
self:CheckModel(v)
|
||||
|
||||
local label = ("%s %s"):format(v.firstname, v.lastname)
|
||||
self.currentElements[#self.currentElements + 1] = { title = label, icon = "fa-regular fa-user", value = v.id}
|
||||
end
|
||||
if #self.currentElements - 1 < Multicharacter.slots then
|
||||
self.currentElements[#self.currentElements + 1] = { title = TranslateCap("create_char"), icon = "fa-solid fa-plus", value = (#self.currentElements + 1), new = true }
|
||||
end
|
||||
end
|
||||
|
||||
local GetSlot = function()
|
||||
for i = 1, Multicharacter.slots do
|
||||
if not Multicharacter.Characters[i] then
|
||||
return i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Menu:NewCharacter()
|
||||
self:Close()
|
||||
local slot = GetSlot()
|
||||
|
||||
TriggerServerEvent("esx_multicharacter:CharacterChosen", slot, true)
|
||||
TriggerEvent("esx_identity:showRegisterIdentity")
|
||||
|
||||
local playerPed = PlayerPedId()
|
||||
|
||||
SetPedAoBlobRendering(playerPed, false)
|
||||
SetEntityAlpha(playerPed, 0, false)
|
||||
|
||||
Multicharacter:CloseUI()
|
||||
end
|
||||
|
||||
|
||||
function Menu:SelectCharacter()
|
||||
local Characters = Multicharacter.Characters
|
||||
local Character = next(Characters)
|
||||
self:CheckModel(Characters[Character])
|
||||
|
||||
if not Multicharacter.spawned then
|
||||
Multicharacter:SetupCharacter(Character)
|
||||
end
|
||||
|
||||
self.currentElements = {
|
||||
{
|
||||
title = TranslateCap("select_char"),
|
||||
icon = "fa-solid fa-users",
|
||||
description = TranslateCap("select_char_description"),
|
||||
unselectable = true
|
||||
}
|
||||
}
|
||||
|
||||
self:AddCharacters()
|
||||
self.onUse = function(_, SelectedCharacter)
|
||||
if SelectedCharacter.new then
|
||||
self:NewCharacter()
|
||||
else
|
||||
if SelectedCharacter.value ~= Multicharacter.spawned then
|
||||
Multicharacter:SetupCharacter(SelectedCharacter.value)
|
||||
local playerPed = PlayerPedId()
|
||||
SetPedAoBlobRendering(playerPed, true)
|
||||
ResetEntityAlpha(playerPed)
|
||||
end
|
||||
self:CharacterOptions()
|
||||
end
|
||||
end
|
||||
|
||||
self:OpenMenu()
|
||||
end
|
||||
|
||||
|
||||
function Menu:CharacterOptions()
|
||||
local currentCharacter = Multicharacter.Characters[Multicharacter.spawned]
|
||||
local elements = {
|
||||
{
|
||||
title = TranslateCap("character", currentCharacter.firstname .. " " .. currentCharacter.lastname),
|
||||
icon = "fa-regular fa-user",
|
||||
unselectable = true
|
||||
},
|
||||
{
|
||||
title = TranslateCap("return"),
|
||||
unselectable = false,
|
||||
icon = "fa-solid fa-arrow-left",
|
||||
description = TranslateCap("return_description"),
|
||||
action = "return"
|
||||
},
|
||||
}
|
||||
|
||||
if not currentCharacter.disabled then
|
||||
elements[3] = {
|
||||
title = TranslateCap("char_play"),
|
||||
description = TranslateCap("char_play_description"),
|
||||
icon = "fa-solid fa-play",
|
||||
action = "play",
|
||||
}
|
||||
else
|
||||
elements[3] = {
|
||||
title = TranslateCap("char_disabled"),
|
||||
icon = "fa-solid fa-xmark",
|
||||
description = TranslateCap("char_disabled_description")
|
||||
}
|
||||
end
|
||||
if Config.CanDelete then
|
||||
elements[4] = {
|
||||
title = TranslateCap("char_delete"),
|
||||
icon = "fa-solid fa-xmark",
|
||||
description = TranslateCap("char_delete_description"),
|
||||
action = "delete",
|
||||
}
|
||||
end
|
||||
|
||||
self.currentElements = elements
|
||||
self.onUse = function(_, Action)
|
||||
if Action.action == "play" then
|
||||
Multicharacter:CloseUI()
|
||||
self:Close()
|
||||
|
||||
TriggerServerEvent("esx_multicharacter:CharacterChosen", Multicharacter.spawned, false)
|
||||
elseif Action.action == "delete" then
|
||||
self:ConfirmDeletion()
|
||||
elseif Action.action == "return" then
|
||||
self:SelectCharacter()
|
||||
end
|
||||
end
|
||||
|
||||
self:OpenMenu()
|
||||
end
|
||||
|
||||
function Menu:ConfirmDeletion()
|
||||
self.currentElements = {
|
||||
{
|
||||
title = TranslateCap("char_delete_confirmation"),
|
||||
icon = "fa-solid fa-users",
|
||||
description = TranslateCap("char_delete_confirmation_description"),
|
||||
unselectable = true
|
||||
},
|
||||
{
|
||||
title = TranslateCap("char_delete"),
|
||||
icon = "fa-solid fa-xmark",
|
||||
description = TranslateCap("char_delete_yes_description"),
|
||||
action = "delete",
|
||||
},
|
||||
{
|
||||
title = TranslateCap("return"),
|
||||
unselectable = false,
|
||||
icon = "fa-solid fa-arrow-left",
|
||||
description = TranslateCap("char_delete_no_description"),
|
||||
action = "return"
|
||||
},
|
||||
}
|
||||
|
||||
self.onUse = function(_, Action)
|
||||
if Action.action == "delete" then
|
||||
self:Close()
|
||||
|
||||
TriggerServerEvent("esx_multicharacter:DeleteCharacter", Multicharacter.spawned)
|
||||
Multicharacter.spawned = false
|
||||
elseif Action.action == "return" then
|
||||
self:CharacterOptions()
|
||||
end
|
||||
end
|
||||
|
||||
self:OpenMenu()
|
||||
end
|
||||
@@ -0,0 +1,292 @@
|
||||
---@diagnostic disable: duplicate-set-field
|
||||
Multicharacter = {}
|
||||
Multicharacter._index = Multicharacter
|
||||
Multicharacter.canRelog = true
|
||||
Multicharacter.Characters = {}
|
||||
Multicharacter.hidePlayers = false
|
||||
|
||||
function Multicharacter:SetupCamera()
|
||||
self.cam = CreateCam("DEFAULT_SCRIPTED_CAMERA", true)
|
||||
SetCamActive(self.cam, true)
|
||||
RenderScriptCams(true, false, 1, true, true)
|
||||
|
||||
local offset = GetOffsetFromEntityInWorldCoords(self.playerPed, 0, 1.7, 0.4)
|
||||
|
||||
SetCamCoord(self.cam, offset.x, offset.y, offset.z)
|
||||
PointCamAtCoord(self.cam, self.spawnCoords.x, self.spawnCoords.y, self.spawnCoords.z + 1.3)
|
||||
end
|
||||
|
||||
function Multicharacter:AwaitFadeIn()
|
||||
while IsScreenFadingIn() do
|
||||
Wait(200)
|
||||
end
|
||||
end
|
||||
|
||||
function Multicharacter:AwaitFadeOut()
|
||||
while IsScreenFadingOut() do
|
||||
Wait(200)
|
||||
end
|
||||
end
|
||||
|
||||
function Multicharacter:DestoryCamera()
|
||||
if self.cam then
|
||||
SetCamActive(self.cam, false)
|
||||
RenderScriptCams(false, false, 0, true, true)
|
||||
self.cam = nil
|
||||
end
|
||||
end
|
||||
|
||||
local HiddenCompents = {}
|
||||
|
||||
local function HideComponents(hide)
|
||||
local components = {11, 12, 21}
|
||||
for i = 1, #components do
|
||||
if hide then
|
||||
local size = GetHudComponentSize(components[i])
|
||||
if size.x > 0 or size.y > 0 then
|
||||
HiddenCompents[components[i]] = size
|
||||
SetHudComponentSize(components[i], 0.0, 0.0)
|
||||
end
|
||||
else
|
||||
if HiddenCompents[components[i]] then
|
||||
local size = HiddenCompents[components[i]]
|
||||
SetHudComponentSize(components[i], size.x, size.z)
|
||||
HiddenCompents[components[i]] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
DisplayRadar(not hide)
|
||||
end
|
||||
|
||||
function Multicharacter:HideHud(hide)
|
||||
self.hidePlayers = true
|
||||
|
||||
MumbleSetVolumeOverride(ESX.PlayerId, 0.0)
|
||||
HideComponents(hide)
|
||||
end
|
||||
|
||||
function Multicharacter:SetupCharacters()
|
||||
ESX.PlayerLoaded = false
|
||||
ESX.PlayerData = {}
|
||||
|
||||
self.spawned = false
|
||||
self.cam = CreateCam("DEFAULT_SCRIPTED_CAMERA", true)
|
||||
|
||||
self.playerPed = PlayerPedId()
|
||||
self.spawnCoords = Config.Spawn[ESX.Math.Random(1,#Config.Spawn)]
|
||||
|
||||
SetEntityCoords(self.playerPed, self.spawnCoords.x, self.spawnCoords.y, self.spawnCoords.z, true, false, false, false)
|
||||
SetEntityHeading(self.playerPed, self.spawnCoords.w)
|
||||
|
||||
SetPlayerControl(ESX.PlayerId, false, 0)
|
||||
self:SetupCamera()
|
||||
self:HideHud(true)
|
||||
|
||||
ShutdownLoadingScreen()
|
||||
ShutdownLoadingScreenNui()
|
||||
TriggerEvent("esx:loadingScreenOff")
|
||||
|
||||
SetTimeout(200, function()
|
||||
TriggerServerEvent("esx_multicharacter:SetupCharacters")
|
||||
end)
|
||||
end
|
||||
|
||||
function Multicharacter:GetSkin()
|
||||
local character = self.Characters[self.tempIndex]
|
||||
local skin = character and character.skin or Config.Default
|
||||
if not character.model then
|
||||
if character.sex == TranslateCap("female") then
|
||||
skin.sex = 1
|
||||
else
|
||||
skin.sex = 0
|
||||
end
|
||||
end
|
||||
return skin
|
||||
end
|
||||
|
||||
function Multicharacter:SpawnTempPed()
|
||||
self.canRelog = false
|
||||
local skin = self:GetSkin()
|
||||
ESX.SpawnPlayer(skin, self.spawnCoords, function()
|
||||
DoScreenFadeIn(600)
|
||||
self.playerPed = PlayerPedId()
|
||||
end)
|
||||
end
|
||||
|
||||
function Multicharacter:ChangeExistingPed()
|
||||
local newCharacter = self.Characters[self.tempIndex]
|
||||
local spawnedCharacter = self.Characters[self.spawned]
|
||||
|
||||
if spawnedCharacter and spawnedCharacter.model then
|
||||
local model = ESX.Streaming.RequestModel(newCharacter.model)
|
||||
if model then
|
||||
SetPlayerModel(ESX.playerId, newCharacter.model)
|
||||
SetModelAsNoLongerNeeded(newCharacter.model)
|
||||
end
|
||||
end
|
||||
|
||||
TriggerEvent("skinchanger:loadSkin", newCharacter.skin)
|
||||
end
|
||||
|
||||
function Multicharacter:PrepForUI()
|
||||
FreezeEntityPosition(self.playerPed, true)
|
||||
SetPedAoBlobRendering(self.playerPed, true)
|
||||
SetEntityAlpha(self.playerPed, 255, false)
|
||||
end
|
||||
|
||||
function Multicharacter:CloseUI()
|
||||
SendNUIMessage({
|
||||
action = "closeui",
|
||||
})
|
||||
end
|
||||
|
||||
function Multicharacter:SetupCharacter(index)
|
||||
local character = self.Characters[index]
|
||||
self.tempIndex = index
|
||||
|
||||
if not self.spawned then
|
||||
self:SpawnTempPed()
|
||||
elseif character and character.skin then
|
||||
self:ChangeExistingPed()
|
||||
end
|
||||
|
||||
self.spawned = index
|
||||
self.playerPed = PlayerPedId()
|
||||
self:PrepForUI()
|
||||
SendNUIMessage({
|
||||
action = "openui",
|
||||
character = character,
|
||||
})
|
||||
end
|
||||
|
||||
function Multicharacter:SetupUI(characters, slots)
|
||||
DoScreenFadeOut(0)
|
||||
|
||||
self.Characters = characters
|
||||
self.slots = slots
|
||||
|
||||
local Character = next(self.Characters)
|
||||
if not Character then
|
||||
self.canRelog = false
|
||||
|
||||
ESX.SpawnPlayer(Config.Default, self.spawnCoords, function()
|
||||
DoScreenFadeIn(400)
|
||||
self:AwaitFadeIn()
|
||||
|
||||
self.playerPed = PlayerPedId()
|
||||
SetPedAoBlobRendering(self.playerPed, false)
|
||||
SetEntityAlpha(self.playerPed, 0, false)
|
||||
|
||||
TriggerServerEvent("esx_multicharacter:CharacterChosen", 1, true)
|
||||
TriggerEvent("esx_identity:showRegisterIdentity")
|
||||
end)
|
||||
else
|
||||
Menu:SelectCharacter()
|
||||
end
|
||||
end
|
||||
|
||||
function Multicharacter:LoadSkinCreator(skin)
|
||||
TriggerEvent("skinchanger:loadSkin", skin, function()
|
||||
DoScreenFadeIn(600)
|
||||
SetPedAoBlobRendering(self.playerPed, true)
|
||||
ResetEntityAlpha(self.playerPed)
|
||||
|
||||
TriggerEvent("esx_skin:openSaveableMenu", function()
|
||||
Multicharacter.finishedCreation = true
|
||||
end, function()
|
||||
Multicharacter.finishedCreation = true
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
function Multicharacter:SetDefaultSkin(playerData)
|
||||
|
||||
local skin = Config.Default[playerData.sex]
|
||||
skin.sex = playerData.sex == "m" and 0 or 1
|
||||
|
||||
local model = skin.sex == 0 and `mp_m_freemode_01` or `mp_f_freemode_01`
|
||||
---@diagnostic disable-next-line: cast-local-type
|
||||
model = ESX.Streaming.RequestModel(model)
|
||||
|
||||
if not model then
|
||||
return
|
||||
end
|
||||
|
||||
SetPlayerModel(ESX.playerId, model)
|
||||
SetModelAsNoLongerNeeded(model)
|
||||
self.playerPed = PlayerPedId()
|
||||
|
||||
self:LoadSkinCreator(skin)
|
||||
end
|
||||
|
||||
function Multicharacter:Reset()
|
||||
self.Characters = {}
|
||||
self.tempIndex = nil
|
||||
self.playerPed = PlayerPedId()
|
||||
self.hidePlayers = false
|
||||
self.slots = nil
|
||||
|
||||
SetTimeout(10000, function()
|
||||
self.canRelog = true
|
||||
end)
|
||||
end
|
||||
|
||||
function Multicharacter:Spawn(spawn)
|
||||
|
||||
RequestCollisionAtCoord(spawn.x, spawn.y, spawn.z)
|
||||
|
||||
FreezeEntityPosition(self.playerPed, true)
|
||||
SetEntityCoordsNoOffset(self.playerPed, spawn.x, spawn.y, spawn.z, false, false, true)
|
||||
SetEntityHeading(self.playerPed, spawn.heading or spawn.w or 0.0)
|
||||
|
||||
while not HasCollisionLoadedAroundEntity(self.playerPed) do
|
||||
Wait(0)
|
||||
end
|
||||
|
||||
NetworkResurrectLocalPlayer(spawn.x, spawn.y, spawn.z, spawn.heading or spawn.w or 0.0, 0, true)
|
||||
end
|
||||
|
||||
function Multicharacter:PlayerLoaded(playerData, isNew, skin)
|
||||
DoScreenFadeOut(750)
|
||||
self:AwaitFadeOut()
|
||||
|
||||
local esxSpawns = ESX.GetConfig().DefaultSpawns
|
||||
local spawn = esxSpawns[math.random(1, #esxSpawns)]
|
||||
|
||||
if not isNew and playerData.coords then
|
||||
spawn = playerData.coords
|
||||
end
|
||||
|
||||
if isNew or not skin or #skin == 1 then
|
||||
self.finishedCreation = false
|
||||
self:SetDefaultSkin(playerData)
|
||||
|
||||
while not self.finishedCreation do
|
||||
Wait(200)
|
||||
end
|
||||
|
||||
DoScreenFadeOut(500)
|
||||
self:AwaitFadeOut()
|
||||
|
||||
elseif not isNew then
|
||||
TriggerEvent("skinchanger:loadSkin", skin or self.Characters[self.spawned].skin)
|
||||
end
|
||||
|
||||
self:DestoryCamera()
|
||||
self:Spawn(spawn)
|
||||
Wait(500)
|
||||
|
||||
self:HideHud(false)
|
||||
SetPlayerControl(ESX.playerId, true, 0)
|
||||
FreezeEntityPosition(self.playerPed, false)
|
||||
|
||||
DoScreenFadeIn(750)
|
||||
self:AwaitFadeIn()
|
||||
|
||||
TriggerServerEvent("esx:onPlayerSpawn")
|
||||
TriggerEvent("esx:onPlayerSpawn")
|
||||
TriggerEvent("playerSpawned")
|
||||
TriggerEvent("esx:restoreLoadout")
|
||||
|
||||
self:Reset()
|
||||
end
|
||||
@@ -30,8 +30,10 @@ else
|
||||
["m"] = {
|
||||
mom = 43,
|
||||
dad = 29,
|
||||
grandparents = 0,
|
||||
face_md_weight = 61,
|
||||
skin_md_weight = 27,
|
||||
face_g_weight = 0,
|
||||
nose_1 = -5,
|
||||
nose_2 = 6,
|
||||
nose_3 = 5,
|
||||
@@ -127,8 +129,10 @@ else
|
||||
["f"] = {
|
||||
mom = 28,
|
||||
dad = 6,
|
||||
grandparents = 0,
|
||||
face_md_weight = 63,
|
||||
skin_md_weight = 60,
|
||||
face_g_weight = 0,
|
||||
nose_1 = -10,
|
||||
nose_2 = 4,
|
||||
nose_3 = 5,
|
||||
|
||||
@@ -3,16 +3,23 @@ fx_version 'cerulean'
|
||||
game 'gta5'
|
||||
author 'ESX-Framework - Linden - KASH'
|
||||
description 'Allows players to have multiple characters on the same account.'
|
||||
version '1.10.10'
|
||||
version '1.10.9'
|
||||
lua54 'yes'
|
||||
|
||||
dependencies { 'es_extended', 'esx_context', 'esx_identity', 'esx_skin' }
|
||||
|
||||
shared_scripts { '@es_extended/imports.lua', '@es_extended/locale.lua', 'locales/*.lua', 'config.lua' }
|
||||
|
||||
server_scripts { '@oxmysql/lib/MySQL.lua', 'server/*.lua' }
|
||||
server_scripts {
|
||||
'@oxmysql/lib/MySQL.lua',
|
||||
'server/*.lua',
|
||||
'server/modules/*.lua'
|
||||
}
|
||||
|
||||
client_scripts { 'client/*.lua' }
|
||||
client_scripts {
|
||||
"client/modules/*.lua",
|
||||
'client/*.lua'
|
||||
}
|
||||
|
||||
ui_page { 'html/ui.html' }
|
||||
|
||||
|
||||
@@ -1,245 +1,41 @@
|
||||
local databaseConnected = false
|
||||
local databaseFound = false
|
||||
local oneSyncState = GetConvar("onesync", "off")
|
||||
Server = {}
|
||||
Server._index = Server
|
||||
|
||||
local DATABASE
|
||||
do
|
||||
local connectionString = GetConvar("mysql_connection_string", "")
|
||||
|
||||
if connectionString == "" then
|
||||
error(connectionString .. "\n^1Unable to start Multicharacter - unable to determine database from mysql_connection_string^0", 0)
|
||||
elseif connectionString:find("mysql://") then
|
||||
connectionString = connectionString:sub(9, -1)
|
||||
DATABASE = connectionString:sub(connectionString:find("/") + 1, -1):gsub("[%?]+[%w%p]*$", "")
|
||||
databaseFound = true
|
||||
else
|
||||
connectionString = { string.strsplit(";", connectionString) }
|
||||
|
||||
for i = 1, #connectionString do
|
||||
local v = connectionString[i]
|
||||
if v:match("database") then
|
||||
DATABASE = v:sub(10, #v)
|
||||
databaseFound = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local DB_TABLES = { users = "identifier" }
|
||||
local SLOTS = Config.Slots or 4
|
||||
local PREFIX = Config.Prefix or "char"
|
||||
local PRIMARY_IDENTIFIER = ESX.GetConfig().Identifier or GetConvar("sv_lan", "") == "true" and "ip" or "license"
|
||||
|
||||
local function GetIdentifier(source)
|
||||
local fxDk = GetConvarInt("sv_fxdkMode", 0)
|
||||
if fxDk == 1 then
|
||||
return "ESX-DEBUG-LICENCE"
|
||||
end
|
||||
|
||||
local identifier = GetPlayerIdentifierByType(source, PRIMARY_IDENTIFIER)
|
||||
return identifier and identifier:gsub(PRIMARY_IDENTIFIER .. ":", "")
|
||||
end
|
||||
|
||||
if next(ESX.Players) then
|
||||
local players = table.clone(ESX.Players)
|
||||
table.wipe(ESX.Players)
|
||||
for _, v in pairs(players) do
|
||||
ESX.Players[GetIdentifier(v.source)] = true
|
||||
end
|
||||
else
|
||||
ESX.Players = {}
|
||||
end
|
||||
|
||||
local function SetupCharacters(source)
|
||||
while not databaseConnected do
|
||||
Wait(100)
|
||||
end
|
||||
|
||||
local identifier = GetIdentifier(source)
|
||||
ESX.Players[identifier] = true
|
||||
|
||||
local slots = MySQL.scalar.await("SELECT slots FROM multicharacter_slots WHERE identifier = ?", { identifier }) or SLOTS
|
||||
identifier = PREFIX .. "%:" .. identifier
|
||||
|
||||
local result = MySQL.query.await("SELECT identifier, accounts, job, job_grade, firstname, lastname, dateofbirth, sex, skin, disabled FROM users WHERE identifier LIKE ? LIMIT ?", { identifier, slots })
|
||||
local characters
|
||||
|
||||
if result then
|
||||
local characterCount = #result
|
||||
characters = table.create(0, characterCount)
|
||||
|
||||
for i = 1, characterCount, 1 do
|
||||
local v = result[i]
|
||||
local job, grade = v.job or "unemployed", tostring(v.job_grade)
|
||||
|
||||
if ESX.Jobs[job] and ESX.Jobs[job].grades[grade] then
|
||||
if job ~= "unemployed" then
|
||||
grade = ESX.Jobs[job].grades[grade].label
|
||||
else
|
||||
grade = ""
|
||||
end
|
||||
job = ESX.Jobs[job].label
|
||||
end
|
||||
|
||||
local accounts = json.decode(v.accounts)
|
||||
local id = tonumber(string.sub(v.identifier, #PREFIX + 1, string.find(v.identifier, ":") - 1))
|
||||
|
||||
characters[id] = {
|
||||
id = id,
|
||||
bank = accounts.bank,
|
||||
money = accounts.money,
|
||||
job = job,
|
||||
job_grade = grade,
|
||||
firstname = v.firstname,
|
||||
lastname = v.lastname,
|
||||
dateofbirth = v.dateofbirth,
|
||||
skin = v.skin and json.decode(v.skin) or {},
|
||||
disabled = v.disabled,
|
||||
sex = v.sex == "m" and TranslateCap("male") or TranslateCap("female"),
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
TriggerClientEvent("esx_multicharacter:SetupUI", source, characters, slots)
|
||||
end
|
||||
Server.oneSync = GetConvar("onesync", "off")
|
||||
Server.slots = Config.Slots or 4
|
||||
Server.prefix = Config.Prefix or "char"
|
||||
Server.identifierType = ESX.GetConfig().Identifier or GetConvar("sv_lan", "") == "true" and "ip" or "license"
|
||||
|
||||
AddEventHandler("playerConnecting", function(_, _, deferrals)
|
||||
deferrals.defer()
|
||||
local identifier = GetIdentifier(source)
|
||||
if oneSyncState == "off" or oneSyncState == "legacy" then
|
||||
return deferrals.done(("[ESX] ESX Requires Onesync Infinity to work. This server currently has Onesync set to: %s"):format(oneSyncState))
|
||||
end
|
||||
|
||||
if not databaseFound then
|
||||
deferrals.done("[ESX] Cannot Find the servers mysql_connection_string. Please make sure it is correctly configured in your server.cfg")
|
||||
end
|
||||
|
||||
if not databaseConnected then
|
||||
deferrals.done("[ESX] OxMySQL Was Unable To Connect to your database. Please make sure it is turned on and correctly configured in your server.cfg")
|
||||
end
|
||||
|
||||
if identifier then
|
||||
if not ESX.GetConfig().EnableDebug then
|
||||
if ESX.Players[identifier] then
|
||||
deferrals.done(("[ESX Multicharacter] A player is already connected to the server with this identifier.\nYour identifier: %s:%s"):format(PRIMARY_IDENTIFIER, identifier))
|
||||
else
|
||||
deferrals.done()
|
||||
end
|
||||
else
|
||||
deferrals.done()
|
||||
end
|
||||
else
|
||||
deferrals.done(("Unable to retrieve player identifier.\nIdentifier type: %s"):format(PRIMARY_IDENTIFIER))
|
||||
end
|
||||
end)
|
||||
|
||||
local function DeleteCharacter(source, charid)
|
||||
local identifier = ("%s%s:%s"):format(PREFIX, charid, GetIdentifier(source))
|
||||
local query = "DELETE FROM `%s` WHERE %s = ?"
|
||||
local queries = {}
|
||||
local count = 0
|
||||
|
||||
for table, column in pairs(DB_TABLES) do
|
||||
count = count + 1
|
||||
queries[count] = { query = query:format(table, column), values = { identifier } }
|
||||
end
|
||||
|
||||
MySQL.transaction(queries, function(result)
|
||||
if result then
|
||||
print(("[^2INFO^7] Player ^5%s %s^7 has deleted a character ^5(%s)^7"):format(GetPlayerName(source), source, identifier))
|
||||
Wait(50)
|
||||
SetupCharacters(source)
|
||||
else
|
||||
error("\n^1Transaction failed while trying to delete " .. identifier .. "^0")
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
MySQL.ready(function()
|
||||
local length = 42 + #PREFIX
|
||||
local DB_COLUMNS = MySQL.query.await(('SELECT TABLE_NAME, COLUMN_NAME, CHARACTER_MAXIMUM_LENGTH FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = "%s" AND DATA_TYPE = "varchar" AND COLUMN_NAME IN (?)'):format(DATABASE, length), {
|
||||
{ "identifier", "owner" },
|
||||
})
|
||||
|
||||
if DB_COLUMNS then
|
||||
local columns = {}
|
||||
local count = 0
|
||||
|
||||
for i = 1, #DB_COLUMNS do
|
||||
local column = DB_COLUMNS[i]
|
||||
DB_TABLES[column.TABLE_NAME] = column.COLUMN_NAME
|
||||
|
||||
if column?.CHARACTER_MAXIMUM_LENGTH ~= length then
|
||||
count = count + 1
|
||||
columns[column.TABLE_NAME] = column.COLUMN_NAME
|
||||
end
|
||||
end
|
||||
|
||||
if next(columns) then
|
||||
local query = "ALTER TABLE `%s` MODIFY COLUMN `%s` VARCHAR(%s)"
|
||||
local queries = table.create(count, 0)
|
||||
|
||||
for k, v in pairs(columns) do
|
||||
queries[#queries + 1] = { query = query:format(k, v, length) }
|
||||
end
|
||||
|
||||
if MySQL.transaction.await(queries) then
|
||||
print(("[^2INFO^7] Updated ^5%s^7 columns to use ^5VARCHAR(%s)^7"):format(count, length))
|
||||
else
|
||||
print(("[^2INFO^7] Unable to update ^5%s^7 columns to use ^5VARCHAR(%s)^7"):format(count, length))
|
||||
end
|
||||
end
|
||||
|
||||
databaseConnected = true
|
||||
|
||||
while not next(ESX.Jobs) do
|
||||
Wait(500)
|
||||
ESX.Jobs = ESX.GetJobs()
|
||||
end
|
||||
end
|
||||
local source = source
|
||||
Server:OnConnecting(source, deferrals)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx_multicharacter:SetupCharacters", function()
|
||||
SetupCharacters(source)
|
||||
local source = source
|
||||
Multicharacter:SetupCharacters(source)
|
||||
end)
|
||||
|
||||
local awaitingRegistration = {}
|
||||
|
||||
RegisterNetEvent("esx_multicharacter:CharacterChosen", function(charid, isNew)
|
||||
if type(charid) == "number" and string.len(charid) <= 2 and type(isNew) == "boolean" then
|
||||
if isNew then
|
||||
awaitingRegistration[source] = charid
|
||||
else
|
||||
if not ESX.GetConfig().EnableDebug then
|
||||
local identifier = PREFIX .. charid .. ":" .. GetIdentifier(source)
|
||||
if ESX.GetPlayerFromIdentifier(identifier) then
|
||||
DropPlayer(source, "Your identifier " .. identifier .. " is already on the server!")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
TriggerEvent("esx:onPlayerJoined", source, PREFIX .. charid)
|
||||
ESX.Players[GetIdentifier(source)] = true
|
||||
end
|
||||
end
|
||||
local source = source
|
||||
Multicharacter:CharacterChosen(source, charid, isNew)
|
||||
end)
|
||||
|
||||
AddEventHandler("esx_identity:completedRegistration", function(source, data)
|
||||
TriggerEvent("esx:onPlayerJoined", source, PREFIX .. awaitingRegistration[source], data)
|
||||
awaitingRegistration[source] = nil
|
||||
ESX.Players[GetIdentifier(source)] = true
|
||||
Multicharacter:RegistrationComplete(source, data)
|
||||
end)
|
||||
|
||||
AddEventHandler("playerDropped", function()
|
||||
awaitingRegistration[source] = nil
|
||||
ESX.Players[GetIdentifier(source)] = nil
|
||||
local source = source
|
||||
Multicharacter:PlayerDropped(source)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx_multicharacter:DeleteCharacter", function(charid)
|
||||
if Config.CanDelete and type(charid) == "number" and string.len(charid) <= 2 then
|
||||
DeleteCharacter(source, charid)
|
||||
if not Config.CanDelete or type(charid) ~= "number" or string.len(charid) > 2 then
|
||||
return
|
||||
end
|
||||
local source = source
|
||||
Database:DeleteCharacter(source, charid)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx_multicharacter:relog", function()
|
||||
|
||||
+15
-33
@@ -2,10 +2,7 @@ ESX.RegisterCommand(
|
||||
"setslots",
|
||||
"admin",
|
||||
function(xPlayer, args)
|
||||
MySQL.insert("INSERT INTO `multicharacter_slots` (`identifier`, `slots`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `slots` = VALUES(`slots`)", {
|
||||
args.identifier,
|
||||
args.slots,
|
||||
})
|
||||
Database:SetSlots(args.identifier, args.slots)
|
||||
xPlayer.triggerEvent("esx:showNotification", TranslateCap("slotsadd", args.slots, args.identifier))
|
||||
end,
|
||||
true,
|
||||
@@ -23,14 +20,9 @@ ESX.RegisterCommand(
|
||||
"remslots",
|
||||
"admin",
|
||||
function(xPlayer, args)
|
||||
local slots = MySQL.scalar.await("SELECT `slots` FROM `multicharacter_slots` WHERE identifier = ?", {
|
||||
args.identifier,
|
||||
})
|
||||
local removed = Database:RemoveSlots(args.identifier)
|
||||
|
||||
if slots then
|
||||
MySQL.update("DELETE FROM `multicharacter_slots` WHERE `identifier` = ?", {
|
||||
args.identifier,
|
||||
})
|
||||
if removed then
|
||||
xPlayer.triggerEvent("esx:showNotification", TranslateCap("slotsrem", args.identifier))
|
||||
end
|
||||
end,
|
||||
@@ -48,17 +40,12 @@ ESX.RegisterCommand(
|
||||
"enablechar",
|
||||
"admin",
|
||||
function(xPlayer, args)
|
||||
local selectedCharacter = "char" .. args.charslot .. ":" .. args.identifier
|
||||
|
||||
MySQL.update("UPDATE `users` SET `disabled` = 0 WHERE identifier = ?", {
|
||||
selectedCharacter,
|
||||
}, function(result)
|
||||
if result > 0 then
|
||||
xPlayer.triggerEvent("esx:showNotification", TranslateCap("charenabled", args.charslot, args.identifier))
|
||||
else
|
||||
xPlayer.triggerEvent("esx:showNotification", TranslateCap("charnotfound", args.charslot, args.identifier))
|
||||
end
|
||||
end)
|
||||
local enabled = Database:EnableSlot(args.identifier, args.charslot)
|
||||
if enabled then
|
||||
xPlayer.triggerEvent("esx:showNotification", TranslateCap("charenabled", args.charslot, args.identifier))
|
||||
else
|
||||
xPlayer.triggerEvent("esx:showNotification", TranslateCap("charnotfound", args.charslot, args.identifier))
|
||||
end
|
||||
end,
|
||||
true,
|
||||
{
|
||||
@@ -75,17 +62,12 @@ ESX.RegisterCommand(
|
||||
"disablechar",
|
||||
"admin",
|
||||
function(xPlayer, args)
|
||||
local selectedCharacter = "char" .. args.charslot .. ":" .. args.identifier
|
||||
|
||||
MySQL.update("UPDATE `users` SET `disabled` = 1 WHERE identifier = ?", {
|
||||
selectedCharacter,
|
||||
}, function(result)
|
||||
if result > 0 then
|
||||
xPlayer.triggerEvent("esx:showNotification", TranslateCap("chardisabled", args.charslot, args.identifier))
|
||||
else
|
||||
xPlayer.triggerEvent("esx:showNotification", TranslateCap("charnotfound", args.charslot, args.identifier))
|
||||
end
|
||||
end)
|
||||
local disabled = Database:DisableSlot(args.identifier, args.charslot)
|
||||
if disabled then
|
||||
xPlayer.triggerEvent("esx:showNotification", TranslateCap("chardisabled", args.charslot, args.identifier))
|
||||
else
|
||||
xPlayer.triggerEvent("esx:showNotification", TranslateCap("charnotfound", args.charslot, args.identifier))
|
||||
end
|
||||
end,
|
||||
true,
|
||||
{
|
||||
@@ -0,0 +1,143 @@
|
||||
Database = {}
|
||||
Database.connected = false
|
||||
Database.found = false
|
||||
Database.tables = { users = "identifier" }
|
||||
|
||||
function Database:GetConnection()
|
||||
local connectionString = GetConvar("mysql_connection_string", "")
|
||||
|
||||
if connectionString == "" then
|
||||
error(connectionString .. "\n^1Unable to start Multicharacter - unable to determine database from mysql_connection_string^0", 0)
|
||||
elseif connectionString:find("mysql://") then
|
||||
connectionString = connectionString:sub(9, -1)
|
||||
|
||||
self.name = connectionString:sub(connectionString:find("/") + 1, -1):gsub("[%?]+[%w%p]*$", "")
|
||||
self.found = true
|
||||
else
|
||||
local connectionExtracted = { string.strsplit(";", connectionString) }
|
||||
|
||||
for i = 1, #connectionExtracted do
|
||||
local v = connectionExtracted[i]
|
||||
if v:match("database") then
|
||||
self.name = connectionString:sub(connectionString:find("/") + 1, -1):gsub("[%?]+[%w%p]*$", "")
|
||||
self.found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
MySQL.ready(function()
|
||||
local length = 42 + #Server.prefix
|
||||
local DB_COLUMNS = MySQL.query.await(('SELECT TABLE_NAME, COLUMN_NAME, CHARACTER_MAXIMUM_LENGTH FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = "%s" AND DATA_TYPE = "varchar" AND COLUMN_NAME IN (?)'):format(Database.name, length), {
|
||||
{ "identifier", "owner" },
|
||||
})
|
||||
|
||||
if DB_COLUMNS then
|
||||
local columns = {}
|
||||
local count = 0
|
||||
|
||||
for i = 1, #DB_COLUMNS do
|
||||
local column = DB_COLUMNS[i]
|
||||
Database.tables[column.TABLE_NAME] = column.COLUMN_NAME
|
||||
|
||||
if column?.CHARACTER_MAXIMUM_LENGTH < length then
|
||||
count = count + 1
|
||||
columns[column.TABLE_NAME] = column.COLUMN_NAME
|
||||
end
|
||||
end
|
||||
|
||||
if next(columns) then
|
||||
local query = "ALTER TABLE `%s` MODIFY COLUMN `%s` VARCHAR(%s)"
|
||||
local queries = table.create(count, 0)
|
||||
|
||||
for k, v in pairs(columns) do
|
||||
queries[#queries + 1] = { query = query:format(k, v, length) }
|
||||
end
|
||||
|
||||
if MySQL.transaction.await(queries) then
|
||||
print(("[^2INFO^7] Updated ^5%s^7 columns to use ^5VARCHAR(%s)^7"):format(count, length))
|
||||
else
|
||||
print(("[^2INFO^7] Unable to update ^5%s^7 columns to use ^5VARCHAR(%s)^7"):format(count, length))
|
||||
end
|
||||
end
|
||||
|
||||
Database.connected = true
|
||||
|
||||
while not next(ESX.Jobs) do
|
||||
Wait(500)
|
||||
ESX.Jobs = ESX.GetJobs()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
function Database:DeleteCharacter(source, charid)
|
||||
local identifier = ("%s%s:%s"):format(Server.prefix, charid, Server:GetIdentifier(source))
|
||||
local query = "DELETE FROM `%s` WHERE %s = ?"
|
||||
local queries = {}
|
||||
local count = 0
|
||||
|
||||
for table, column in pairs(self.tables) do
|
||||
count = count + 1
|
||||
queries[count] = { query = query:format(table, column), values = { identifier } }
|
||||
end
|
||||
|
||||
MySQL.transaction(queries, function(result)
|
||||
if result then
|
||||
local name = GetPlayerName(source)
|
||||
print(("[^2INFO^7] Player ^5%s %s^7 has deleted a character ^5(%s)^7"):format(name, source, identifier))
|
||||
Wait(50)
|
||||
Multicharacter:SetupCharacters(source)
|
||||
else
|
||||
error("\n^1Transaction failed while trying to delete " .. identifier .. "^0")
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function Database:GetPlayerSlots(identifier)
|
||||
return MySQL.scalar.await("SELECT slots FROM multicharacter_slots WHERE identifier = ?", { identifier }) or
|
||||
Server.slots
|
||||
end
|
||||
|
||||
function Database:GetPlayerInfo(identifier, slots)
|
||||
return MySQL.query.await(
|
||||
"SELECT identifier, accounts, job, job_grade, firstname, lastname, dateofbirth, sex, skin, disabled FROM users WHERE identifier LIKE ? LIMIT ?",
|
||||
{ identifier, slots })
|
||||
end
|
||||
|
||||
function Database:SetSlots(identifier, slots)
|
||||
MySQL.insert("INSERT INTO `multicharacter_slots` (`identifier`, `slots`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `slots` = VALUES(`slots`)", {
|
||||
identifier,
|
||||
slots,
|
||||
})
|
||||
end
|
||||
|
||||
function Database:RemoveSlots(identifier)
|
||||
local slots = MySQL.scalar.await("SELECT `slots` FROM `multicharacter_slots` WHERE identifier = ?", {
|
||||
identifier,
|
||||
})
|
||||
|
||||
if slots then
|
||||
MySQL.update("DELETE FROM `multicharacter_slots` WHERE `identifier` = ?", {
|
||||
identifier,
|
||||
})
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function Database:EnableSlot(identifier, slot)
|
||||
local selectedCharacter = ("char%s:%s"):format(slot, identifier)
|
||||
|
||||
local updated = MySQL.update.await("UPDATE `users` SET `disabled` = 0 WHERE identifier = ?", {selectedCharacter})
|
||||
return updated > 0
|
||||
end
|
||||
|
||||
function Database:DisableSlot(identifier, slot)
|
||||
local selectedCharacter = ("char%s:%s"):format(slot, identifier)
|
||||
|
||||
local updated = MySQL.update.await("UPDATE `users` SET `disabled` = 1 WHERE identifier = ?", {selectedCharacter})
|
||||
return updated > 0
|
||||
end
|
||||
|
||||
Database:GetConnection()
|
||||
@@ -0,0 +1,60 @@
|
||||
function Server:GetIdentifier(source)
|
||||
local fxDk = GetConvarInt("sv_fxdkMode", 0)
|
||||
if fxDk == 1 then
|
||||
return "ESX-DEBUG-LICENCE"
|
||||
end
|
||||
|
||||
local identifier = GetPlayerIdentifierByType(source, self.identifierType)
|
||||
return identifier and identifier:gsub(self.identifierType .. ":", "")
|
||||
end
|
||||
|
||||
function Server:ResetPlayers()
|
||||
if next(ESX.Players) then
|
||||
local players = table.clone(ESX.Players)
|
||||
table.wipe(ESX.Players)
|
||||
|
||||
for _, v in pairs(players) do
|
||||
ESX.Players[self:GetIdentifier(v.source)] = true
|
||||
end
|
||||
else
|
||||
ESX.Players = {}
|
||||
end
|
||||
end
|
||||
|
||||
function Server:OnConnecting(source, deferrals)
|
||||
deferrals.defer()
|
||||
Wait(0) -- Required
|
||||
local identifier = self:GetIdentifier(source)
|
||||
|
||||
if not GetPlayerTimeOnline then
|
||||
return deferrals.done(("[ESX Multicharacter] ESX Requires a minimum Artifact version of 9515, Please update your server."))
|
||||
end
|
||||
|
||||
if Server.oneSync == "off" or Server.oneSync == "legacy" then
|
||||
return deferrals.done(("[ESX Multicharacter] ESX Requires Onesync Infinity to work. This server currently has Onesync set to: %s"):format(Server.oneSync))
|
||||
end
|
||||
|
||||
if not Database.found then
|
||||
deferrals.done("[ESX Multicharacter] Cannot Find the servers mysql_connection_string. Please make sure it is correctly configured in your server.cfg")
|
||||
end
|
||||
|
||||
if not Database.connected then
|
||||
deferrals.done("[ESX Multicharacter] OxMySQL Was Unable To Connect to your database. Please make sure it is turned on and correctly configured in your server.cfg")
|
||||
end
|
||||
|
||||
if identifier then
|
||||
if not ESX.GetConfig().EnableDebug then
|
||||
if ESX.Players[identifier] then
|
||||
deferrals.done(("[ESX Multicharacter] A player is already connected to the server with this identifier.\nYour identifier: %s:%s"):format(Server.identifierType, identifier))
|
||||
else
|
||||
deferrals.done()
|
||||
end
|
||||
else
|
||||
deferrals.done()
|
||||
end
|
||||
else
|
||||
deferrals.done(("[ESX Multicharacter] Unable to retrieve player identifier.\nIdentifier type: %s"):format(Server.identifierType))
|
||||
end
|
||||
end
|
||||
|
||||
Server:ResetPlayers()
|
||||
@@ -0,0 +1,98 @@
|
||||
---@diagnostic disable: duplicate-set-field
|
||||
|
||||
Multicharacter = {}
|
||||
Multicharacter._index = Multicharacter
|
||||
Multicharacter.awaitingRegistration = {}
|
||||
|
||||
function Multicharacter:SetupCharacters(source)
|
||||
SetPlayerRoutingBucket(source, source)
|
||||
while not Database.connected do
|
||||
Wait(100)
|
||||
end
|
||||
|
||||
local identifier = Server:GetIdentifier(source)
|
||||
ESX.Players[identifier] = true
|
||||
|
||||
local slots = Database:GetPlayerSlots()
|
||||
identifier = Server.prefix .. "%:" .. identifier
|
||||
|
||||
local rawCharacters = Database:GetPlayerInfo(identifier, slots)
|
||||
local characters
|
||||
|
||||
if rawCharacters then
|
||||
local characterCount = #rawCharacters
|
||||
characters = table.create(0, characterCount)
|
||||
|
||||
for i = 1, characterCount, 1 do
|
||||
local v = rawCharacters[i]
|
||||
local job, grade = v.job or "unemployed", tostring(v.job_grade)
|
||||
|
||||
if ESX.Jobs[job] and ESX.Jobs[job].grades[grade] then
|
||||
if job ~= "unemployed" then
|
||||
grade = ESX.Jobs[job].grades[grade].label
|
||||
else
|
||||
grade = ""
|
||||
end
|
||||
job = ESX.Jobs[job].label
|
||||
end
|
||||
|
||||
local accounts = json.decode(v.accounts)
|
||||
local idString = string.sub(v.identifier, #Server.prefix + 1, string.find(v.identifier, ":") - 1)
|
||||
local id = tonumber(idString)
|
||||
if id then
|
||||
characters[id] = {
|
||||
id = id,
|
||||
bank = accounts.bank,
|
||||
money = accounts.money,
|
||||
job = job,
|
||||
job_grade = grade,
|
||||
firstname = v.firstname,
|
||||
lastname = v.lastname,
|
||||
dateofbirth = v.dateofbirth,
|
||||
skin = v.skin and json.decode(v.skin) or {},
|
||||
disabled = v.disabled,
|
||||
sex = v.sex == "m" and TranslateCap("male") or TranslateCap("female"),
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
TriggerClientEvent("esx_multicharacter:SetupUI", source, characters, slots)
|
||||
end
|
||||
|
||||
function Multicharacter:CharacterChosen(source, charid, isNew)
|
||||
if type(charid) ~= "number" or string.len(charid) > 2 or type(isNew) ~= "boolean" then
|
||||
return
|
||||
end
|
||||
|
||||
if isNew then
|
||||
self.awaitingRegistration[source] = charid
|
||||
else
|
||||
SetPlayerRoutingBucket(source, 0)
|
||||
if not ESX.GetConfig().EnableDebug then
|
||||
local identifier = ("%s%s:%s"):format(Server.prefix, charid, Server:GetIdentifier(source))
|
||||
|
||||
if ESX.GetPlayerFromIdentifier(identifier) then
|
||||
DropPlayer(source, "[ESX Multicharacter] Your identifier " .. identifier .. " is already on the server!")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
TriggerEvent("esx:onPlayerJoined", source, ("%s%s"):format(Server.prefix, charid))
|
||||
ESX.Players[Server:GetIdentifier(source)] = true
|
||||
end
|
||||
end
|
||||
|
||||
function Multicharacter:RegistrationComplete(source, data)
|
||||
local charId = self.awaitingRegistration[source]
|
||||
self.awaitingRegistration[source] = nil
|
||||
ESX.Players[Server:GetIdentifier(source)] = true
|
||||
|
||||
SetPlayerRoutingBucket(source, 0)
|
||||
TriggerEvent("esx:onPlayerJoined", source, ("%s%s"):format(Server.prefix, charId), data)
|
||||
end
|
||||
|
||||
function Multicharacter:PlayerDropped(player)
|
||||
self.awaitingRegistration[player] = nil
|
||||
ESX.Players[Server:GetIdentifier(player)] = nil
|
||||
end
|
||||
@@ -38,21 +38,21 @@ if Debug then
|
||||
RegisterCommand("oldnotify", function()
|
||||
---@diagnostic disable-next-line
|
||||
ESX.ShowNotification("No Waypoint Set.", true, false, 140)
|
||||
end)
|
||||
end, false)
|
||||
|
||||
RegisterCommand("notify", function()
|
||||
ESX.ShowNotification("You Recived ~br~ 1x ball~s~!", "success", 3000)
|
||||
end)
|
||||
end, false)
|
||||
|
||||
RegisterCommand("notify1", function()
|
||||
ESX.ShowNotification("Well ~g~Done~s~!", "success", 3000)
|
||||
end)
|
||||
end, false)
|
||||
|
||||
RegisterCommand("notify2", function()
|
||||
ESX.ShowNotification("Information Recived", "info", 3000)
|
||||
end)
|
||||
end, false)
|
||||
|
||||
RegisterCommand("notify3", function()
|
||||
ESX.ShowNotification("You Did something ~r~WRONG~s~!", "error", 3000)
|
||||
end)
|
||||
end, false)
|
||||
end
|
||||
|
||||
@@ -1,5 +1,37 @@
|
||||
---@class ProgressBarAnimation
|
||||
---@field public type "anim" | "Scenario"
|
||||
---@field public dict? string
|
||||
---@field public lib? string
|
||||
|
||||
---@class ProgressBarOptions
|
||||
---@field public animation ProgressBarAnimation
|
||||
---@field public FreezePlayer? boolean
|
||||
---@field public onFinish? function
|
||||
|
||||
local CurrentProgress = nil
|
||||
|
||||
local function startProcessing()
|
||||
while (CurrentProgress ~= nil) do
|
||||
if CurrentProgress.length > 0 then
|
||||
CurrentProgress.length = CurrentProgress.length - 1000
|
||||
else
|
||||
ClearPedTasks(ESX.PlayerData.ped)
|
||||
if CurrentProgress.FreezePlayer then
|
||||
FreezeEntityPosition(ESX.PlayerData.ped, false)
|
||||
end
|
||||
if CurrentProgress.onFinish then
|
||||
CurrentProgress.onFinish()
|
||||
end
|
||||
CurrentProgress = nil
|
||||
end
|
||||
Wait(1000)
|
||||
end
|
||||
end
|
||||
|
||||
---@param message string
|
||||
---@param length? number Timeout in milliseconds
|
||||
---@param Options? ProgressBarOptions
|
||||
---@return boolean Success
|
||||
local function Progressbar(message, length, Options)
|
||||
if CurrentProgress then
|
||||
return false
|
||||
@@ -24,21 +56,8 @@ local function Progressbar(message, length, Options)
|
||||
message = message or "ESX-Framework",
|
||||
})
|
||||
CurrentProgress.length = length or 3000
|
||||
while CurrentProgress ~= nil do
|
||||
if CurrentProgress.length > 0 then
|
||||
CurrentProgress.length = CurrentProgress.length - 1000
|
||||
else
|
||||
ClearPedTasks(ESX.PlayerData.ped)
|
||||
if CurrentProgress.FreezePlayer then
|
||||
FreezeEntityPosition(ESX.PlayerData.ped, false)
|
||||
end
|
||||
if CurrentProgress.onFinish then
|
||||
CurrentProgress.onFinish()
|
||||
end
|
||||
CurrentProgress = nil
|
||||
end
|
||||
Wait(1000)
|
||||
end
|
||||
CreateThread(startProcessing);
|
||||
return true;
|
||||
end
|
||||
|
||||
local function CancelProgressbar()
|
||||
|
||||
+64
-284
@@ -1,260 +1,51 @@
|
||||
local lastSkin, cam, isCameraActive
|
||||
local firstSpawn, zoomOffset, camOffset, heading = true, 0.0, 0.0, 90.0
|
||||
Skin = {}
|
||||
Skin._index = Skin
|
||||
|
||||
RegisterNetEvent("esx:playerLoaded")
|
||||
AddEventHandler("esx:playerLoaded", function(_, _, skin)
|
||||
TriggerServerEvent("esx_skin:setWeight", skin)
|
||||
end)
|
||||
Skin.firstSpawn = true
|
||||
Skin.zoomOffset = 0.0
|
||||
Skin.camOffset = 0.0
|
||||
Skin.heading = 90.0
|
||||
Skin.customPI = math.pi / 180.0
|
||||
|
||||
function OpenMenu(submitCb, cancelCb, restrict)
|
||||
local playerPed = PlayerPedId()
|
||||
|
||||
TriggerEvent("skinchanger:getSkin", function(skin)
|
||||
lastSkin = skin
|
||||
end)
|
||||
TriggerEvent("skinchanger:getData", function(components, maxVals)
|
||||
local elements = {}
|
||||
local _components = {}
|
||||
|
||||
-- Restrict menu
|
||||
if restrict == nil then
|
||||
for i = 1, #components, 1 do
|
||||
_components[i] = components[i]
|
||||
end
|
||||
else
|
||||
for i = 1, #components, 1 do
|
||||
local found = false
|
||||
|
||||
for j = 1, #restrict, 1 do
|
||||
if components[i].name == restrict[j] then
|
||||
found = true
|
||||
end
|
||||
end
|
||||
|
||||
if found then
|
||||
table.insert(_components, components[i])
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Insert elements
|
||||
for i = 1, #_components, 1 do
|
||||
local value = _components[i].value
|
||||
local componentId = _components[i].componentId
|
||||
|
||||
if componentId == 0 then
|
||||
value = GetPedPropIndex(playerPed, _components[i].componentId)
|
||||
end
|
||||
|
||||
local data = {
|
||||
label = _components[i].label,
|
||||
name = _components[i].name,
|
||||
value = value,
|
||||
min = _components[i].min,
|
||||
textureof = _components[i].textureof,
|
||||
zoomOffset = _components[i].zoomOffset,
|
||||
camOffset = _components[i].camOffset,
|
||||
type = "slider",
|
||||
}
|
||||
|
||||
for k, v in pairs(maxVals) do
|
||||
if k == _components[i].name then
|
||||
data.max = v
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(elements, data)
|
||||
end
|
||||
|
||||
CreateSkinCam()
|
||||
zoomOffset = _components[1].zoomOffset
|
||||
camOffset = _components[1].camOffset
|
||||
|
||||
ESX.UI.Menu.Open("default", GetCurrentResourceName(), "skin", {
|
||||
title = TranslateCap("skin_menu"),
|
||||
align = "bottom-left",
|
||||
elements = elements,
|
||||
}, function(data, menu)
|
||||
TriggerEvent("skinchanger:getSkin", function(skin)
|
||||
lastSkin = skin
|
||||
end)
|
||||
|
||||
submitCb(data, menu)
|
||||
DeleteSkinCam()
|
||||
end, function(data, menu)
|
||||
menu.close()
|
||||
DeleteSkinCam()
|
||||
TriggerEvent("skinchanger:loadSkin", lastSkin)
|
||||
|
||||
if cancelCb ~= nil then
|
||||
cancelCb(data, menu)
|
||||
end
|
||||
end, function(data, menu)
|
||||
local skin, tempMaxVals
|
||||
|
||||
TriggerEvent("skinchanger:getSkin", function(getSkin)
|
||||
skin = getSkin
|
||||
end)
|
||||
|
||||
zoomOffset = data.current.zoomOffset
|
||||
camOffset = data.current.camOffset
|
||||
|
||||
if skin[data.current.name] ~= data.current.value then
|
||||
-- Change skin element
|
||||
TriggerEvent("skinchanger:change", data.current.name, data.current.value)
|
||||
|
||||
-- Update max values
|
||||
TriggerEvent("skinchanger:getData", function(comp, max)
|
||||
tempMaxVals = max
|
||||
end)
|
||||
|
||||
local newData = {}
|
||||
|
||||
for i = 1, #elements, 1 do
|
||||
newData.max = tempMaxVals[elements[i].name]
|
||||
|
||||
if elements[i].textureof ~= nil and data.current.name == elements[i].textureof then
|
||||
newData.value = 0
|
||||
end
|
||||
|
||||
menu.update({ name = elements[i].name }, newData)
|
||||
end
|
||||
|
||||
menu.refresh()
|
||||
end
|
||||
end, function()
|
||||
DeleteSkinCam()
|
||||
end)
|
||||
end)
|
||||
function Skin:CalcualteHeading(angle)
|
||||
if angle > 360 then
|
||||
angle = angle - 360
|
||||
elseif angle < 0 then
|
||||
angle = angle + 360
|
||||
end
|
||||
angle = angle * self.customPI
|
||||
return angle
|
||||
end
|
||||
|
||||
function CreateSkinCam()
|
||||
if not DoesCamExist(cam) then
|
||||
cam = CreateCam("DEFAULT_SCRIPTED_CAMERA", true)
|
||||
end
|
||||
function Skin:CalcuatePosition(coords)
|
||||
|
||||
local playerPed = PlayerPedId()
|
||||
local angle = self.heading * self.customPI
|
||||
local theta = {
|
||||
x = math.cos(angle),
|
||||
y = math.sin(angle),
|
||||
}
|
||||
|
||||
SetCamActive(cam, true)
|
||||
RenderScriptCams(true, true, 500, true, true)
|
||||
local pos = {
|
||||
x = coords.x + (self.zoomOffset * theta.x),
|
||||
y = coords.y + (self.zoomOffset * theta.y),
|
||||
}
|
||||
|
||||
isCameraActive = true
|
||||
SetCamCoord(cam, GetEntityCoords(playerPed))
|
||||
SetCamRot(cam, 0.0, 0.0, 270.0, true)
|
||||
SetEntityHeading(playerPed, 0.0)
|
||||
end
|
||||
local angleToLook = self:CalcualteHeading(self.heading - 140.0)
|
||||
|
||||
local thetaToLook = {
|
||||
x = math.cos(angleToLook),
|
||||
y = math.sin(angleToLook),
|
||||
}
|
||||
|
||||
function DeleteSkinCam()
|
||||
isCameraActive = false
|
||||
SetCamActive(cam, false)
|
||||
RenderScriptCams(false, true, 500, true, true)
|
||||
cam = nil
|
||||
end
|
||||
|
||||
CreateThread(function()
|
||||
local customPI <const> = math.pi / 180.0
|
||||
while true do
|
||||
local sleep = 1500
|
||||
|
||||
if isCameraActive then
|
||||
sleep = 0
|
||||
DisableControlAction(2, 30, true)
|
||||
DisableControlAction(2, 31, true)
|
||||
DisableControlAction(2, 32, true)
|
||||
DisableControlAction(2, 33, true)
|
||||
DisableControlAction(2, 34, true)
|
||||
DisableControlAction(2, 35, true)
|
||||
DisableControlAction(0, 25, true) -- Input Aim
|
||||
DisableControlAction(0, 24, true) -- Input Attack
|
||||
|
||||
local playerPed = PlayerPedId()
|
||||
local coords = GetEntityCoords(playerPed)
|
||||
|
||||
local angle = heading * customPI
|
||||
local theta = {
|
||||
x = math.cos(angle),
|
||||
y = math.sin(angle),
|
||||
}
|
||||
|
||||
local pos = {
|
||||
x = coords.x + (zoomOffset * theta.x),
|
||||
y = coords.y + (zoomOffset * theta.y),
|
||||
}
|
||||
|
||||
local angleToLook = heading - 140.0
|
||||
if angleToLook > 360 then
|
||||
angleToLook = angleToLook - 360
|
||||
elseif angleToLook < 0 then
|
||||
angleToLook = angleToLook + 360
|
||||
end
|
||||
|
||||
angleToLook = angleToLook * customPI
|
||||
local thetaToLook = {
|
||||
x = math.cos(angleToLook),
|
||||
y = math.sin(angleToLook),
|
||||
}
|
||||
|
||||
local posToLook = {
|
||||
x = coords.x + (zoomOffset * thetaToLook.x),
|
||||
y = coords.y + (zoomOffset * thetaToLook.y),
|
||||
}
|
||||
|
||||
SetCamCoord(cam, pos.x, pos.y, coords.z + camOffset)
|
||||
PointCamAtCoord(cam, posToLook.x, posToLook.y, coords.z + camOffset)
|
||||
|
||||
ESX.ShowHelpNotification(TranslateCap("use_rotate_view"))
|
||||
end
|
||||
Wait(sleep)
|
||||
end
|
||||
end)
|
||||
|
||||
CreateThread(function()
|
||||
local angle = 90
|
||||
|
||||
while true do
|
||||
local sleep = 1500
|
||||
|
||||
if isCameraActive then
|
||||
sleep = 0
|
||||
if IsControlPressed(0, 209) then
|
||||
angle = angle - 1
|
||||
elseif IsControlPressed(0, 19) then
|
||||
angle = angle + 1
|
||||
end
|
||||
|
||||
if angle > 360 then
|
||||
angle = angle - 360
|
||||
elseif angle < 0 then
|
||||
angle = angle + 360
|
||||
end
|
||||
|
||||
heading = angle + 0.0
|
||||
end
|
||||
Wait(sleep)
|
||||
end
|
||||
end)
|
||||
|
||||
function OpenSaveableMenu(submitCb, cancelCb, restrict)
|
||||
TriggerEvent("skinchanger:getSkin", function(skin)
|
||||
lastSkin = skin
|
||||
end)
|
||||
|
||||
OpenMenu(function(data, menu)
|
||||
menu.close()
|
||||
DeleteSkinCam()
|
||||
|
||||
TriggerEvent("skinchanger:getSkin", function(skin)
|
||||
TriggerServerEvent("esx_skin:save", skin)
|
||||
|
||||
if submitCb ~= nil then
|
||||
submitCb(data, menu)
|
||||
end
|
||||
end)
|
||||
end, cancelCb, restrict)
|
||||
local posToLook = {
|
||||
x = coords.x + (self.zoomOffset * thetaToLook.x),
|
||||
y = coords.y + (self.zoomOffset * thetaToLook.y),
|
||||
}
|
||||
return pos, posToLook
|
||||
end
|
||||
|
||||
AddEventHandler("esx_skin:resetFirstSpawn", function()
|
||||
firstSpawn = true
|
||||
Skin.firstSpawn = true
|
||||
ESX.PlayerLoaded = false
|
||||
end)
|
||||
|
||||
@@ -264,57 +55,46 @@ AddEventHandler("esx_skin:playerRegistered", function()
|
||||
Wait(100)
|
||||
end
|
||||
|
||||
if firstSpawn then
|
||||
if Skin.firstSpawn then
|
||||
ESX.TriggerServerCallback("esx_skin:getPlayerSkin", function(skin)
|
||||
if skin == nil then
|
||||
TriggerEvent("skinchanger:loadSkin", { sex = 0 }, OpenSaveableMenu)
|
||||
Wait(100)
|
||||
exports["skinchanger"]:loadSkin({ sex = 0 })
|
||||
Menu:Saveable()
|
||||
else
|
||||
TriggerEvent("skinchanger:loadSkin", skin)
|
||||
Wait(100)
|
||||
exports["skinchanger"]:loadSkin(skin)
|
||||
end
|
||||
end)
|
||||
|
||||
firstSpawn = false
|
||||
Skin.firstSpawn = false
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx:playerLoaded")
|
||||
AddEventHandler("esx:playerLoaded", function()
|
||||
RegisterNetEvent("esx:playerLoaded", function(_, _, skin)
|
||||
ESX.PlayerLoaded = true
|
||||
TriggerServerEvent("esx_skin:setWeight", skin)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx_skin:openMenu", function(submitCb, cancelCb)
|
||||
Menu:Open(submitCb, cancelCb, nil)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx_skin:openRestrictedMenu", function(submitCb, cancelCb, restrict)
|
||||
Menu:Open(submitCb, cancelCb, nil)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx_skin:openSaveableMenu", function(submitCb, cancelCb)
|
||||
Menu:Saveable(submitCb, cancelCb, nil)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx_skin:openSaveableRestrictedMenu", function(submitCb, cancelCb, restrict)
|
||||
Menu:Saveable(submitCb, cancelCb, restrict)
|
||||
end)
|
||||
|
||||
AddEventHandler("esx_skin:getLastSkin", function(cb)
|
||||
cb(lastSkin)
|
||||
cb(Skin.Last)
|
||||
end)
|
||||
|
||||
AddEventHandler("esx_skin:setLastSkin", function(skin)
|
||||
lastSkin = skin
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx_skin:openMenu")
|
||||
AddEventHandler("esx_skin:openMenu", function(submitCb, cancelCb)
|
||||
OpenMenu(submitCb, cancelCb, nil)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx_skin:openRestrictedMenu")
|
||||
AddEventHandler("esx_skin:openRestrictedMenu", function(submitCb, cancelCb, restrict)
|
||||
OpenMenu(submitCb, cancelCb, restrict)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx_skin:openSaveableMenu")
|
||||
AddEventHandler("esx_skin:openSaveableMenu", function(submitCb, cancelCb)
|
||||
OpenSaveableMenu(submitCb, cancelCb, nil)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx_skin:openSaveableRestrictedMenu")
|
||||
AddEventHandler("esx_skin:openSaveableRestrictedMenu", function(submitCb, cancelCb, restrict)
|
||||
OpenSaveableMenu(submitCb, cancelCb, restrict)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("esx_skin:requestSaveSkin")
|
||||
AddEventHandler("esx_skin:requestSaveSkin", function()
|
||||
TriggerEvent("skinchanger:getSkin", function(skin)
|
||||
TriggerServerEvent("esx_skin:responseSaveSkin", skin)
|
||||
end)
|
||||
end)
|
||||
Skin.Last = skin
|
||||
end)
|
||||
@@ -0,0 +1,84 @@
|
||||
Camera = {}
|
||||
Camera._index = Camera
|
||||
|
||||
function Camera:AngleLoop()
|
||||
CreateThread(function()
|
||||
local angle = 90
|
||||
|
||||
while self.cam do
|
||||
if IsDisabledControlPressed(0, 44) then
|
||||
angle = angle - 1
|
||||
elseif IsDisabledControlPressed(0, 38) then
|
||||
angle = angle + 1
|
||||
end
|
||||
|
||||
if angle > 360 then
|
||||
angle = angle - 360
|
||||
elseif angle < 0 then
|
||||
angle = angle + 360
|
||||
end
|
||||
|
||||
Skin.heading = angle + 0.0
|
||||
Wait(0)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function Camera:DisableContols()
|
||||
local controls = {30, 31, 32, 33, 34, 35, 25, 24}
|
||||
for i=1 , #controls do
|
||||
DisableControlAction(2, controls[i], true)
|
||||
end
|
||||
end
|
||||
|
||||
function Camera:PositionLoop()
|
||||
CreateThread(function()
|
||||
while self.cam do
|
||||
self:DisableContols()
|
||||
|
||||
local ped = ESX.PlayerData.ped or PlayerPedId()
|
||||
local coords = GetEntityCoords(ped)
|
||||
|
||||
local pos, posToLook = Skin:CalcuatePosition(coords)
|
||||
|
||||
SetCamCoord(self.cam, pos.x, pos.y, coords.z + Skin.camOffset)
|
||||
PointCamAtCoord(self.cam, posToLook.x, posToLook.y, coords.z + Skin.camOffset)
|
||||
Wait(0)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function Camera:StartLoops()
|
||||
ESX.TextUI(Translate('use_rotate_view', "Q", "E"))
|
||||
self:AngleLoop()
|
||||
self:PositionLoop()
|
||||
end
|
||||
|
||||
function Camera:Create()
|
||||
if self.cam then
|
||||
return
|
||||
end
|
||||
|
||||
local playerPed = ESX.PlayerData.ped or PlayerPedId()
|
||||
local playerCoords = GetEntityCoords(playerPed)
|
||||
|
||||
self.cam = CreateCam("DEFAULT_SCRIPTED_CAMERA", true)
|
||||
SetCamCoord(self.cam, playerCoords.x, playerCoords.y, playerCoords.z + 1.0)
|
||||
SetCamRot(self.cam, 0.0, 0.0, 270.0, 2)
|
||||
SetEntityHeading(playerPed, 0.0)
|
||||
|
||||
SetCamActive(self.cam, true)
|
||||
RenderScriptCams(true, true, 500, true, true)
|
||||
self:StartLoops()
|
||||
end
|
||||
|
||||
function Camera:Destroy()
|
||||
if not self.cam then
|
||||
return
|
||||
end
|
||||
|
||||
ESX.HideUI()
|
||||
RenderScriptCams(false, true, 500, true, true)
|
||||
DestroyCam(self.cam, true)
|
||||
self.cam = nil
|
||||
end
|
||||
@@ -0,0 +1,144 @@
|
||||
Menu = {}
|
||||
Menu._index = Menu
|
||||
|
||||
function Menu:ESXMenu()
|
||||
ESX.UI.Menu.Open("default", GetCurrentResourceName(), "skin", {
|
||||
title = TranslateCap("skin_menu"),
|
||||
align = "bottom-left",
|
||||
elements = self.elements,
|
||||
}, function(data, menu)
|
||||
self:Submit(data, menu)
|
||||
end, function(data, menu)
|
||||
self:Cancel(data, menu)
|
||||
end, function(data, menu)
|
||||
self:Change(data, menu)
|
||||
end, function()
|
||||
self:Close()
|
||||
end)
|
||||
end
|
||||
|
||||
function Menu:Restrict()
|
||||
local _components = {}
|
||||
for i = 1, #self.components, 1 do
|
||||
local found = false
|
||||
|
||||
for j = 1, #self.restricted, 1 do
|
||||
if self.components[i].name == self.restricted[j] then
|
||||
found = true
|
||||
end
|
||||
end
|
||||
|
||||
if found then
|
||||
_components[#_components + 1] = self.components[i]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Menu:InsertElements()
|
||||
local playerPed = ESX.PlayerData.ped or PlayerPedId()
|
||||
|
||||
for i = 1, #self.components, 1 do
|
||||
local value = self.components[i].value
|
||||
local componentId = self.components[i].componentId
|
||||
|
||||
if componentId == 0 then
|
||||
value = GetPedPropIndex(playerPed, self.components[i].componentId)
|
||||
end
|
||||
|
||||
local data = self.components[i]
|
||||
data.value = value
|
||||
data.type = "slider"
|
||||
data.max = type(data.max) == "function" and data.max() or data.max
|
||||
|
||||
if not self.elements then
|
||||
self.elements = {}
|
||||
end
|
||||
self.elements[#self.elements + 1] = data
|
||||
end
|
||||
end
|
||||
|
||||
function Menu:Submit(data, menu)
|
||||
Skin.last = exports["skinchanger"]:GetSkin()
|
||||
self.submitCb(data, menu)
|
||||
Camera:Destroy()
|
||||
end
|
||||
|
||||
function Menu:Cancel(data, menu)
|
||||
menu.close()
|
||||
Camera:Destroy()
|
||||
TriggerEvent("skinchanger:loadSkin", Skin.last)
|
||||
|
||||
if self.cancelCb then
|
||||
self.cancelCb(data, menu)
|
||||
end
|
||||
end
|
||||
|
||||
function Menu:Change(data, menu)
|
||||
local skin = exports["skinchanger"]:GetSkin()
|
||||
|
||||
Skin.zoomOffset = data.current.zoomOffset
|
||||
Skin.camOffset = data.current.camOffset
|
||||
|
||||
if skin[data.current.name] ~= data.current.value then
|
||||
-- Change skin element
|
||||
TriggerEvent("skinchanger:change", data.current.name, data.current.value)
|
||||
|
||||
local newData = {}
|
||||
|
||||
for i = 1, #self.elements, 1 do
|
||||
local component = self.components[i]
|
||||
newData.max = type(component.max) == "function" and component.max() or component.max
|
||||
|
||||
if self.elements[i].textureof ~= nil and data.current.name == self.elements[i].textureof then
|
||||
newData.value = 0
|
||||
end
|
||||
|
||||
menu.update({ name = self.elements[i].name }, newData)
|
||||
end
|
||||
|
||||
self.elements = newData
|
||||
menu.refresh()
|
||||
end
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line: duplicate-set-field
|
||||
function Menu:Close()
|
||||
Camera:Destroy()
|
||||
end
|
||||
|
||||
function Menu:Open(submit, cancel, restrict)
|
||||
self.submitCb = submit
|
||||
self.cancelCb = cancel
|
||||
self.restricted = restrict
|
||||
Skin.last = exports["skinchanger"]:GetSkin()
|
||||
|
||||
self.components, self.maxValues = exports["skinchanger"]:GetData()
|
||||
|
||||
if restrict then
|
||||
self.components = self:Restrict()
|
||||
end
|
||||
|
||||
self:InsertElements()
|
||||
|
||||
self.zoomOffset = self.components[1].zoomOffset
|
||||
self.camOffset = self.components[1].camOffset
|
||||
Camera:Create()
|
||||
|
||||
self:ESXMenu()
|
||||
end
|
||||
|
||||
function Menu:Saveable(submitCb, cancelCb, restrict)
|
||||
Skin.last = exports["skinchanger"]:GetSkin()
|
||||
|
||||
self:Open(function(data, menu)
|
||||
menu.close()
|
||||
Camera:Destroy()
|
||||
|
||||
local skin = exports["skinchanger"]:GetSkin()
|
||||
TriggerServerEvent("esx_skin:save", skin)
|
||||
|
||||
if submitCb ~= nil then
|
||||
submitCb(data, menu)
|
||||
end
|
||||
end, cancelCb, restrict)
|
||||
end
|
||||
@@ -5,21 +5,21 @@ description 'Allows players to customise their character\'s appearance'
|
||||
version '1.10.10'
|
||||
lua54 'yes'
|
||||
|
||||
shared_script '@es_extended/imports.lua'
|
||||
shared_scripts {
|
||||
'@es_extended/locale.lua',
|
||||
'locales/*.lua',
|
||||
'@es_extended/imports.lua',
|
||||
'config.lua',
|
||||
}
|
||||
|
||||
server_scripts {
|
||||
'@oxmysql/lib/MySQL.lua',
|
||||
'@es_extended/locale.lua',
|
||||
'locales/*.lua',
|
||||
'config.lua',
|
||||
'server/main.lua'
|
||||
}
|
||||
|
||||
client_scripts {
|
||||
'@es_extended/locale.lua',
|
||||
'locales/*.lua',
|
||||
'config.lua',
|
||||
'client/main.lua'
|
||||
'client/main.lua',
|
||||
'client/modules/*.lua'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Locales["da"] = {
|
||||
["skin_menu"] = "Udseende Menu",
|
||||
["use_rotate_view"] = "brug ~INPUT_FRONTEND_LS~ og ~INPUT_CHARACTER_WHEEL~ for at dreje kameraet.",
|
||||
["use_rotate_view"] = "brug Q og E for at dreje kameraet.",
|
||||
["skin"] = "skift udseende",
|
||||
["saveskin"] = "gem udseende til en fil",
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Locales["de"] = {
|
||||
["skin_menu"] = "Skin Menü",
|
||||
["use_rotate_view"] = "Drücke ~INPUT_FRONTEND_LS~ oder ~INPUT_CHARACTER_WHEEL~ um deine Ansicht zu ändern.",
|
||||
["use_rotate_view"] = "Drücke Q oder E um deine Ansicht zu ändern.",
|
||||
["skin"] = "Aussehen ändern",
|
||||
["saveskin"] = "Aussehen abspeichern",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Locales["en"] = {
|
||||
["skin_menu"] = "Skin Menu",
|
||||
["use_rotate_view"] = "use ~INPUT_FRONTEND_LS~ and ~INPUT_CHARACTER_WHEEL~ to rotate the view.",
|
||||
["use_rotate_view"] = "use Q and E to rotate the view.",
|
||||
["skin"] = "change skin",
|
||||
["saveskin"] = "save skin to a file",
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Locales["es"] = {
|
||||
["skin_menu"] = "Menú de apariencia",
|
||||
["use_rotate_view"] = "Utiliza ~INPUT_FRONTEND_LS~ y ~INPUT_CHARACTER_WHEEL~ para rotar la vista.",
|
||||
["use_rotate_view"] = "Utiliza Q y E para rotar la vista.",
|
||||
["skin"] = "Cambiar aspecto",
|
||||
["saveskin"] = "Guarda tu aspecto actual",
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Locales["fi"] = {
|
||||
["skin_menu"] = "Ulkonäkö",
|
||||
["use_rotate_view"] = "Paina ~INPUT_FRONTEND_LS~ tai ~INPUT_CHARACTER_WHEEL~ liikutaaksesi kameraa.",
|
||||
["use_rotate_view"] = "Paina Q tai E liikutaaksesi kameraa.",
|
||||
["skin"] = "Muokkaa ulkonäköä",
|
||||
["saveskin"] = "Tallenna",
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Locales["fr"] = {
|
||||
["skin_menu"] = "menu de Skin",
|
||||
["use_rotate_view"] = "utilisez ~INPUT_FRONTEND_LS~ et ~INPUT_CHARACTER_WHEEL~ pour tourner la vue.",
|
||||
["use_rotate_view"] = "utilisez Q et E pour tourner la vue.",
|
||||
["skin"] = "changer de skin",
|
||||
["saveskin"] = "sauvegarder skin dans un fichier",
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Locales["he"] = {
|
||||
["skin_menu"] = "תפריט עור",
|
||||
["use_rotate_view"] = "השתמש ~INPUT_FRONTEND_LS~ ו ~INPUT_CHARACTER_WHEEL~ כדי לסובב את התצוגה.",
|
||||
["use_rotate_view"] = "השתמש Q ו E כדי לסובב את התצוגה.",
|
||||
["skin"] = "שנה עור",
|
||||
["saveskin"] = "שמור עור לקובץ",
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Locales["hu"] = {
|
||||
["skin_menu"] = "Kinézet Menü",
|
||||
["use_rotate_view"] = "Használd ~INPUT_FRONTEND_LS~ vagy ~INPUT_CHARACTER_WHEEL~ gombokat a forgatáshoz",
|
||||
["use_rotate_view"] = "Használd Q vagy E gombokat a forgatáshoz",
|
||||
["skin"] = "kinézet változtatása",
|
||||
["saveskin"] = "kinézet mentése fájlba",
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Locales["it"] = {
|
||||
["skin_menu"] = "Menu Skin",
|
||||
["use_rotate_view"] = "usa ~INPUT_FRONTEND_LS~ e ~INPUT_CHARACTER_WHEEL~ per ruotare la visuale.",
|
||||
["use_rotate_view"] = "usa Q e E per ruotare la visuale.",
|
||||
["skin"] = "cambia skin",
|
||||
["saveskin"] = "salvare la skin",
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Locales["nl"] = {
|
||||
["skin_menu"] = "Kleding Menu",
|
||||
["use_rotate_view"] = "gebruik ~INPUT_FRONTEND_LS~ en ~INPUT_CHARACTER_WHEEL~ om de camera te draaien.",
|
||||
["use_rotate_view"] = "gebruik Q en E om de camera te draaien.",
|
||||
["skin"] = "verander outfit",
|
||||
["saveskin"] = "sla outfit op in je kledingkast.",
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Locales["pl"] = {
|
||||
["skin_menu"] = "menu wyglądu",
|
||||
["use_rotate_view"] = "użyj ~INPUT_FRONTEND_LS~ i ~INPUT_CHARACTER_WHEEL~ aby obrócić ekran.",
|
||||
["use_rotate_view"] = "użyj Q i E aby obrócić ekran.",
|
||||
["skin"] = "zmień wygląd",
|
||||
["saveskin"] = "zapisz wygląd do pliku",
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Locales["pt"] = {
|
||||
["skin_menu"] = "Menu de Skin",
|
||||
["use_rotate_view"] = "Usa ~INPUT_FRONTEND_LS~ e ~INPUT_CHARACTER_WHEEL~ para rodar a câmara.",
|
||||
["use_rotate_view"] = "Usa Q e E para rodar a câmara.",
|
||||
["skin"] = "alterar skin",
|
||||
["saveskin"] = "salvar a skin num ficheiro",
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Locales["sl"] = {
|
||||
["skin_menu"] = "Oblacilni menu.",
|
||||
["use_rotate_view"] = "Pritisni ~INPUT_FRONTEND_LS~ in ~INPUT_CHARACTER_WHEEL~ da se obracas s pogledom.",
|
||||
["use_rotate_view"] = "Pritisni Q in E da se obracas s pogledom.",
|
||||
["skin"] = "Zamenjaj Skin.",
|
||||
["saveskin"] = "Shrani skin.",
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Locales["sr"] = {
|
||||
["skin_menu"] = "Skin Meni",
|
||||
["use_rotate_view"] = "koristi ~INPUT_FRONTEND_LS~ i ~INPUT_CHARACTER_WHEEL~ da rotiras kameru.",
|
||||
["use_rotate_view"] = "koristi Q i E da rotiras kameru.",
|
||||
["skin"] = "promeni skin",
|
||||
["saveskin"] = "sacuvaj skin u file",
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Locales["sv"] = {
|
||||
["skin_menu"] = "Skin Meny",
|
||||
["use_rotate_view"] = "Använd ~INPUT_FRONTEND_LS~ och ~INPUT_CHARACTER_WHEEL~ för att rotera.",
|
||||
["use_rotate_view"] = "Använd Q och E för att rotera.",
|
||||
["skin"] = "Ända utseende",
|
||||
["saveskin"] = "Spara",
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Locales["zh-cn"] = {
|
||||
["skin_menu"] = "皮肤选单",
|
||||
["use_rotate_view"] = "使用 ~INPUT_FRONTEND_LS~ 和 ~INPUT_CHARACTER_WHEEL~ 旋转镜头视角.",
|
||||
["use_rotate_view"] = "使用 Q 和 E 旋转镜头视角.",
|
||||
["skin"] = "更换皮肤数据",
|
||||
["saveskin"] = "保存皮肤数据",
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
RegisterServerEvent("esx_skin:save")
|
||||
AddEventHandler("esx_skin:save", function(skin)
|
||||
RegisterNetEvent("esx_skin:save", function(skin)
|
||||
local xPlayer = ESX.GetPlayerFromId(source)
|
||||
|
||||
if not ESX.GetConfig().OxInventory then
|
||||
@@ -19,8 +18,7 @@ AddEventHandler("esx_skin:save", function(skin)
|
||||
})
|
||||
end)
|
||||
|
||||
RegisterServerEvent("esx_skin:setWeight")
|
||||
AddEventHandler("esx_skin:setWeight", function(skin)
|
||||
RegisterServerEvent("esx_skin:setWeight", function(skin)
|
||||
local xPlayer = ESX.GetPlayerFromId(source)
|
||||
|
||||
if not ESX.GetConfig().OxInventory then
|
||||
@@ -35,28 +33,13 @@ AddEventHandler("esx_skin:setWeight", function(skin)
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterServerEvent("esx_skin:responseSaveSkin")
|
||||
AddEventHandler("esx_skin:responseSaveSkin", function(skin)
|
||||
local xPlayer = ESX.GetPlayerFromId(source)
|
||||
|
||||
if xPlayer.getGroup() == "admin" then
|
||||
local file = io.open("resources/[esx]/esx_skin/skins.txt", "a")
|
||||
|
||||
file:write(json.encode(skin) .. "\n\n")
|
||||
file:flush()
|
||||
file:close()
|
||||
else
|
||||
print(("[^2INFO^7] ^5%s^7 attempted saving skin to file"):format(xPlayer.getIdentifier()))
|
||||
end
|
||||
end)
|
||||
|
||||
ESX.RegisterServerCallback("esx_skin:getPlayerSkin", function(source, cb)
|
||||
local xPlayer = ESX.GetPlayerFromId(source)
|
||||
|
||||
MySQL.query("SELECT skin FROM users WHERE identifier = @identifier", {
|
||||
["@identifier"] = xPlayer.identifier,
|
||||
}, function(users)
|
||||
local user, skin = users[1]
|
||||
local user, skin = users[1], nil
|
||||
|
||||
local jobSkin = {
|
||||
skin_male = xPlayer.job.skin_male,
|
||||
@@ -74,7 +57,3 @@ end)
|
||||
ESX.RegisterCommand("skin", "admin", function(xPlayer)
|
||||
xPlayer.triggerEvent("esx_skin:openSaveableMenu")
|
||||
end, false, { help = TranslateCap("skin") })
|
||||
|
||||
ESX.RegisterCommand("skinsave", "admin", function(xPlayer)
|
||||
xPlayer.triggerEvent("esx_skin:requestSaveSkin")
|
||||
end, false, { help = TranslateCap("saveskin") })
|
||||
|
||||
@@ -29,17 +29,17 @@ RegisterNetEvent("ESX:HideUI", HideUI)
|
||||
if Debug then
|
||||
RegisterCommand("textui:error", function()
|
||||
ESX.TextUI("i ~r~love~s~ donuts", "error")
|
||||
end)
|
||||
end, false)
|
||||
|
||||
RegisterCommand("textui:success", function()
|
||||
ESX.TextUI("i ~g~love~s~ donuts", "success")
|
||||
end)
|
||||
end, false)
|
||||
|
||||
RegisterCommand("textui:info", function()
|
||||
ESX.TextUI("i ~b~love~s~ donuts", "info")
|
||||
end)
|
||||
end, false)
|
||||
|
||||
RegisterCommand("textui:hide", function()
|
||||
ESX.HideUI()
|
||||
end)
|
||||
end, false)
|
||||
end
|
||||
|
||||
+263
-373
@@ -1,342 +1,299 @@
|
||||
local LastSex = -1
|
||||
local LoadSkin = nil
|
||||
local LoadClothes = nil
|
||||
local Character = {}
|
||||
local Components = Config.Components
|
||||
|
||||
for i = 1, #Components, 1 do
|
||||
Character[Components[i].name] = Components[i].value
|
||||
end
|
||||
SkinChanger = {}
|
||||
SkinChanger._index = SkinChanger
|
||||
|
||||
function LoadDefaultModel(malePed, cb)
|
||||
local playerPed = PlayerPedId()
|
||||
local characterModel
|
||||
SkinChanger.lastSex = -1
|
||||
SkinChanger.character = {}
|
||||
SkinChanger.components = Config.Components
|
||||
|
||||
if malePed then
|
||||
characterModel = `mp_m_freemode_01`
|
||||
else
|
||||
characterModel = `mp_f_freemode_01`
|
||||
function SkinChanger:RequestModel(model)
|
||||
if type(model) == "string" then
|
||||
model = joaat(model)
|
||||
end
|
||||
|
||||
RequestModel(characterModel)
|
||||
RequestModel(model)
|
||||
|
||||
CreateThread(function()
|
||||
while not HasModelLoaded(characterModel) do
|
||||
RequestModel(characterModel)
|
||||
Wait(0)
|
||||
end
|
||||
while not HasModelLoaded(model) do
|
||||
Wait(200)
|
||||
end
|
||||
|
||||
if IsModelInCdimage(characterModel) and IsModelValid(characterModel) then
|
||||
SetPlayerModel(PlayerId(), characterModel)
|
||||
SetPedDefaultComponentVariation(playerPed)
|
||||
end
|
||||
|
||||
SetModelAsNoLongerNeeded(characterModel)
|
||||
|
||||
if cb ~= nil then
|
||||
cb()
|
||||
end
|
||||
|
||||
TriggerEvent("skinchanger:modelLoaded")
|
||||
end)
|
||||
return model
|
||||
end
|
||||
|
||||
function GetMaxVals()
|
||||
local playerPed = PlayerPedId()
|
||||
function SkinChanger:ModelLoaded()
|
||||
ClearPedProp(PlayerPedId(), 0)
|
||||
|
||||
local data = {
|
||||
sex = 1,
|
||||
mom = 45, -- numbers 21-41 and 45 are female (22 total)
|
||||
dad = 44, -- numbers 0-20 and 42-44 are male (24 total)
|
||||
face_md_weight = 100,
|
||||
skin_md_weight = 100,
|
||||
nose_1 = 10,
|
||||
nose_2 = 10,
|
||||
nose_3 = 10,
|
||||
nose_4 = 10,
|
||||
nose_5 = 10,
|
||||
nose_6 = 10,
|
||||
cheeks_1 = 10,
|
||||
cheeks_2 = 10,
|
||||
cheeks_3 = 10,
|
||||
lip_thickness = 10,
|
||||
jaw_1 = 10,
|
||||
jaw_2 = 10,
|
||||
chin_1 = 10,
|
||||
chin_2 = 10,
|
||||
chin_3 = 10,
|
||||
chin_4 = 10,
|
||||
neck_thickness = 10,
|
||||
age_1 = GetPedHeadOverlayNum(3) - 1,
|
||||
age_2 = 10,
|
||||
beard_1 = GetPedHeadOverlayNum(1) - 1,
|
||||
beard_2 = 10,
|
||||
beard_3 = GetNumHairColors() - 1,
|
||||
beard_4 = GetNumHairColors() - 1,
|
||||
hair_1 = GetNumberOfPedDrawableVariations(playerPed, 2) - 1,
|
||||
hair_2 = GetNumberOfPedTextureVariations(playerPed, 2, Character["hair_1"]) - 1,
|
||||
hair_color_1 = GetNumHairColors() - 1,
|
||||
hair_color_2 = GetNumHairColors() - 1,
|
||||
eye_color = 31,
|
||||
eye_squint = 10,
|
||||
eyebrows_1 = GetPedHeadOverlayNum(2) - 1,
|
||||
eyebrows_2 = 10,
|
||||
eyebrows_3 = GetNumHairColors() - 1,
|
||||
eyebrows_4 = GetNumHairColors() - 1,
|
||||
eyebrows_5 = 10,
|
||||
eyebrows_6 = 10,
|
||||
makeup_1 = GetPedHeadOverlayNum(4) - 1,
|
||||
makeup_2 = 10,
|
||||
makeup_3 = GetNumHairColors() - 1,
|
||||
makeup_4 = GetNumHairColors() - 1,
|
||||
lipstick_1 = GetPedHeadOverlayNum(8) - 1,
|
||||
lipstick_2 = 10,
|
||||
lipstick_3 = GetNumHairColors() - 1,
|
||||
lipstick_4 = GetNumHairColors() - 1,
|
||||
blemishes_1 = GetPedHeadOverlayNum(0) - 1,
|
||||
blemishes_2 = 10,
|
||||
blush_1 = GetPedHeadOverlayNum(5) - 1,
|
||||
blush_2 = 10,
|
||||
blush_3 = GetNumHairColors() - 1,
|
||||
complexion_1 = GetPedHeadOverlayNum(6) - 1,
|
||||
complexion_2 = 10,
|
||||
sun_1 = GetPedHeadOverlayNum(7) - 1,
|
||||
sun_2 = 10,
|
||||
moles_1 = GetPedHeadOverlayNum(9) - 1,
|
||||
moles_2 = 10,
|
||||
chest_1 = GetPedHeadOverlayNum(10) - 1,
|
||||
chest_2 = 10,
|
||||
chest_3 = GetNumHairColors() - 1,
|
||||
bodyb_1 = GetPedHeadOverlayNum(11) - 1,
|
||||
bodyb_2 = 10,
|
||||
bodyb_3 = GetPedHeadOverlayNum(12) - 1,
|
||||
bodyb_4 = 10,
|
||||
ears_1 = GetNumberOfPedPropDrawableVariations(playerPed, 2) - 1,
|
||||
ears_2 = GetNumberOfPedPropTextureVariations(playerPed, 2, Character["ears_1"] - 1),
|
||||
tshirt_1 = GetNumberOfPedDrawableVariations(playerPed, 8) - 1,
|
||||
tshirt_2 = GetNumberOfPedTextureVariations(playerPed, 8, Character["tshirt_1"]) - 1,
|
||||
torso_1 = GetNumberOfPedDrawableVariations(playerPed, 11) - 1,
|
||||
torso_2 = GetNumberOfPedTextureVariations(playerPed, 11, Character["torso_1"]) - 1,
|
||||
decals_1 = GetNumberOfPedDrawableVariations(playerPed, 10) - 1,
|
||||
decals_2 = GetNumberOfPedTextureVariations(playerPed, 10, Character["decals_1"]) - 1,
|
||||
arms = GetNumberOfPedDrawableVariations(playerPed, 3) - 1,
|
||||
arms_2 = 10,
|
||||
pants_1 = GetNumberOfPedDrawableVariations(playerPed, 4) - 1,
|
||||
pants_2 = GetNumberOfPedTextureVariations(playerPed, 4, Character["pants_1"]) - 1,
|
||||
shoes_1 = GetNumberOfPedDrawableVariations(playerPed, 6) - 1,
|
||||
shoes_2 = GetNumberOfPedTextureVariations(playerPed, 6, Character["shoes_1"]) - 1,
|
||||
mask_1 = GetNumberOfPedDrawableVariations(playerPed, 1) - 1,
|
||||
mask_2 = GetNumberOfPedTextureVariations(playerPed, 1, Character["mask_1"]) - 1,
|
||||
bproof_1 = GetNumberOfPedDrawableVariations(playerPed, 9) - 1,
|
||||
bproof_2 = GetNumberOfPedTextureVariations(playerPed, 9, Character["bproof_1"]) - 1,
|
||||
chain_1 = GetNumberOfPedDrawableVariations(playerPed, 7) - 1,
|
||||
chain_2 = GetNumberOfPedTextureVariations(playerPed, 7, Character["chain_1"]) - 1,
|
||||
bags_1 = GetNumberOfPedDrawableVariations(playerPed, 5) - 1,
|
||||
bags_2 = GetNumberOfPedTextureVariations(playerPed, 5, Character["bags_1"]) - 1,
|
||||
helmet_1 = GetNumberOfPedPropDrawableVariations(playerPed, 0) - 1,
|
||||
helmet_2 = GetNumberOfPedPropTextureVariations(playerPed, 0, Character["helmet_1"]) - 1,
|
||||
glasses_1 = GetNumberOfPedPropDrawableVariations(playerPed, 1) - 1,
|
||||
glasses_2 = GetNumberOfPedPropTextureVariations(playerPed, 1, Character["glasses_1"] - 1),
|
||||
watches_1 = GetNumberOfPedPropDrawableVariations(playerPed, 6) - 1,
|
||||
watches_2 = GetNumberOfPedPropTextureVariations(playerPed, 6, Character["watches_1"]) - 1,
|
||||
bracelets_1 = GetNumberOfPedPropDrawableVariations(playerPed, 7) - 1,
|
||||
bracelets_2 = GetNumberOfPedPropTextureVariations(playerPed, 7, Character["bracelets_1"] - 1),
|
||||
}
|
||||
if self.loadSkin ~= nil then
|
||||
self:ApplySkin(self.loadSkin)
|
||||
self.loadSkin = nil
|
||||
end
|
||||
|
||||
if self.loadClothes ~= nil then
|
||||
self:ApplySkin(self.loadClothes.playerSkin, self.loadClothes.clothesSkin)
|
||||
self.loadClothes = nil
|
||||
end
|
||||
end
|
||||
|
||||
function SkinChanger:DefaultModel(male, cb)
|
||||
local playerPed = PlayerPedId()
|
||||
local characterModel = male and `mp_m_freemode_01` or `mp_f_freemode_01`
|
||||
|
||||
if not IsModelInCdimage(characterModel) or not IsModelValid(characterModel) then
|
||||
return
|
||||
end
|
||||
local model = self:RequestModel(characterModel)
|
||||
|
||||
SetPlayerModel(PlayerId(), model)
|
||||
SetPedDefaultComponentVariation(playerPed)
|
||||
|
||||
SetModelAsNoLongerNeeded(model)
|
||||
|
||||
if cb then
|
||||
cb()
|
||||
end
|
||||
|
||||
self:ModelLoaded()
|
||||
end
|
||||
|
||||
function SkinChanger:MaxValues()
|
||||
local playerPed = PlayerPedId()
|
||||
local data = {}
|
||||
|
||||
for i = 1, #self.components, 1 do
|
||||
local component = self.components[i]
|
||||
local max = type(component.max) == "function" and component.max(playerPed, self.character) or component.max
|
||||
data[component.name] = max
|
||||
end
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
function ApplySkin(skin, clothes)
|
||||
local playerPed = PlayerPedId()
|
||||
function SkinChanger:DefaultFromSex(sex, cb)
|
||||
if sex == 0 then
|
||||
self:DefaultModel(true, cb)
|
||||
else
|
||||
self:DefaultModel(false, cb)
|
||||
end
|
||||
end
|
||||
|
||||
function SkinChanger:LoadSkin(skin, cb)
|
||||
local sex = skin["sex"]
|
||||
if sex ~= self.lastSex then
|
||||
self.loadSkin = skin
|
||||
self:DefaultFromSex(sex, cb)
|
||||
|
||||
self.lastSex = sex
|
||||
else
|
||||
self:ApplySkin(skin)
|
||||
|
||||
if cb ~= nil then
|
||||
cb()
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function SkinChanger:ValidClothes(key)
|
||||
local keys = {["sex"] = true, ["mom"] = true, ["dad"] = true, ["face_md_weight"] = true, ["skin_md_weight"] = true, ["nose_1"] = true, ["nose_2"] = true, ["nose_3"] = true, ["nose_4"] = true, ["nose_5"] = true, ["nose_6"] = true, ["cheeks_1"] = true, ["cheeks_2"] = true, ["cheeks_3"] = true, ["lip_thickness"] = true, ["jaw_1"] = true, ["jaw_2"] = true, ["chin_1"] = true, ["chin_2"] = true, ["chin_3"] = true, ["chin_4"] = true, ["neck_thickness"] = true, ["age_1"] = true, ["age_2"] = true, ["eye_color"] = true, ["eye_squint"] = true, ["beard_1"] = true, ["beard_2"] = true, ["beard_3"] = true, ["beard_4"] = true, ["hair_1"] = true, ["hair_2"] = true, ["hair_color_1"] = true, ["hair_color_2"] = true, ["eyebrows_1"] = true, ["eyebrows_2"] = true, ["eyebrows_3"] = true, ["eyebrows_4"] = true, ["eyebrows_5"] = true, ["eyebrows_6"] = true, ["makeup_1"] = true, ["makeup_2"] = true, ["makeup_3"] = true, ["makeup_4"] = true, ["lipstick_1"] = true, ["lipstick_2"] = true, ["lipstick_3"] = true, ["lipstick_4"] = true, ["blemishes_1"] = true, ["blemishes_2"] = true, ["blemishes_3"] = true, ["blush_1"] = true, ["blush_2"] = true, ["blush_3"] = true, ["complexion_1"] = true, ["complexion_2"] = true, ["sun_1"] = true, ["sun_2"] = true, ["moles_1"] = true, ["moles_2"] = true, ["chest_1"] = true, ["chest_2"] = true, ["chest_3"] = true, ["bodyb_1"] = true, ["bodyb_2"] = true, ["bodyb_3"] = true, ["bodyb_4"] = true}
|
||||
return keys[key] ~= nil
|
||||
end
|
||||
|
||||
local function Normalise(weight, divison)
|
||||
return (weight / divison) + 0.0
|
||||
end
|
||||
|
||||
function SkinChanger:SetHead()
|
||||
local face_weight = Normalise(self.character["face_md_weight"], 100)
|
||||
local skin_weight = Normalise(self.character["skin_md_weight"], 100)
|
||||
|
||||
if not self.character["face_g_weight"] then
|
||||
self.character["face_g_weight"] = 0
|
||||
end
|
||||
|
||||
local third_weight = Normalise(self.character["face_g_weight"], 100)
|
||||
|
||||
if not self.character["grandparents"] then
|
||||
self.character["grandparents"] = 0
|
||||
end
|
||||
|
||||
SetPedHeadBlendData(self.playerPed,
|
||||
self.character["mom"], self.character["dad"], self.character["grandparents"] , self.character["mom"],
|
||||
self.character["dad"], self.character["grandparents"], face_weight, skin_weight, third_weight, false)
|
||||
end
|
||||
|
||||
function SkinChanger:SetFace()
|
||||
local features = {"nose_1", "nose_2", "nose_3", "nose_4", "nose_5", "nose_6", "eyebrows_5", "eyebrows_6", "cheeks_1", "cheeks_2", "cheeks_3", "eye_squint", "lip_thickness", "jaw_1", "jaw_2", "chin_1", "chin_2", "chin_3", "chin_4", "neck_thickness"}
|
||||
for i = 1, #features, 1 do
|
||||
local feature = features[i]
|
||||
SetPedFaceFeature(self.playerPed, i - 1, Normalise(self.character[feature], 10))
|
||||
end
|
||||
SetPedEyeColor(self.playerPed, self.character["eye_color"]) -- Eyes color
|
||||
end
|
||||
|
||||
function SkinChanger:SetHeadOverlay()
|
||||
local features = {{"age_1", "age_2"}, {"blemishes_1", "blemishes_2"}, {"beard_1", "beard_2"}, {"eyebrows_1", "eyebrows_2"}, {"makeup_1", "makeup_2"}, {"lipstick_1", "lipstick_2"}, {"blush_1", "blush_2"}, {"complexion_1", "complexion_2"}, {"sun_1", "sun_2"}, {"moles_1", "moles_2"}, {"chest_1", "chest_2"}}
|
||||
for i = 1, #features, 1 do
|
||||
local feature = features[i]
|
||||
SetPedHeadOverlay(self.playerPed, i - 1, self.character[feature[1]], Normalise(self.character[feature[2]], 10))
|
||||
end
|
||||
end
|
||||
|
||||
function SkinChanger:SetHeadOverlayColour()
|
||||
local features = {{"beard_3", "beard_4"}, {"eyebrows_3", "eyebrows_4"}, {"makeup_3", "makeup_4"}, {"lipstick_3", "lipstick_4"}, {"blush_3", 0}, {"chest_3", 0}}
|
||||
for i = 1, #features, 1 do
|
||||
local feature = features[i]
|
||||
SetPedHeadOverlayColor(self.playerPed, i - 1, 1, self.character[feature[1]], self.character[feature[2]])
|
||||
end
|
||||
if self.character["bodyb_1"] == -1 then
|
||||
SetPedHeadOverlay(self.playerPed, 11, 255, (self.character["bodyb_2"] / 10) + 0.0) -- Body Blemishes + opacity
|
||||
else
|
||||
SetPedHeadOverlay(self.playerPed, 11, self.character["bodyb_1"], (self.character["bodyb_2"] / 10) + 0.0)
|
||||
end
|
||||
|
||||
if self.character["bodyb_3"] == -1 then
|
||||
SetPedHeadOverlay(self.playerPed, 12, 255, (self.character["bodyb_4"] / 10) + 0.0)
|
||||
else
|
||||
SetPedHeadOverlay(self.playerPed, 12, self.character["bodyb_3"], (self.character["bodyb_4"] / 10) + 0.0) -- Blemishes 'added body effect' + opacity
|
||||
end
|
||||
end
|
||||
|
||||
function SkinChanger:SetHair()
|
||||
SetPedComponentVariation(self.playerPed, 2, self.character["hair_1"], self.character["hair_2"], 2) -- Hair
|
||||
SetPedHairColor(self.playerPed, self.character["hair_color_1"], self.character["hair_color_2"]) -- Hair Color
|
||||
end
|
||||
|
||||
function SkinChanger:SetComponents()
|
||||
local components = {{"tshirt_1", "tshirt_2", 8}, {"torso_1", "torso_2", 11}, {"decals_1", "decals_2", 10}, {"arms", "arms_2", 3}, {"pants_1", "pants_2", 4}, {"shoes_1", "shoes_2", 6}, {"mask_1", "mask_2", 1}, {"bproof_1", "bproof_2", 9}, {"chain_1", "chain_2", 7}, {"bags_1", "bags_2", 5}}
|
||||
for i = 1, #components, 1 do
|
||||
local component = components[i]
|
||||
SetPedComponentVariation(self.playerPed, component[3], self.character[component[1]], self.character[component[2]], 2)
|
||||
end
|
||||
end
|
||||
|
||||
function SkinChanger:SetProps()
|
||||
local props = {{"helmet_1", "helmet_2", 0}, {"glasses_1", "glasses_2", 1}, {"ears_1", "ears_2", 2}, {"watches_1", "watches_2", 6}, {"bracelets_1", "bracelets_2", 7}}
|
||||
for i = 1, #props, 1 do
|
||||
local prop = props[i]
|
||||
local propVal = self.character[prop[1]]
|
||||
if propVal == -1 then
|
||||
ClearPedProp(self.playerPed, prop[3])
|
||||
else
|
||||
SetPedPropIndex(self.playerPed, prop[3], propVal, self.character[prop[2]], true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function SkinChanger:ApplySkin(skin, clothes)
|
||||
self.playerPed = PlayerPedId()
|
||||
|
||||
for k, v in pairs(skin) do
|
||||
Character[k] = v
|
||||
self.character[k] = v
|
||||
end
|
||||
|
||||
if clothes ~= nil then
|
||||
for k, v in pairs(clothes) do
|
||||
if
|
||||
k ~= "sex"
|
||||
and k ~= "mom"
|
||||
and k ~= "dad"
|
||||
and k ~= "face_md_weight"
|
||||
and k ~= "skin_md_weight"
|
||||
and k ~= "nose_1"
|
||||
and k ~= "nose_2"
|
||||
and k ~= "nose_3"
|
||||
and k ~= "nose_4"
|
||||
and k ~= "nose_5"
|
||||
and k ~= "nose_6"
|
||||
and k ~= "cheeks_1"
|
||||
and k ~= "cheeks_2"
|
||||
and k ~= "cheeks_3"
|
||||
and k ~= "lip_thickness"
|
||||
and k ~= "jaw_1"
|
||||
and k ~= "jaw_2"
|
||||
and k ~= "chin_1"
|
||||
and k ~= "chin_2"
|
||||
and k ~= "chin_3"
|
||||
and k ~= "chin_4"
|
||||
and k ~= "neck_thickness"
|
||||
and k ~= "age_1"
|
||||
and k ~= "age_2"
|
||||
and k ~= "eye_color"
|
||||
and k ~= "eye_squint"
|
||||
and k ~= "beard_1"
|
||||
and k ~= "beard_2"
|
||||
and k ~= "beard_3"
|
||||
and k ~= "beard_4"
|
||||
and k ~= "hair_1"
|
||||
and k ~= "hair_2"
|
||||
and k ~= "hair_color_1"
|
||||
and k ~= "hair_color_2"
|
||||
and k ~= "eyebrows_1"
|
||||
and k ~= "eyebrows_2"
|
||||
and k ~= "eyebrows_3"
|
||||
and k ~= "eyebrows_4"
|
||||
and k ~= "eyebrows_5"
|
||||
and k ~= "eyebrows_6"
|
||||
and k ~= "makeup_1"
|
||||
and k ~= "makeup_2"
|
||||
and k ~= "makeup_3"
|
||||
and k ~= "makeup_4"
|
||||
and k ~= "lipstick_1"
|
||||
and k ~= "lipstick_2"
|
||||
and k ~= "lipstick_3"
|
||||
and k ~= "lipstick_4"
|
||||
and k ~= "blemishes_1"
|
||||
and k ~= "blemishes_2"
|
||||
and k ~= "blemishes_3"
|
||||
and k ~= "blush_1"
|
||||
and k ~= "blush_2"
|
||||
and k ~= "blush_3"
|
||||
and k ~= "complexion_1"
|
||||
and k ~= "complexion_2"
|
||||
and k ~= "sun_1"
|
||||
and k ~= "sun_2"
|
||||
and k ~= "moles_1"
|
||||
and k ~= "moles_2"
|
||||
and k ~= "chest_1"
|
||||
and k ~= "chest_2"
|
||||
and k ~= "chest_3"
|
||||
and k ~= "bodyb_1"
|
||||
and k ~= "bodyb_2"
|
||||
and k ~= "bodyb_3"
|
||||
and k ~= "bodyb_4"
|
||||
then
|
||||
Character[k] = v
|
||||
if self:ValidClothes(k) then
|
||||
self.character[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local face_weight = (Character["face_md_weight"] / 100) + 0.0
|
||||
local skin_weight = (Character["skin_md_weight"] / 100) + 0.0
|
||||
SetPedHeadBlendData(playerPed, Character["mom"], Character["dad"], 0, Character["mom"], Character["dad"], 0, face_weight, skin_weight, 0.0, false)
|
||||
self:SetHead()
|
||||
self:SetHair()
|
||||
self:SetFace()
|
||||
self:SetHeadOverlay()
|
||||
self:SetHeadOverlayColour()
|
||||
self:SetComponents()
|
||||
self:SetProps()
|
||||
return true
|
||||
end
|
||||
|
||||
SetPedFaceFeature(playerPed, 0, (Character["nose_1"] / 10) + 0.0) -- Nose Width
|
||||
SetPedFaceFeature(playerPed, 1, (Character["nose_2"] / 10) + 0.0) -- Nose Peak Height
|
||||
SetPedFaceFeature(playerPed, 2, (Character["nose_3"] / 10) + 0.0) -- Nose Peak Length
|
||||
SetPedFaceFeature(playerPed, 3, (Character["nose_4"] / 10) + 0.0) -- Nose Bone Height
|
||||
SetPedFaceFeature(playerPed, 4, (Character["nose_5"] / 10) + 0.0) -- Nose Peak Lowering
|
||||
SetPedFaceFeature(playerPed, 5, (Character["nose_6"] / 10) + 0.0) -- Nose Bone Twist
|
||||
SetPedFaceFeature(playerPed, 6, (Character["eyebrows_5"] / 10) + 0.0) -- Eyebrow height
|
||||
SetPedFaceFeature(playerPed, 7, (Character["eyebrows_6"] / 10) + 0.0) -- Eyebrow depth
|
||||
SetPedFaceFeature(playerPed, 8, (Character["cheeks_1"] / 10) + 0.0) -- Cheekbones Height
|
||||
SetPedFaceFeature(playerPed, 9, (Character["cheeks_2"] / 10) + 0.0) -- Cheekbones Width
|
||||
SetPedFaceFeature(playerPed, 10, (Character["cheeks_3"] / 10) + 0.0) -- Cheeks Width
|
||||
SetPedFaceFeature(playerPed, 11, (Character["eye_squint"] / 10) + 0.0) -- Eyes squint
|
||||
SetPedFaceFeature(playerPed, 12, (Character["lip_thickness"] / 10) + 0.0) -- Lip Fullness
|
||||
SetPedFaceFeature(playerPed, 13, (Character["jaw_1"] / 10) + 0.0) -- Jaw Bone Width
|
||||
SetPedFaceFeature(playerPed, 14, (Character["jaw_2"] / 10) + 0.0) -- Jaw Bone Length
|
||||
SetPedFaceFeature(playerPed, 15, (Character["chin_1"] / 10) + 0.0) -- Chin Height
|
||||
SetPedFaceFeature(playerPed, 16, (Character["chin_2"] / 10) + 0.0) -- Chin Length
|
||||
SetPedFaceFeature(playerPed, 17, (Character["chin_3"] / 10) + 0.0) -- Chin Width
|
||||
SetPedFaceFeature(playerPed, 18, (Character["chin_4"] / 10) + 0.0) -- Chin Hole Size
|
||||
SetPedFaceFeature(playerPed, 19, (Character["neck_thickness"] / 10) + 0.0) -- Neck Thickness
|
||||
function SkinChanger:LoadClothes(playerSkin, clothesSkin)
|
||||
local sex = playerSkin["sex"]
|
||||
if sex ~= self.lastSex then
|
||||
self.loadClothes = {
|
||||
playerSkin = playerSkin,
|
||||
clothesSkin = clothesSkin,
|
||||
}
|
||||
|
||||
SetPedHairColor(playerPed, Character["hair_color_1"], Character["hair_color_2"]) -- Hair Color
|
||||
SetPedHeadOverlay(playerPed, 3, Character["age_1"], (Character["age_2"] / 10) + 0.0) -- Age + opacity
|
||||
SetPedHeadOverlay(playerPed, 0, Character["blemishes_1"], (Character["blemishes_2"] / 10) + 0.0) -- Blemishes + opacity
|
||||
SetPedHeadOverlay(playerPed, 1, Character["beard_1"], (Character["beard_2"] / 10) + 0.0) -- Beard + opacity
|
||||
SetPedEyeColor(playerPed, Character["eye_color"]) -- Eyes color
|
||||
SetPedHeadOverlay(playerPed, 2, Character["eyebrows_1"], (Character["eyebrows_2"] / 10) + 0.0) -- Eyebrows + opacity
|
||||
SetPedHeadOverlay(playerPed, 4, Character["makeup_1"], (Character["makeup_2"] / 10) + 0.0) -- Makeup + opacity
|
||||
SetPedHeadOverlay(playerPed, 8, Character["lipstick_1"], (Character["lipstick_2"] / 10) + 0.0) -- Lipstick + opacity
|
||||
SetPedComponentVariation(playerPed, 2, Character["hair_1"], Character["hair_2"], 2) -- Hair
|
||||
SetPedHeadOverlayColor(playerPed, 1, 1, Character["beard_3"], Character["beard_4"]) -- Beard Color
|
||||
SetPedHeadOverlayColor(playerPed, 2, 1, Character["eyebrows_3"], Character["eyebrows_4"]) -- Eyebrows Color
|
||||
SetPedHeadOverlayColor(playerPed, 4, 2, Character["makeup_3"], Character["makeup_4"]) -- Makeup Color
|
||||
SetPedHeadOverlayColor(playerPed, 8, 1, Character["lipstick_3"], Character["lipstick_4"]) -- Lipstick Color
|
||||
SetPedHeadOverlay(playerPed, 5, Character["blush_1"], (Character["blush_2"] / 10) + 0.0) -- Blush + opacity
|
||||
SetPedHeadOverlayColor(playerPed, 5, 2, Character["blush_3"]) -- Blush Color
|
||||
SetPedHeadOverlay(playerPed, 6, Character["complexion_1"], (Character["complexion_2"] / 10) + 0.0) -- Complexion + opacity
|
||||
SetPedHeadOverlay(playerPed, 7, Character["sun_1"], (Character["sun_2"] / 10) + 0.0) -- Sun Damage + opacity
|
||||
SetPedHeadOverlay(playerPed, 9, Character["moles_1"], (Character["moles_2"] / 10) + 0.0) -- Moles/Freckles + opacity
|
||||
SetPedHeadOverlay(playerPed, 10, Character["chest_1"], (Character["chest_2"] / 10) + 0.0) -- Chest Hair + opacity
|
||||
SetPedHeadOverlayColor(playerPed, 10, 1, Character["chest_3"]) -- Torso Color
|
||||
self:DefaultFromSex(sex)
|
||||
|
||||
if Character["bodyb_1"] == -1 then
|
||||
SetPedHeadOverlay(playerPed, 11, 255, (Character["bodyb_2"] / 10) + 0.0) -- Body Blemishes + opacity
|
||||
self.lastSex = sex
|
||||
return true
|
||||
else
|
||||
SetPedHeadOverlay(playerPed, 11, Character["bodyb_1"], (Character["bodyb_2"] / 10) + 0.0)
|
||||
end
|
||||
|
||||
if Character["bodyb_3"] == -1 then
|
||||
SetPedHeadOverlay(playerPed, 12, 255, (Character["bodyb_4"] / 10) + 0.0)
|
||||
else
|
||||
SetPedHeadOverlay(playerPed, 12, Character["bodyb_3"], (Character["bodyb_4"] / 10) + 0.0) -- Blemishes 'added body effect' + opacity
|
||||
end
|
||||
|
||||
if Character["ears_1"] == -1 then
|
||||
ClearPedProp(playerPed, 2)
|
||||
else
|
||||
SetPedPropIndex(playerPed, 2, Character["ears_1"], Character["ears_2"], 2) -- Ears Accessories
|
||||
end
|
||||
|
||||
SetPedComponentVariation(playerPed, 8, Character["tshirt_1"], Character["tshirt_2"], 2) -- Tshirt
|
||||
SetPedComponentVariation(playerPed, 11, Character["torso_1"], Character["torso_2"], 2) -- torso parts
|
||||
SetPedComponentVariation(playerPed, 3, Character["arms"], Character["arms_2"], 2) -- Arms
|
||||
SetPedComponentVariation(playerPed, 10, Character["decals_1"], Character["decals_2"], 2) -- decals
|
||||
SetPedComponentVariation(playerPed, 4, Character["pants_1"], Character["pants_2"], 2) -- pants
|
||||
SetPedComponentVariation(playerPed, 6, Character["shoes_1"], Character["shoes_2"], 2) -- shoes
|
||||
SetPedComponentVariation(playerPed, 1, Character["mask_1"], Character["mask_2"], 2) -- mask
|
||||
SetPedComponentVariation(playerPed, 9, Character["bproof_1"], Character["bproof_2"], 2) -- bulletproof
|
||||
SetPedComponentVariation(playerPed, 7, Character["chain_1"], Character["chain_2"], 2) -- chain
|
||||
SetPedComponentVariation(playerPed, 5, Character["bags_1"], Character["bags_2"], 2) -- Bag
|
||||
|
||||
if Character["helmet_1"] == -1 then
|
||||
ClearPedProp(playerPed, 0)
|
||||
else
|
||||
SetPedPropIndex(playerPed, 0, Character["helmet_1"], Character["helmet_2"], 2) -- Helmet
|
||||
end
|
||||
|
||||
if Character["glasses_1"] == -1 then
|
||||
ClearPedProp(playerPed, 1)
|
||||
else
|
||||
SetPedPropIndex(playerPed, 1, Character["glasses_1"], Character["glasses_2"], 2) -- Glasses
|
||||
end
|
||||
|
||||
if Character["watches_1"] == -1 then
|
||||
ClearPedProp(playerPed, 6)
|
||||
else
|
||||
SetPedPropIndex(playerPed, 6, Character["watches_1"], Character["watches_2"], 2) -- Watches
|
||||
end
|
||||
|
||||
if Character["bracelets_1"] == -1 then
|
||||
ClearPedProp(playerPed, 7)
|
||||
else
|
||||
SetPedPropIndex(playerPed, 7, Character["bracelets_1"], Character["bracelets_2"], 2) -- Bracelets
|
||||
return self:ApplySkin(playerSkin, clothesSkin)
|
||||
end
|
||||
end
|
||||
|
||||
function SkinChanger:Change(key, val)
|
||||
self.character[key] = val
|
||||
|
||||
if key == "sex" then
|
||||
self:LoadSkin(self.character)
|
||||
else
|
||||
self:ApplySkin(self.character)
|
||||
end
|
||||
end
|
||||
|
||||
function SkinChanger:Init()
|
||||
for i = 1, #self.components, 1 do
|
||||
local component = self.components[i]
|
||||
self.character[component.name] = component.value
|
||||
end
|
||||
end
|
||||
|
||||
--- exports
|
||||
|
||||
exports("GetSkin", function()
|
||||
return SkinChanger.character
|
||||
end)
|
||||
|
||||
exports("GetMaxVals", function()
|
||||
return SkinChanger:MaxValues()
|
||||
end)
|
||||
|
||||
exports("LoadSkin", function(skin)
|
||||
return SkinChanger:LoadSkin(skin)
|
||||
end)
|
||||
|
||||
exports("LoadClothes", function(playerSkin, clothesSkin)
|
||||
return SkinChanger:LoadClothes(playerSkin, clothesSkin)
|
||||
end)
|
||||
|
||||
exports("Change", function(key, val)
|
||||
return SkinChanger:Change(key, val)
|
||||
end)
|
||||
|
||||
--- Events
|
||||
|
||||
RegisterNetEvent("skinchanger:loadSkin", function(skin, cb)
|
||||
SkinChanger:LoadSkin(skin, cb)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("skinchanger:loadClothes", function(playerSkin, clothesSkin)
|
||||
SkinChanger:LoadClothes(playerSkin, clothesSkin)
|
||||
end)
|
||||
|
||||
AddEventHandler("skinchanger:getSkin", function(cb)
|
||||
cb(SkinChanger.character)
|
||||
end)
|
||||
|
||||
AddEventHandler("skinchanger:loadDefaultModel", function(loadMale, cb)
|
||||
LoadDefaultModel(loadMale, cb)
|
||||
SkinChanger:DefaultModel(loadMale, cb)
|
||||
end)
|
||||
|
||||
AddEventHandler("skinchanger:change", function(key, val)
|
||||
SkinChanger:Change(key, val)
|
||||
end)
|
||||
|
||||
AddEventHandler("skinchanger:modelLoaded", function()
|
||||
SkinChanger:ModelLoaded()
|
||||
end)
|
||||
|
||||
AddEventHandler("skinchanger:getData", function(cb)
|
||||
local components = json.decode(json.encode(Components))
|
||||
|
||||
for k, v in pairs(Character) do
|
||||
local components = SkinChanger.components
|
||||
for k, v in pairs(SkinChanger.character) do
|
||||
for i = 1, #components, 1 do
|
||||
if k == components[i].name then
|
||||
components[i].value = v
|
||||
@@ -344,74 +301,7 @@ AddEventHandler("skinchanger:getData", function(cb)
|
||||
end
|
||||
end
|
||||
|
||||
cb(components, GetMaxVals())
|
||||
cb(components, SkinChanger:MaxValues())
|
||||
end)
|
||||
|
||||
AddEventHandler("skinchanger:change", function(key, val)
|
||||
Character[key] = val
|
||||
|
||||
if key == "sex" then
|
||||
TriggerEvent("skinchanger:loadSkin", Character)
|
||||
else
|
||||
ApplySkin(Character)
|
||||
end
|
||||
end)
|
||||
|
||||
AddEventHandler("skinchanger:getSkin", function(cb)
|
||||
cb(Character)
|
||||
end)
|
||||
|
||||
AddEventHandler("skinchanger:modelLoaded", function()
|
||||
ClearPedProp(PlayerPedId(), 0)
|
||||
|
||||
if LoadSkin ~= nil then
|
||||
ApplySkin(LoadSkin)
|
||||
LoadSkin = nil
|
||||
end
|
||||
|
||||
if LoadClothes ~= nil then
|
||||
ApplySkin(LoadClothes.playerSkin, LoadClothes.clothesSkin)
|
||||
LoadClothes = nil
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("skinchanger:loadSkin")
|
||||
AddEventHandler("skinchanger:loadSkin", function(skin, cb)
|
||||
if skin["sex"] ~= LastSex then
|
||||
LoadSkin = skin
|
||||
|
||||
if skin["sex"] == 0 then
|
||||
TriggerEvent("skinchanger:loadDefaultModel", true, cb)
|
||||
else
|
||||
TriggerEvent("skinchanger:loadDefaultModel", false, cb)
|
||||
end
|
||||
else
|
||||
ApplySkin(skin)
|
||||
|
||||
if cb ~= nil then
|
||||
cb()
|
||||
end
|
||||
end
|
||||
|
||||
LastSex = skin["sex"]
|
||||
end)
|
||||
|
||||
RegisterNetEvent("skinchanger:loadClothes")
|
||||
AddEventHandler("skinchanger:loadClothes", function(playerSkin, clothesSkin)
|
||||
if playerSkin["sex"] ~= LastSex then
|
||||
LoadClothes = {
|
||||
playerSkin = playerSkin,
|
||||
clothesSkin = clothesSkin,
|
||||
}
|
||||
|
||||
if playerSkin["sex"] == 0 then
|
||||
TriggerEvent("skinchanger:loadDefaultModel", true)
|
||||
else
|
||||
TriggerEvent("skinchanger:loadDefaultModel", false)
|
||||
end
|
||||
else
|
||||
ApplySkin(playerSkin, clothesSkin)
|
||||
end
|
||||
|
||||
LastSex = playerSkin["sex"]
|
||||
end)
|
||||
SkinChanger:Init()
|
||||
@@ -10,14 +10,16 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
max = 1,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("mom"),
|
||||
name = "mom",
|
||||
value = 21,
|
||||
min = 21,
|
||||
value = 0,
|
||||
min = 0,
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
max = 45,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("dad"),
|
||||
@@ -26,6 +28,16 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
max = 44,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("grandparents"),
|
||||
name = "grandparents",
|
||||
value = 0,
|
||||
min = 0,
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
max = 45,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("resemblance"),
|
||||
@@ -34,6 +46,16 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
max = 100,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("resemblance_g"),
|
||||
name = "face_g_weight",
|
||||
value = 0,
|
||||
min = 0,
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
max = 100,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("skin_tone"),
|
||||
@@ -42,6 +64,7 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
max = 100,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("nose_1"),
|
||||
@@ -50,6 +73,7 @@ Config.Components = {
|
||||
min = -10,
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("nose_2"),
|
||||
@@ -58,6 +82,7 @@ Config.Components = {
|
||||
min = -10,
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("nose_3"),
|
||||
@@ -66,6 +91,7 @@ Config.Components = {
|
||||
min = -10,
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("nose_4"),
|
||||
@@ -74,6 +100,7 @@ Config.Components = {
|
||||
min = -10,
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("nose_5"),
|
||||
@@ -82,6 +109,7 @@ Config.Components = {
|
||||
min = -10,
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("nose_6"),
|
||||
@@ -90,6 +118,7 @@ Config.Components = {
|
||||
min = -10,
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("cheeks_1"),
|
||||
@@ -98,6 +127,7 @@ Config.Components = {
|
||||
min = -10,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("cheeks_2"),
|
||||
@@ -106,6 +136,7 @@ Config.Components = {
|
||||
min = -10,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("cheeks_3"),
|
||||
@@ -114,6 +145,7 @@ Config.Components = {
|
||||
min = -10,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("lip_fullness"),
|
||||
@@ -122,6 +154,7 @@ Config.Components = {
|
||||
min = -10,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("jaw_bone_width"),
|
||||
@@ -130,6 +163,7 @@ Config.Components = {
|
||||
min = -10,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("jaw_bone_length"),
|
||||
@@ -138,6 +172,7 @@ Config.Components = {
|
||||
min = -10,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("chin_height"),
|
||||
@@ -146,6 +181,7 @@ Config.Components = {
|
||||
min = -10,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("chin_length"),
|
||||
@@ -154,6 +190,7 @@ Config.Components = {
|
||||
min = -10,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("chin_width"),
|
||||
@@ -162,6 +199,7 @@ Config.Components = {
|
||||
min = -10,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("chin_hole"),
|
||||
@@ -170,6 +208,7 @@ Config.Components = {
|
||||
min = -10,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("neck_thickness"),
|
||||
@@ -178,6 +217,7 @@ Config.Components = {
|
||||
min = -10,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("hair_1"),
|
||||
@@ -186,6 +226,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
max = function(playerPed)
|
||||
return GetNumberOfPedDrawableVariations(playerPed, 2) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("hair_2"),
|
||||
@@ -194,6 +237,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
max = function(playerPed, Character)
|
||||
return GetNumberOfPedTextureVariations(playerPed, 2, Character["hair_1"]) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("hair_color_1"),
|
||||
@@ -202,6 +248,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
max = function()
|
||||
return GetNumHairColors() - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("hair_color_2"),
|
||||
@@ -210,6 +259,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
max = function()
|
||||
return GetNumHairColors() - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("tshirt_1"),
|
||||
@@ -219,6 +271,9 @@ Config.Components = {
|
||||
zoomOffset = 0.75,
|
||||
camOffset = 0.15,
|
||||
componentId = 8,
|
||||
max = function(playerPed)
|
||||
return GetNumberOfPedDrawableVariations(playerPed, 8) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("tshirt_2"),
|
||||
@@ -228,6 +283,9 @@ Config.Components = {
|
||||
zoomOffset = 0.75,
|
||||
camOffset = 0.15,
|
||||
textureof = "tshirt_1",
|
||||
max = function(playerPed, Character)
|
||||
return GetNumberOfPedTextureVariations(playerPed, 8, Character["tshirt_1"]) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("torso_1"),
|
||||
@@ -237,6 +295,9 @@ Config.Components = {
|
||||
zoomOffset = 0.75,
|
||||
camOffset = 0.15,
|
||||
componentId = 11,
|
||||
max = function(playerPed)
|
||||
return GetNumberOfPedDrawableVariations(playerPed, 11) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("torso_2"),
|
||||
@@ -246,6 +307,9 @@ Config.Components = {
|
||||
zoomOffset = 0.75,
|
||||
camOffset = 0.15,
|
||||
textureof = "torso_1",
|
||||
max = function(playerPed, Character)
|
||||
return GetNumberOfPedTextureVariations(playerPed, 11, Character["torso_1"]) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("decals_1"),
|
||||
@@ -255,6 +319,9 @@ Config.Components = {
|
||||
zoomOffset = 0.75,
|
||||
camOffset = 0.15,
|
||||
componentId = 10,
|
||||
max = function(playerPed)
|
||||
return GetNumberOfPedDrawableVariations(playerPed, 10) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("decals_2"),
|
||||
@@ -264,6 +331,9 @@ Config.Components = {
|
||||
zoomOffset = 0.75,
|
||||
camOffset = 0.15,
|
||||
textureof = "decals_1",
|
||||
max = function(playerPed, Character)
|
||||
return GetNumberOfPedTextureVariations(playerPed, 10, Character["decals_1"]) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("arms"),
|
||||
@@ -272,6 +342,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.75,
|
||||
camOffset = 0.15,
|
||||
max = function(playerPed)
|
||||
return GetNumberOfPedDrawableVariations(playerPed, 3) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("arms_2"),
|
||||
@@ -280,6 +353,7 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.75,
|
||||
camOffset = 0.15,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("pants_1"),
|
||||
@@ -289,6 +363,9 @@ Config.Components = {
|
||||
zoomOffset = 0.8,
|
||||
camOffset = -0.5,
|
||||
componentId = 4,
|
||||
max = function(playerPed)
|
||||
return GetNumberOfPedDrawableVariations(playerPed, 4) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("pants_2"),
|
||||
@@ -298,6 +375,9 @@ Config.Components = {
|
||||
zoomOffset = 0.8,
|
||||
camOffset = -0.5,
|
||||
textureof = "pants_1",
|
||||
max = function(playerPed, Character)
|
||||
return GetNumberOfPedTextureVariations(playerPed, 4, Character["pants_1"]) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("shoes_1"),
|
||||
@@ -307,6 +387,9 @@ Config.Components = {
|
||||
zoomOffset = 0.8,
|
||||
camOffset = -0.8,
|
||||
componentId = 6,
|
||||
max = function(playerPed)
|
||||
return GetNumberOfPedDrawableVariations(playerPed, 6) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("shoes_2"),
|
||||
@@ -316,6 +399,9 @@ Config.Components = {
|
||||
zoomOffset = 0.8,
|
||||
camOffset = -0.8,
|
||||
textureof = "shoes_1",
|
||||
max = function(playerPed, Character)
|
||||
return GetNumberOfPedTextureVariations(playerPed, 6, Character["shoes_1"]) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("mask_1"),
|
||||
@@ -325,6 +411,9 @@ Config.Components = {
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
componentId = 1,
|
||||
max = function(playerPed)
|
||||
return GetNumberOfPedDrawableVariations(playerPed, 1) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("mask_2"),
|
||||
@@ -334,6 +423,9 @@ Config.Components = {
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
textureof = "mask_1",
|
||||
max = function(playerPed, Character)
|
||||
return GetNumberOfPedTextureVariations(playerPed, 1, Character["mask_1"]) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("bproof_1"),
|
||||
@@ -343,6 +435,9 @@ Config.Components = {
|
||||
zoomOffset = 0.75,
|
||||
camOffset = 0.15,
|
||||
componentId = 9,
|
||||
max = function(playerPed)
|
||||
return GetNumberOfPedDrawableVariations(playerPed, 9) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("bproof_2"),
|
||||
@@ -352,6 +447,9 @@ Config.Components = {
|
||||
zoomOffset = 0.75,
|
||||
camOffset = 0.15,
|
||||
textureof = "bproof_1",
|
||||
max = function(playerPed, Character)
|
||||
return GetNumberOfPedTextureVariations(playerPed, 9, Character["bproof_1"]) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("chain_1"),
|
||||
@@ -361,6 +459,9 @@ Config.Components = {
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
componentId = 7,
|
||||
max = function(playerPed)
|
||||
return GetNumberOfPedDrawableVariations(playerPed, 7) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("chain_2"),
|
||||
@@ -370,6 +471,9 @@ Config.Components = {
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
textureof = "chain_1",
|
||||
max = function(playerPed, Character)
|
||||
return GetNumberOfPedTextureVariations(playerPed, 7, Character["chain_1"]) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("helmet_1"),
|
||||
@@ -379,6 +483,9 @@ Config.Components = {
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
componentId = 0,
|
||||
max = function(playerPed)
|
||||
return GetNumberOfPedPropDrawableVariations(playerPed, 0) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("helmet_2"),
|
||||
@@ -388,6 +495,9 @@ Config.Components = {
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
textureof = "helmet_1",
|
||||
max = function(playerPed, Character)
|
||||
return GetNumberOfPedPropTextureVariations(playerPed, 0, Character["helmet_1"]) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("glasses_1"),
|
||||
@@ -397,6 +507,9 @@ Config.Components = {
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
componentId = 1,
|
||||
max = function(playerPed)
|
||||
return GetNumberOfPedPropDrawableVariations(playerPed, 1) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("glasses_2"),
|
||||
@@ -406,6 +519,9 @@ Config.Components = {
|
||||
zoomOffset = 0.6,
|
||||
camOffset = 0.65,
|
||||
textureof = "glasses_1",
|
||||
max = function(playerPed, Character)
|
||||
return GetNumberOfPedPropTextureVariations(playerPed, 1, Character["glasses_1"]) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("watches_1"),
|
||||
@@ -415,6 +531,9 @@ Config.Components = {
|
||||
zoomOffset = 0.75,
|
||||
camOffset = 0.15,
|
||||
componentId = 6,
|
||||
max = function(playerPed)
|
||||
return GetNumberOfPedPropDrawableVariations(playerPed, 6) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("watches_2"),
|
||||
@@ -424,6 +543,9 @@ Config.Components = {
|
||||
zoomOffset = 0.75,
|
||||
camOffset = 0.15,
|
||||
textureof = "watches_1",
|
||||
max = function(playerPed, Character)
|
||||
return GetNumberOfPedPropTextureVariations(playerPed, 6, Character["watches_1"]) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("bracelets_1"),
|
||||
@@ -433,6 +555,9 @@ Config.Components = {
|
||||
zoomOffset = 0.75,
|
||||
camOffset = 0.15,
|
||||
componentId = 7,
|
||||
max = function(playerPed)
|
||||
return GetNumberOfPedPropDrawableVariations(playerPed, 7) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("bracelets_2"),
|
||||
@@ -442,6 +567,9 @@ Config.Components = {
|
||||
zoomOffset = 0.75,
|
||||
camOffset = 0.15,
|
||||
textureof = "bracelets_1",
|
||||
max = function(playerPed, Character)
|
||||
return GetNumberOfPedPropTextureVariations(playerPed, 7, Character["bracelets_1"]) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("bag"),
|
||||
@@ -451,6 +579,9 @@ Config.Components = {
|
||||
zoomOffset = 0.75,
|
||||
camOffset = 0.15,
|
||||
componentId = 5,
|
||||
max = function(playerPed)
|
||||
return GetNumberOfPedDrawableVariations(playerPed, 5) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("bag_color"),
|
||||
@@ -460,6 +591,9 @@ Config.Components = {
|
||||
zoomOffset = 0.75,
|
||||
camOffset = 0.15,
|
||||
textureof = "bags_1",
|
||||
max = function(playerPed, Character)
|
||||
return GetNumberOfPedTextureVariations(playerPed, 5, Character["bags_1"]) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("eye_color"),
|
||||
@@ -468,6 +602,7 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 31,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("eye_squint"),
|
||||
@@ -476,6 +611,7 @@ Config.Components = {
|
||||
min = -10,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("eyebrow_size"),
|
||||
@@ -484,6 +620,7 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("eyebrow_type"),
|
||||
@@ -492,6 +629,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = function()
|
||||
return GetPedHeadOverlayNum(2) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("eyebrow_color_1"),
|
||||
@@ -500,6 +640,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = function()
|
||||
return GetNumHairColors() - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("eyebrow_color_2"),
|
||||
@@ -508,6 +651,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = function()
|
||||
return GetNumHairColors() - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("eyebrow_height"),
|
||||
@@ -516,6 +662,7 @@ Config.Components = {
|
||||
min = -10,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("eyebrow_depth"),
|
||||
@@ -524,6 +671,7 @@ Config.Components = {
|
||||
min = -10,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("makeup_type"),
|
||||
@@ -532,6 +680,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = function()
|
||||
return GetPedHeadOverlayNum(4) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("makeup_thickness"),
|
||||
@@ -540,6 +691,7 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("makeup_color_1"),
|
||||
@@ -548,6 +700,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = function()
|
||||
return GetNumHairColors() - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("makeup_color_2"),
|
||||
@@ -556,6 +711,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = function()
|
||||
return GetNumHairColors() - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("lipstick_type"),
|
||||
@@ -564,6 +722,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = function()
|
||||
return GetPedHeadOverlayNum(8) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("lipstick_thickness"),
|
||||
@@ -572,6 +733,7 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("lipstick_color_1"),
|
||||
@@ -580,6 +742,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = function()
|
||||
return GetNumHairColors() - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("lipstick_color_2"),
|
||||
@@ -588,6 +753,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = function()
|
||||
return GetNumHairColors() - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("ear_accessories"),
|
||||
@@ -597,6 +765,9 @@ Config.Components = {
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
componentId = 2,
|
||||
max = function(playerPed)
|
||||
return GetNumberOfPedPropDrawableVariations(playerPed, 2) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("ear_accessories_color"),
|
||||
@@ -606,6 +777,9 @@ Config.Components = {
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
textureof = "ears_1",
|
||||
max = function(playerPed, Character)
|
||||
return GetNumberOfPedPropTextureVariations(playerPed, 2, Character["ears_1"]) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("chest_hair"),
|
||||
@@ -614,6 +788,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.75,
|
||||
camOffset = 0.15,
|
||||
max = function()
|
||||
return GetPedHeadOverlayNum(10) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("chest_hair_1"),
|
||||
@@ -622,6 +799,7 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.75,
|
||||
camOffset = 0.15,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("chest_color"),
|
||||
@@ -630,6 +808,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.75,
|
||||
camOffset = 0.15,
|
||||
max = function()
|
||||
return GetNumHairColors() - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("bodyb"),
|
||||
@@ -638,6 +819,9 @@ Config.Components = {
|
||||
min = -1,
|
||||
zoomOffset = 0.75,
|
||||
camOffset = 0.15,
|
||||
max = function()
|
||||
return GetPedHeadOverlayNum(11) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("bodyb_size"),
|
||||
@@ -646,6 +830,7 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.75,
|
||||
camOffset = 0.15,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("bodyb_extra"),
|
||||
@@ -654,6 +839,9 @@ Config.Components = {
|
||||
min = -1,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.15,
|
||||
max = function()
|
||||
return GetPedHeadOverlayNum(12) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("bodyb_extra_thickness"),
|
||||
@@ -662,6 +850,7 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.15,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("wrinkles"),
|
||||
@@ -670,6 +859,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = function()
|
||||
return GetPedHeadOverlayNum(3) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("wrinkle_thickness"),
|
||||
@@ -678,6 +870,7 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("blemishes"),
|
||||
@@ -686,6 +879,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = function()
|
||||
return GetPedHeadOverlayNum(0) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("blemishes_size"),
|
||||
@@ -694,6 +890,7 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("blush"),
|
||||
@@ -702,6 +899,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = function()
|
||||
return GetPedHeadOverlayNum(5) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("blush_1"),
|
||||
@@ -710,6 +910,7 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("blush_color"),
|
||||
@@ -718,6 +919,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = function()
|
||||
return GetNumHairColors() - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("complexion"),
|
||||
@@ -726,6 +930,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = function()
|
||||
return GetPedHeadOverlayNum(6) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("complexion_1"),
|
||||
@@ -734,6 +941,7 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("sun"),
|
||||
@@ -742,6 +950,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = function()
|
||||
return GetPedHeadOverlayNum(7) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("sun_1"),
|
||||
@@ -750,6 +961,7 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("freckles"),
|
||||
@@ -758,6 +970,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = function()
|
||||
return GetPedHeadOverlayNum(9) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("freckles_1"),
|
||||
@@ -766,6 +981,7 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("beard_type"),
|
||||
@@ -774,6 +990,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = function()
|
||||
return GetPedHeadOverlayNum(1) - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("beard_size"),
|
||||
@@ -782,6 +1001,7 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = 10,
|
||||
},
|
||||
{
|
||||
label = TranslateCap("beard_color_1"),
|
||||
@@ -790,6 +1010,9 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = function()
|
||||
return GetNumHairColors() - 1
|
||||
end
|
||||
},
|
||||
{
|
||||
label = TranslateCap("beard_color_2"),
|
||||
@@ -798,5 +1021,8 @@ Config.Components = {
|
||||
min = 0,
|
||||
zoomOffset = 0.4,
|
||||
camOffset = 0.65,
|
||||
max = function()
|
||||
return GetNumHairColors() - 1
|
||||
end
|
||||
},
|
||||
}
|
||||
|
||||
+3
-3
@@ -23,7 +23,9 @@ sv_maxclients 10 # Allow access to features usually locked behind a FiveM patr
|
||||
sets sv_projectName "Unconfigured | ESX Legacy Server"
|
||||
sets sv_projectDesc "The official recipe of the most popular FiveM RP framework, containing Jobs, Housing, Vehicles & more!"
|
||||
sets tags "default, esx, esx legacy, legacy, official, roleplay"
|
||||
sv_scriptHookAllowed 0
|
||||
|
||||
sv_enforceGameBuild 3095 #mp2023_02 - The Chop Shop
|
||||
set resources_useSystemChat true
|
||||
|
||||
set onesync on # Enable OneSync [REQUIRED FOR LEGACY 1.7.5 +]
|
||||
set mysql_connection_string "mysql://user:password@localhost/es_extended?waitForConnections=true&charset=utf8mb4"
|
||||
@@ -36,7 +38,6 @@ set mysql_ui true
|
||||
## These resources will start by default.
|
||||
ensure chat
|
||||
ensure spawnmanager
|
||||
ensure sessionmanager
|
||||
ensure hardcap
|
||||
|
||||
## Add system admins
|
||||
@@ -49,7 +50,6 @@ add_ace resource.es_extended command.remove_principal allow
|
||||
add_ace resource.es_extended command.stop allow
|
||||
|
||||
## Default & Standalone resources
|
||||
ensure chat-theme-gtao # Cool Chat theme
|
||||
ensure oxmysql
|
||||
|
||||
## ESX Legacy
|
||||
|
||||
Reference in New Issue
Block a user