Merge branch 'dev' into job-duty

This commit is contained in:
Kasey Fitton
2024-10-31 22:08:48 +00:00
committed by GitHub
76 changed files with 3995 additions and 2038 deletions
+6
View File
@@ -0,0 +1,6 @@
{
"Lua.diagnostics.globals": [
"MySQL",
"GetPlayerTimeOnline"
]
}
-14
View File
@@ -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._
+30 -6
View File
@@ -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)
+279 -48
View File
@@ -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
View File
@@ -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)
+129 -106
View File
@@ -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()
+65 -23
View File
@@ -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)
+57 -31
View File
@@ -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
+25
View File
@@ -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(...)
+10
View File
@@ -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)
+12 -5
View File
@@ -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)
+39 -1
View File
@@ -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
+6 -6
View File
@@ -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
+6 -7
View File
@@ -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',
}
+1 -1
View File
@@ -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
+144 -108
View File
@@ -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
+17 -5
View File
@@ -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 -11
View File
@@ -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()
+77 -5
View File
@@ -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
+57 -51
View File
@@ -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)
+69 -26
View File
@@ -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,
+13 -7
View File
@@ -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
+53 -51
View File
@@ -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
+674
View File
@@ -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>.
+15
View File
@@ -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/.
+17
View File
@@ -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'
+74
View File
@@ -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");
})();
+103
View File
@@ -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;
}
+5 -5
View File
@@ -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
+13
View File
@@ -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)
+16 -3
View File
@@ -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
+10 -1
View File
@@ -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
+53 -353
View File
@@ -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
+4
View File
@@ -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,
+10 -3
View File
@@ -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' }
+19 -223
View File
@@ -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()
@@ -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
+5 -5
View File
@@ -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
+34 -15
View File
@@ -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
View File
@@ -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)
+84
View File
@@ -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
+144
View File
@@ -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
+8 -8
View File
@@ -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 -1
View File
@@ -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",
}
+2 -2
View File
@@ -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 -1
View File
@@ -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 -1
View 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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View 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 -1
View File
@@ -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"] = "保存皮肤数据",
}
+3 -24
View File
@@ -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") })
+4 -4
View File
@@ -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
View File
@@ -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()
+228 -2
View File
@@ -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
View File
@@ -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