This commit is contained in:
2026-07-07 21:08:52 +02:00
commit 4c20cfc716
2613 changed files with 318021 additions and 0 deletions

View File

@@ -0,0 +1,100 @@
Skin = {}
Skin._index = Skin
Skin.firstSpawn = true
Skin.zoomOffset = 0.0
Skin.camOffset = 0.0
Skin.heading = 90.0
Skin.customPI = math.pi / 180.0
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 Skin:CalcuatePosition(coords)
local angle = self.heading * self.customPI
local theta = {
x = math.cos(angle),
y = math.sin(angle),
}
local pos = {
x = coords.x + (self.zoomOffset * theta.x),
y = coords.y + (self.zoomOffset * theta.y),
}
local angleToLook = self:CalcualteHeading(self.heading - 140.0)
local thetaToLook = {
x = math.cos(angleToLook),
y = math.sin(angleToLook),
}
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()
Skin.firstSpawn = true
ESX.PlayerLoaded = false
end)
AddEventHandler("esx_skin:playerRegistered", function()
CreateThread(function()
while not ESX.PlayerLoaded do
Wait(100)
end
if Skin.firstSpawn then
ESX.TriggerServerCallback("esx_skin:getPlayerSkin", function(skin)
if skin == nil then
exports["skinchanger"]:LoadSkin({ sex = 0 })
Menu:Saveable()
else
exports["skinchanger"]:LoadSkin(skin)
end
end)
Skin.firstSpawn = false
end
end)
end)
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, restrict)
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(Skin.Last)
end)
AddEventHandler("esx_skin:setLastSkin", function(skin)
Skin.Last = skin
end)

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 = 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 = 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

View File

@@ -0,0 +1,156 @@
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
return _components
end
function Menu:InsertElements()
local playerPed = PlayerPedId()
self.elements = {}
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 = table.clone(self.components[i])
data.value = value
data.type = "slider"
data.max = self.maxValues[self.components[i].name]
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
return
end
-- Change skin element
exports["skinchanger"]:Change(data.current.name, data.current.value)
skin[data.current.name] = data.current.value
-- Texture variation changed. We don't have to update anything.
if data.current.textureof then
return
end
-- Texture changed. Update variation max value.
for i = 1, #self.elements, 1 do
local element = self.elements[i]
if element.textureof == data.current.name then
local component = self.components[i]
if ESX.IsFunctionReference(component.max) then
self.elements[i].max = component.max(PlayerPedId(), skin)
end
self.elements[i].value = 0
-- Change skin element
exports["skinchanger"]:Change(element.name, 0)
skin[element.name] = data.current.value
menu.update({ name = self.elements[i].name }, self.elements[i])
menu.refresh()
break
end
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()
Skin.zoomOffset = self.components[1].zoomOffset
Skin.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