0.0.1
This commit is contained in:
60
resources/[core]/esx_multicharacter/client/main.lua
Normal file
60
resources/[core]/esx_multicharacter/client/main.lua
Normal file
@@ -0,0 +1,60 @@
|
||||
local nuiReady = false
|
||||
|
||||
CreateThread(function()
|
||||
while not ESX.PlayerLoaded do
|
||||
Wait(100)
|
||||
|
||||
if NetworkIsPlayerActive(ESX.playerId) then
|
||||
ESX.DisableSpawnManager()
|
||||
DoScreenFadeOut(0)
|
||||
Multicharacter:SetupCharacters()
|
||||
break
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Events
|
||||
|
||||
ESX.SecureNetEvent("esx_multicharacter:SetupUI", function(data, slots)
|
||||
if not nuiReady then
|
||||
print('[WARNING]', 'NUI not ready yet, awaiting...')
|
||||
ESX.Await(function()
|
||||
return nuiReady == true
|
||||
end, 'NUI Failed to load after 10000ms', 10000)
|
||||
end
|
||||
Multicharacter:SetupUI(data, slots)
|
||||
end)
|
||||
|
||||
RegisterNetEvent('esx:playerLoaded', function(playerData, isNew, skin)
|
||||
Multicharacter:PlayerLoaded(playerData, isNew, skin)
|
||||
end)
|
||||
|
||||
ESX.SecureNetEvent('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)
|
||||
end
|
||||
end, false)
|
||||
end
|
||||
|
||||
RegisterNuiCallback('nuiReady', function(_, cb)
|
||||
nuiReady = true
|
||||
cb(1)
|
||||
end)
|
||||
77
resources/[core]/esx_multicharacter/client/modules/menu.lua
Normal file
77
resources/[core]/esx_multicharacter/client/modules/menu.lua
Normal file
@@ -0,0 +1,77 @@
|
||||
Menu = {}
|
||||
|
||||
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
|
||||
|
||||
local GetSlot = function()
|
||||
for i = 1, Multicharacter.slots do
|
||||
if not Multicharacter.Characters[i] then
|
||||
return i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Menu:NewCharacter()
|
||||
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:InitCharacter()
|
||||
local Characters = Multicharacter.Characters
|
||||
local Character = next(Characters)
|
||||
self:CheckModel(Characters[Character])
|
||||
|
||||
if not Multicharacter.spawned then
|
||||
Multicharacter:SetupCharacter(Character)
|
||||
end
|
||||
Wait(500)
|
||||
|
||||
SendNUIMessage({
|
||||
action = "ToggleMulticharacter",
|
||||
data = {
|
||||
show = true,
|
||||
Characters = Characters,
|
||||
CanDelete = Config.CanDelete,
|
||||
AllowedSlot = Multicharacter.slots,
|
||||
Locale = Locales[Config.Locale].UI,
|
||||
}
|
||||
})
|
||||
|
||||
SetNuiFocus(true, true)
|
||||
end
|
||||
|
||||
function Menu:SelectCharacter(index)
|
||||
Multicharacter:SetupCharacter(index)
|
||||
local playerPed = PlayerPedId()
|
||||
SetPedAoBlobRendering(playerPed, true)
|
||||
ResetEntityAlpha(playerPed)
|
||||
end
|
||||
|
||||
function Menu:PlayCharacter()
|
||||
Multicharacter:CloseUI()
|
||||
TriggerServerEvent("esx_multicharacter:CharacterChosen", Multicharacter.spawned, false)
|
||||
end
|
||||
|
||||
function Menu:DeleteCharacter()
|
||||
TriggerServerEvent("esx_multicharacter:DeleteCharacter", Multicharacter.spawned)
|
||||
Multicharacter.spawned = false
|
||||
end
|
||||
@@ -0,0 +1,282 @@
|
||||
---@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 + 0.7, offset.y , offset.z)
|
||||
PointCamAtCoord(self.cam, self.spawnCoords.x + 0.4, 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.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 not newCharacter.model then
|
||||
newCharacter.model = newCharacter.sex == TranslateCap("male") and `mp_m_freemode_01` or `mp_f_freemode_01`
|
||||
end
|
||||
|
||||
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 = "ToggleMulticharacter",
|
||||
data = {
|
||||
show = false
|
||||
}
|
||||
})
|
||||
SetNuiFocus(false, false)
|
||||
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()
|
||||
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:InitCharacter()
|
||||
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: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
|
||||
|
||||
skin = exports["skinchanger"]:GetSkin()
|
||||
DoScreenFadeOut(500)
|
||||
self:AwaitFadeOut()
|
||||
|
||||
elseif not isNew then
|
||||
TriggerEvent("skinchanger:loadSkin", skin or self.Characters[self.spawned].skin)
|
||||
end
|
||||
|
||||
self:DestoryCamera()
|
||||
ESX.SpawnPlayer(skin, spawn, function()
|
||||
self:HideHud(false)
|
||||
SetPlayerControl(ESX.playerId, true, 0)
|
||||
|
||||
self.playerPed = PlayerPedId()
|
||||
FreezeEntityPosition(self.playerPed, false)
|
||||
SetEntityCollision(self.playerPed, true, true)
|
||||
|
||||
DoScreenFadeIn(750)
|
||||
|
||||
self:AwaitFadeIn()
|
||||
|
||||
TriggerServerEvent("esx:onPlayerSpawn")
|
||||
TriggerEvent("esx:onPlayerSpawn")
|
||||
TriggerEvent("esx:restoreLoadout")
|
||||
|
||||
self:Reset()
|
||||
end)
|
||||
end
|
||||
23
resources/[core]/esx_multicharacter/client/modules/nui.lua
Normal file
23
resources/[core]/esx_multicharacter/client/modules/nui.lua
Normal file
@@ -0,0 +1,23 @@
|
||||
RegisterNuiCallback('SelectCharacter', function (data, cb)
|
||||
local selectedIndex = tonumber(data.id)
|
||||
|
||||
if selectedIndex then
|
||||
Menu:SelectCharacter(selectedIndex)
|
||||
end
|
||||
cb('ok')
|
||||
end)
|
||||
|
||||
RegisterNuiCallback('PlayCharacter', function (data, cb)
|
||||
Menu:PlayCharacter()
|
||||
cb('ok')
|
||||
end)
|
||||
|
||||
RegisterNuiCallback('DeleteCharacter', function (data, cb)
|
||||
Menu:DeleteCharacter()
|
||||
cb('ok')
|
||||
end)
|
||||
|
||||
RegisterNuiCallback('CreateCharacter', function (data, cb)
|
||||
Menu:NewCharacter()
|
||||
cb('ok')
|
||||
end)
|
||||
Reference in New Issue
Block a user