From 698397fcb20d568953ae285012672ce3805a4ac3 Mon Sep 17 00:00:00 2001 From: Linden Date: Sun, 27 Jun 2021 12:55:54 +1000 Subject: [PATCH 01/60] tweak(client): Move SetupCharacters Server trigger; added locales --- client/main.lua | 7 +++---- locales/cs.lua | 2 ++ locales/de.lua | 2 ++ locales/en.lua | 2 ++ locales/pt.lua | 2 ++ locales/tr.lua | 2 ++ 6 files changed, 13 insertions(+), 4 deletions(-) diff --git a/client/main.lua b/client/main.lua index 0f9384dc..522522b8 100644 --- a/client/main.lua +++ b/client/main.lua @@ -8,8 +8,6 @@ if ESX.GetConfig().Multichar then while not GetResourceState('esx_menu_default') == 'started' do Citizen.Wait(0) end - Citizen.Wait(200) - TriggerServerEvent("esx_multicharacter:SetupCharacters") TriggerEvent("esx_multicharacter:SetupCharacters") break end @@ -38,6 +36,8 @@ if ESX.GetConfig().Multichar then ShutdownLoadingScreen() ShutdownLoadingScreenNui() TriggerEvent('esx:loadingScreenOff') + Citizen.Wait(200) + TriggerServerEvent("esx_multicharacter:SetupCharacters") end) StartLoop = function() @@ -97,7 +97,7 @@ if ESX.GetConfig().Multichar then if Characters[index] then local skin = Characters[index].skin or Config.Default if not Characters[index].model then - if Characters[index].sex == 'Female' then skin.sex = 1 else skin.sex = 0 end + if Characters[index].sex == _('female') then skin.sex = 1 else skin.sex = 0 end end TriggerEvent('skinchanger:loadSkin', skin) end @@ -315,7 +315,6 @@ if ESX.GetConfig().Multichar then AddEventHandler('esx:onPlayerLogout', function() DoScreenFadeOut(0) Spawned = false - TriggerServerEvent("esx_multicharacter:SetupCharacters") TriggerEvent("esx_multicharacter:SetupCharacters") TriggerEvent('esx_skin:resetFirstSpawn') end) diff --git a/locales/cs.lua b/locales/cs.lua index f4ffd7b5..a9734076 100644 --- a/locales/cs.lua +++ b/locales/cs.lua @@ -1,4 +1,6 @@ Locales['cs'] = { + ['male'] = "Muž", + ['female'] = "Žena", ['delete_label'] = "Vymazat %s %s?", ['select_char'] = "Zvolit Postavu", ['create_char'] = "Vytvořit novou postavu", diff --git a/locales/de.lua b/locales/de.lua index 8ffefc6b..654971af 100644 --- a/locales/de.lua +++ b/locales/de.lua @@ -1,4 +1,6 @@ Locales['de'] = { + ['male'] = "Männlich", + ['female'] = "Weiblich", ['delete_label'] = "Lösche %s %s?", ['select_char'] = "Charakter auswählen", ['create_char'] = "Neuen Charakter erstellen", diff --git a/locales/en.lua b/locales/en.lua index 72aa9cbe..f814d78a 100644 --- a/locales/en.lua +++ b/locales/en.lua @@ -1,4 +1,6 @@ Locales['en'] = { + ['male'] = "Male", + ['female'] = "Female", ['delete_label'] = "Delete %s %s?", ['select_char'] = "Select Character", ['create_char'] = "Create new character", diff --git a/locales/pt.lua b/locales/pt.lua index bd83f35e..04b828a3 100644 --- a/locales/pt.lua +++ b/locales/pt.lua @@ -1,4 +1,6 @@ Locales['pt'] = { + ['male'] = "Masculino", + ['female'] = "Feminino", ['delete_label'] = "Eliminar %s %s?", ['select_char'] = "Selecionar personagem", ['create_char'] = "Criar novo personagem", diff --git a/locales/tr.lua b/locales/tr.lua index 6fc35dab..8a3aa060 100644 --- a/locales/tr.lua +++ b/locales/tr.lua @@ -1,4 +1,6 @@ Locales['tr'] = { + ['male'] = "Erkek", + ['female'] = "Kadın", ['delete_label'] = "Sil %s %s?", ['select_char'] = "Karakter Seç", ['create_char'] = "Yeni Karakter Oluştur", From 7acd7bc5a04f52d4b84ab24b4149abe5c127f501 Mon Sep 17 00:00:00 2001 From: Linden Date: Sun, 27 Jun 2021 12:59:08 +1000 Subject: [PATCH 02/60] feat(server): Set VARCHAR(60) automatically; improvements to SQL.ready functions --- fxmanifest.lua | 14 +++++----- server/main.lua | 72 +++++++++++++++++++++---------------------------- 2 files changed, 38 insertions(+), 48 deletions(-) diff --git a/fxmanifest.lua b/fxmanifest.lua index 2916b49e..9ee95603 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -1,7 +1,7 @@ fx_version 'adamant' game 'gta5' description 'https://github.com/thelindat/esx_multicharacter' -version '1.1.1' +version '1.1.2' dependencies { 'es_extended', @@ -10,18 +10,20 @@ dependencies { 'esx_skin' } -shared_script 'config.lua' +shared_scripts { + '@es_extended/locale.lua', + 'locales/*.lua', + 'config.lua' +} server_scripts { '@es_extended/imports.lua', '@mysql-async/lib/MySQL.lua', - 'server/*.lua', + 'server/*.lua' } client_scripts { - '@es_extended/locale.lua', - 'client/main.lua', - 'locales/*.lua', + 'client/*.lua' } ui_page { diff --git a/server/main.lua b/server/main.lua index 09cf277f..c7fd07be 100644 --- a/server/main.lua +++ b/server/main.lua @@ -1,30 +1,20 @@ if not ESX then SetTimeout(3000, print('[^3WARNING^7] Unable to start Multicharacter - your version of ESX is not compatible ')) elseif ESX.GetConfig().Multichar == true then - local fetchCharacters = -1 - local IdentifierTables = {} - + local IdentifierTables, Fetch = {}, -1 + -- enter the name of your database here Config.Database = 'es_extended' + -- enter a prefix to prepend to all user identifiers (keep it short) Config.Prefix = 'char' - - RegisterCommand('varchar', function(source) - if source == 0 then - if next(IdentifierTables) == nil then print(' Unable to update your tables - have you defined the correct database?') return end - for _, itable in pairs(IdentifierTables) do - print('Setting `'..itable.table..'` column `'..itable.column..'` to VARCHAR(60)') - MySQL.Sync.execute("ALTER TABLE "..itable.table.." MODIFY COLUMN "..itable.column.." VARCHAR(60)", {}) - end - Citizen.Wait(100) - print(' Updated your tables to use the correct field length - you should remove this command') - end - end, true) - SetupCharacters = function(playerId) - while fetchCharacters == -1 do Citizen.Wait(100) end + local SetupCharacters = function(playerId) + while Fetch == -1 do Citizen.Wait(500) end local identifier = Config.Prefix..'%:'..ESX.GetIdentifier(playerId) - MySQL.Async.fetchAll(fetchCharacters, {'identifier', {identifier} + MySQL.Async.fetchAll(Fetch, { + identifier, + Config.Slots }, function(result) local characters = {} for i=1, #result, 1 do @@ -46,41 +36,43 @@ elseif ESX.GetConfig().Multichar == true then dateofbirth = result[i].dateofbirth, skin = json.decode(result[i].skin) } - if result[i].sex == 'm' then characters[id].sex = 'Male' else characters[id].sex = 'Female' end + if result[i].sex == 'm' then characters[id].sex = _('male') else characters[id].sex = _('female') end end TriggerClientEvent('esx_multicharacter:SetupUI', playerId, characters) end) end - DeleteCharacter = function(playerId, charid) + local DeleteCharacter = function(playerId, charid) local identifier = Config.Prefix..charid..':'..ESX.GetIdentifier(playerId) - for _, itable in pairs(IdentifierTables) do - MySQL.Async.execute("DELETE FROM "..itable.table.." WHERE "..itable.column.." = @identifier", { - ['@identifier'] = identifier + for k, v in pairs(IdentifierTables) do + MySQL.Async.execute("DELETE FROM "..v.table.." WHERE "..v.column.." = ?", { + identifier }) end + print(('[^2INFO^7] Player [%s] %s has deleted a character (%s)'):format(GetPlayerName(playerId), playerId, identifier)) end Citizen.CreateThread(function() MySQL.ready(function () - fetchCharacters = MySQL.Sync.store("SELECT `identifier`, `accounts`, `job`, `job_grade`, `firstname`, `lastname`, `dateofbirth`, `sex`, `skin` FROM `users` WHERE ?? LIKE ?") - - MySQL.Async.fetchAll('SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME = @id AND TABLE_SCHEMA = @db', { ['@id'] = "owner", ["@db"] = Config.Database}, function(result) - if result then - for k, v in pairs(result) do - IdentifierTables[#IdentifierTables+1] = {table = v.TABLE_NAME, column = 'owner'} - end + local result = MySQL.Sync.fetchAll('SELECT `TABLE_NAME`, `COLUMN_NAME`, `CHARACTER_MAXIMUM_LENGTH` FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = ? AND (COLUMN_NAME = ? OR COLUMN_NAME = ?) ', { + Config.Database, 'identifier', 'owner' + }) + if result then + local varchar, varsize = {}, 0 + for k, v in pairs(result) do + if v.CHARACTER_MAXIMUM_LENGTH < 60 then varchar[v.TABLE_NAME] = v.COLUMN_NAME varsize = varsize+1 end + table.insert(IdentifierTables, {table = v.TABLE_NAME, column = v.COLUMN_NAME}) end - end) - MySQL.Async.fetchAll('SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME = @id AND TABLE_SCHEMA = @db', { ['@id'] = "identifier", ["@db"] = Config.Database}, function(result) - if result then - for k, v in pairs(result) do - IdentifierTables[#IdentifierTables+1] = {table = v.TABLE_NAME, column = 'identifier'} + if next(varchar) then + for k, v in pairs(varchar) do + MySQL.Sync.execute("ALTER TABLE "..k.." MODIFY COLUMN "..v.." VARCHAR(60)") end + print(('[^2INFO^7] Attempted to update ^5%s^7 columns to use VARCHAR(60)'):format(varsize)) end - end) + end + if not next(ESX.Jobs) then ESX.Jobs = GetJobs() end + Fetch = MySQL.Sync.store("SELECT `identifier`, `accounts`, `job`, `job_grade`, `firstname`, `lastname`, `dateofbirth`, `sex`, `skin` FROM `users` WHERE identifier LIKE ? LIMIT ?") end) - if not next(ESX.Jobs) then ESX.Jobs = GetJobs() end end) RegisterServerEvent("esx_multicharacter:SetupCharacters") @@ -98,8 +90,6 @@ elseif ESX.GetConfig().Multichar == true then else TriggerEvent('esx:onPlayerJoined', src, Config.Prefix..charid) end - else - -- Trigger Ban Event here to ban individuals trying to use SQL Injections end end) @@ -118,8 +108,6 @@ elseif ESX.GetConfig().Multichar == true then if type(charid) == "number" and string.len(charid) <= 2 then DeleteCharacter(src, charid) SetupCharacters(src) - else - -- Trigger Ban Event here to ban individuals trying to use SQL Injections end end) @@ -133,6 +121,6 @@ elseif ESX.GetConfig().Multichar == true then TriggerEvent('esx:playerLogout', source) end, true) -elseif ESX.GetConfig().Multichar == false then +else SetTimeout(3000, print('[^3WARNING^7] Multicharacter is disabled - please check your ESX configuration')) end From 7332a64ef634ac1247164368d2713b2004dfabb2 Mon Sep 17 00:00:00 2001 From: Linden Date: Sun, 27 Jun 2021 18:05:50 +1000 Subject: [PATCH 03/60] improvements(client): Overhauled most of the character selection and spawning logic --- client/main.lua | 109 ++++++++++++++++++++++-------------------------- fxmanifest.lua | 2 +- 2 files changed, 50 insertions(+), 61 deletions(-) diff --git a/client/main.lua b/client/main.lua index 522522b8..5b859a7d 100644 --- a/client/main.lua +++ b/client/main.lua @@ -13,10 +13,8 @@ if ESX.GetConfig().Multichar then end end) - local cam, cam2 = nil, nil + local canRelog, cam, Spawned = true local Characters = {} - local canRelog = true - local Spawned RegisterNetEvent('esx_multicharacter:SetupCharacters') AddEventHandler('esx_multicharacter:SetupCharacters', function() @@ -42,8 +40,9 @@ if ESX.GetConfig().Multichar then StartLoop = function() hidePlayers = true + MumbleSetVolumeOverride(PlayerId(), 0.0) Citizen.CreateThread(function() - local keys = {18, 27, 172, 173, 176, 177, 187, 188, 191, 201} + local keys = {18, 27, 172, 173, 176, 177, 187, 188, 191, 201, 108, 109} while hidePlayers do DisableAllControlActions(0) for i=1, #keys do @@ -53,31 +52,35 @@ if ESX.GetConfig().Multichar then SetLocalPlayerVisibleLocally(1) SetPlayerInvincible(PlayerId(), 1) Citizen.Wait(3) + 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) + Citizen.Wait(10000) + canRelog = true end) Citizen.CreateThread(function() - local players, vehicles + local playerPool = {} while hidePlayers do - players = GetActivePlayers() + local players = GetActivePlayers() for i=1, #players do - if players[i] ~= PlayerId() then NetworkConcealPlayer(players[i], true, true) end - end - vehicles = ESX.Game.GetVehicles() - for i=1, #vehicles do - NetworkConcealEntity(vehicles[i], true) + local player = players[i] + if player ~= PlayerId() and not playerPool[player] then + playerPool[player] = true + NetworkConcealPlayer(players[player], true, true) + end end Citizen.Wait(500) end - for i=1, #players do - if players[i] ~= PlayerId() then NetworkConcealPlayer(players[i], false, false) end + for i=1, #playerPool do + NetworkConcealPlayer(playerPool[i], false, false) end - for i=1, #vehicles do - NetworkConcealEntity(vehicles[i], false) - end - SetEntityVisible(PlayerPedId(), 1, 0) - SetPlayerInvincible(PlayerId(), 0) - Citizen.Wait(10000) - canRelog = true end) end @@ -144,10 +147,12 @@ if ESX.GetConfig().Multichar then z = Config.Spawn.z, heading = Config.Spawn.w, model = `mp_m_freemode_01`, - skipFade = false + skipFade = true }, function() exports.spawnmanager:setAutoSpawn(false) canRelog = false + DoScreenFadeIn(400) + while not IsScreenFadedIn() do Citizen.Wait(200) end local playerPed = PlayerPedId() SetPedAoBlobRendering(playerPed, false) SetEntityAlpha(playerPed, 0) @@ -155,8 +160,6 @@ if ESX.GetConfig().Multichar then TriggerEvent('esx_identity:showRegisterIdentity') end) else - DoScreenFadeIn(400) - while not IsScreenFadedIn() do Citizen.Wait(200) end 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 @@ -165,6 +168,8 @@ if ESX.GetConfig().Multichar then local label = v.firstname..' '..v.lastname elements[#elements+1] = {label = label, value = v.id} end + DoScreenFadeIn(200) + Citizen.Wait(200) if #elements < Config.Slots then elements[#elements+1] = {label = _('create_char'), value = (#elements+1), new = true} end @@ -184,12 +189,10 @@ if ESX.GetConfig().Multichar then }, function(data, menu) if data.current.action == 'play' then ESX.UI.Menu.CloseAll() - DoScreenFadeOut(300) SendNUIMessage({ action = "closeui" }) TriggerServerEvent('esx_multicharacter:CharacterChosen', data.current.value, false) - Citizen.Wait(300) else local elements2 = {} elements2[1] = {label = _('cancel')} @@ -248,25 +251,19 @@ if ESX.GetConfig().Multichar then RegisterNetEvent('esx:playerLoaded') AddEventHandler('esx:playerLoaded', function(playerData, isNew, skin) + DoScreenFadeOut(100) local spawn = playerData.coords - DoScreenFadeOut(0) - Citizen.Wait(300) local playerPed = PlayerPedId() - cam2 = CreateCamWithParams("DEFAULT_SCRIPTED_CAMERA", spawn.x,spawn.y,spawn.z+200, 0.0, 0.0, 0.0, 100.00, false, 0) - PointCamAtCoord(cam2, spawn.x,spawn.y,spawn.z+10) - SetCamActiveWithInterp(cam2, cam, 400, true, true) - - Citizen.Wait(700) - DoScreenFadeIn(500) FreezeEntityPosition(playerPed, true) - SetEntityVisible(playerPed, true, 0) - SetEntityCoords(playerPed, spawn.x, spawn.y, spawn.z, true, false, false, false) + SetEntityCoords(playerPed, spawn.x, spawn.y, spawn.z-1.3, true, false, false, false) + SetEntityHeading(playerPed, spawn.heading) + Citizen.Wait(300) + SetCamActive(cam, false) + RenderScriptCams(0, 0) + DestroyAllCams(true) + DoScreenFadeIn(400) + Citizen.Wait(400) - cam = CreateCamWithParams("DEFAULT_SCRIPTED_CAMERA", spawn.x,spawn.y,spawn.z+10, 0.0, 0.0, 0.0, 100.00, false, 0) - PointCamAtCoord(cam, spawn.x,spawn.y,spawn.z) - SetCamActiveWithInterp(cam, cam2, 1700, true, true) - - PlaySoundFrontend(-1, "Zoom_Out", "DLC_HEIST_PLANNING_BOARD_SOUNDS", 1) if isNew or not skin or #skin == 1 then local sex = skin.sex or 0 if sex == 0 then model = `mp_m_freemode_01` else model = `mp_f_freemode_01` end @@ -280,35 +277,27 @@ if ESX.GetConfig().Multichar then skin = Config.Default skin.sex = sex TriggerEvent('skinchanger:loadSkin', skin, function() - Citizen.CreateThread(function() - playerped = PlayerPedId() - SetPedAoBlobRendering(playerPed, true) - ResetEntityAlpha(playerPed) - Citizen.Wait(1500) - TriggerEvent('esx_skin:openSaveableMenu') - SetEntityHeading(playerPed, spawn.heading) + playerPed = PlayerPedId() + SetPedAoBlobRendering(playerPed, true) + ResetEntityAlpha(playerPed) + TriggerEvent('esx_skin:openSaveableMenu', function() + finished = true end, function() finished = true end) + SetEntityHeading(playerPed, spawn.heading) end) + while not finished do Citizen.Wait(200) end else + local playerPed = PlayerPedId() + SetEntityHeading(playerPed, spawn.heading) TriggerEvent('skinchanger:loadSkin', skin or Characters[Spawned].skin) - Citizen.Wait(1500) + playerPed = PlayerPedId() + SetEntityVisible(playerPed, true, 0) end - Characters = {} TriggerServerEvent('esx:onPlayerSpawn') TriggerEvent('esx:onPlayerSpawn') TriggerEvent('playerSpawned') TriggerEvent('esx:restoreLoadout') - playerPed = PlayerPedId() - FreezeEntityPosition(playerPed, false) - SetEntityHeading(PlayerPed, spawn.heading) - Citizen.Wait(500) - RenderScriptCams(false, true, 500, true, true) - PlaySoundFrontend(-1, "CAR_BIKE_WHOOSH", "MP_LOBBY_SOUNDS", 1) - SetCamActive(cam, false) - DestroyAllCams(true) - DisplayHud(true) - DisplayRadar(true) - hidePlayers = false + Characters, hidePlayers = {}, false end) RegisterNetEvent('esx:onPlayerLogout') diff --git a/fxmanifest.lua b/fxmanifest.lua index 9ee95603..e41f9719 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -1,7 +1,7 @@ fx_version 'adamant' game 'gta5' description 'https://github.com/thelindat/esx_multicharacter' -version '1.1.2' +version '1.2.0' dependencies { 'es_extended', From b081008863bd69f82fad315c419ab9bfc8fe612f Mon Sep 17 00:00:00 2001 From: Linden Date: Sun, 27 Jun 2021 18:05:50 +1000 Subject: [PATCH 04/60] improvements(client): Fix nil value; overhauled most of the character selection and spawning logic --- server/main.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/main.lua b/server/main.lua index c7fd07be..790e41bc 100644 --- a/server/main.lua +++ b/server/main.lua @@ -60,7 +60,7 @@ elseif ESX.GetConfig().Multichar == true then if result then local varchar, varsize = {}, 0 for k, v in pairs(result) do - if v.CHARACTER_MAXIMUM_LENGTH < 60 then varchar[v.TABLE_NAME] = v.COLUMN_NAME varsize = varsize+1 end + if v.CHARACTER_MAXIMUM_LENGTH and v.CHARACTER_MAXIMUM_LENGTH >= 40 and v.CHARACTER_MAXIMUM_LENGTH < 60 then varchar[v.TABLE_NAME] = v.COLUMN_NAME varsize = varsize+1 end table.insert(IdentifierTables, {table = v.TABLE_NAME, column = v.COLUMN_NAME}) end if next(varchar) then From d6c7e10594395fbf03651c0615c5d6a41e321ec0 Mon Sep 17 00:00:00 2001 From: Linden Date: Mon, 28 Jun 2021 18:02:15 +1000 Subject: [PATCH 05/60] improvements(client): Disable spawnmanager auto spawning --- client/main.lua | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/client/main.lua b/client/main.lua index 5b859a7d..4ea6edb5 100644 --- a/client/main.lua +++ b/client/main.lua @@ -2,17 +2,21 @@ ESX = exports['es_extended']:getSharedObject() if ESX.GetConfig().Multichar then Citizen.CreateThread(function() - while NetworkIsPlayerActive(PlayerId()) and not ESX.IsPlayerLoaded() do - Citizen.Wait(5) - DoScreenFadeOut(0) - while not GetResourceState('esx_menu_default') == 'started' do - Citizen.Wait(0) + while not ESX.PlayerLoaded do + Citizen.Wait(0) + if NetworkIsPlayerActive(PlayerId()) then + exports.spawnmanager:setAutoSpawn(false) + DoScreenFadeOut(0) + while not GetResourceState('esx_menu_default') == 'started' do + Citizen.Wait(0) + end + TriggerEvent("esx_multicharacter:SetupCharacters") + break end - TriggerEvent("esx_multicharacter:SetupCharacters") - break end end) + local canRelog, cam, Spawned = true local Characters = {} @@ -86,8 +90,6 @@ if ESX.GetConfig().Multichar then SetupCharacter = function(index) if Spawned == false then - exports.spawnmanager:setAutoSpawn(false) - exports.spawnmanager:forceRespawn() exports.spawnmanager:spawnPlayer({ x = Config.Spawn.x, y = Config.Spawn.y, @@ -104,7 +106,6 @@ if ESX.GetConfig().Multichar then end TriggerEvent('skinchanger:loadSkin', skin) end - exports.spawnmanager:setAutoSpawn(false) end) elseif Characters[index] and Characters[index].skin then if Characters[Spawned] and Characters[Spawned].model then @@ -149,10 +150,9 @@ if ESX.GetConfig().Multichar then model = `mp_m_freemode_01`, skipFade = true }, function() - exports.spawnmanager:setAutoSpawn(false) canRelog = false DoScreenFadeIn(400) - while not IsScreenFadedIn() do Citizen.Wait(200) end + Citizen.Wait(400) local playerPed = PlayerPedId() SetPedAoBlobRendering(playerPed, false) SetEntityAlpha(playerPed, 0) @@ -168,8 +168,8 @@ if ESX.GetConfig().Multichar then local label = v.firstname..' '..v.lastname elements[#elements+1] = {label = label, value = v.id} end - DoScreenFadeIn(200) - Citizen.Wait(200) + DoScreenFadeIn(400) + Citizen.Wait(400) if #elements < Config.Slots then elements[#elements+1] = {label = _('create_char'), value = (#elements+1), new = true} end From f3eb335547bf326dc8567235ac11461b85d482d2 Mon Sep 17 00:00:00 2001 From: Linden Date: Mon, 28 Jun 2021 18:54:50 +1000 Subject: [PATCH 06/60] refactor(server): Should be using Async here --- server/main.lua | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/server/main.lua b/server/main.lua index 790e41bc..4a52cac9 100644 --- a/server/main.lua +++ b/server/main.lua @@ -52,11 +52,10 @@ elseif ESX.GetConfig().Multichar == true then print(('[^2INFO^7] Player [%s] %s has deleted a character (%s)'):format(GetPlayerName(playerId), playerId, identifier)) end - Citizen.CreateThread(function() - MySQL.ready(function () - local result = MySQL.Sync.fetchAll('SELECT `TABLE_NAME`, `COLUMN_NAME`, `CHARACTER_MAXIMUM_LENGTH` FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = ? AND (COLUMN_NAME = ? OR COLUMN_NAME = ?) ', { - Config.Database, 'identifier', 'owner' - }) + MySQL.ready(function() + MySQL.Async.fetchAll('SELECT `TABLE_NAME`, `COLUMN_NAME`, `CHARACTER_MAXIMUM_LENGTH` FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = ? AND (COLUMN_NAME = ? OR COLUMN_NAME = ?) ', { + Config.Database, 'identifier', 'owner' + }, function(result) if result then local varchar, varsize = {}, 0 for k, v in pairs(result) do @@ -69,9 +68,9 @@ elseif ESX.GetConfig().Multichar == true then end print(('[^2INFO^7] Attempted to update ^5%s^7 columns to use VARCHAR(60)'):format(varsize)) end + if not next(ESX.Jobs) then ESX.Jobs = GetJobs() end + Fetch = MySQL.Sync.store("SELECT `identifier`, `accounts`, `job`, `job_grade`, `firstname`, `lastname`, `dateofbirth`, `sex`, `skin` FROM `users` WHERE identifier LIKE ? LIMIT ?") end - if not next(ESX.Jobs) then ESX.Jobs = GetJobs() end - Fetch = MySQL.Sync.store("SELECT `identifier`, `accounts`, `job`, `job_grade`, `firstname`, `lastname`, `dateofbirth`, `sex`, `skin` FROM `users` WHERE identifier LIKE ? LIMIT ?") end) end) From c97daae2a963dd6e5e424d3aaf092e8fcc49549c Mon Sep 17 00:00:00 2001 From: Linden Date: Mon, 28 Jun 2021 19:00:17 +1000 Subject: [PATCH 07/60] improvements(client): Hide HUD components while active; refactor --- client/main.lua | 23 +++++++++++++---------- fxmanifest.lua | 2 +- readme.md | 7 +++---- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/client/main.lua b/client/main.lua index 4ea6edb5..f79c29b5 100644 --- a/client/main.lua +++ b/client/main.lua @@ -32,8 +32,6 @@ if ESX.GetConfig().Multichar then SetCamActive(cam, true) RenderScriptCams(true, false, 1, true, true) ESX.UI.HUD.SetDisplay(0.0) - DisplayHud(false) - DisplayRadar(false) StartLoop() ShutdownLoadingScreen() ShutdownLoadingScreenNui() @@ -55,6 +53,11 @@ if ESX.GetConfig().Multichar then SetEntityVisible(PlayerPedId(), 0, 0) SetLocalPlayerVisibleLocally(1) SetPlayerInvincible(PlayerId(), 1) + ThefeedHideThisFrame() + HideHudComponentThisFrame(11) + HideHudComponentThisFrame(12) + HideHudComponentThisFrame(21) + HideHudAndRadarThisFrame() Citizen.Wait(3) local vehicles = GetGamePool('CVehicle') for i=1, #vehicles do @@ -106,7 +109,10 @@ if ESX.GetConfig().Multichar then end TriggerEvent('skinchanger:loadSkin', skin) end + DoScreenFadeIn(400) + while IsScreenFadedOut() do Citizen.Wait(100) end end) + elseif Characters[index] and Characters[index].skin then if Characters[Spawned] and Characters[Spawned].model then RequestModel(Characters[index].model) @@ -119,15 +125,15 @@ if ESX.GetConfig().Multichar then end TriggerEvent('skinchanger:loadSkin', Characters[index].skin) end - FreezeEntityPosition(PlayerPedId(), true) Spawned = index - SendNUIMessage({ - action = "openui", - character = Characters[index] - }) local playerPed = PlayerPedId() + FreezeEntityPosition(PlayerPedId(), true) SetPedAoBlobRendering(playerPed, true) SetEntityAlpha(playerPed, 255) + SendNUIMessage({ + action = "openui", + character = Characters[Spawned] + }) end RegisterNetEvent('esx_multicharacter:SetupUI') @@ -141,7 +147,6 @@ if ESX.GetConfig().Multichar then SendNUIMessage({ action = "closeui" }) - exports.spawnmanager:forceRespawn() exports.spawnmanager:spawnPlayer({ x = Config.Spawn.x, y = Config.Spawn.y, @@ -168,8 +173,6 @@ if ESX.GetConfig().Multichar then local label = v.firstname..' '..v.lastname elements[#elements+1] = {label = label, value = v.id} end - DoScreenFadeIn(400) - Citizen.Wait(400) if #elements < Config.Slots then elements[#elements+1] = {label = _('create_char'), value = (#elements+1), new = true} end diff --git a/fxmanifest.lua b/fxmanifest.lua index e41f9719..57787aaa 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -1,7 +1,7 @@ fx_version 'adamant' game 'gta5' description 'https://github.com/thelindat/esx_multicharacter' -version '1.2.0' +version '1.2.1' dependencies { 'es_extended', diff --git a/readme.md b/readme.md index c6c5341f..7fa5bfea 100644 --- a/readme.md +++ b/readme.md @@ -9,8 +9,7 @@ - Modify your ESX config with `Config.Multichar = true` - Set your database name for `Config.Database` in server/main.lua - All owner and identifier columns should be set to `VARCHAR(60)` to ensure correct data entry -- Use the `varchar` command from the console to update your SQL tables -- Once you have used the command you should just remove it for sanity's sake + - The resource will attempt to set columns automatically ### Conflicts * The following resources should not be used with ESX Legacy and can result in errors @@ -19,14 +18,14 @@ - fivem-map-skater - fivem-map-hipster - default_spawnpoint - - cui_character (or other resources that modify spawn behaviour) ### Common issues #### Black screen / loading scripts - Download and run all requirements + - Use a fresh spawnmanager as many people alter the code - Ensure none of the conflicting resources are enabled #### mysql-async duplicate entry - - You have not increased the VARCHAR size of your tables (use the command) + - You have not increased the VARCHAR size of the table holding identifiers - usually `owner` or `identifier` #### The menu interface is esx_menu_default - you can use any version if you want a different appearance ![image](https://user-images.githubusercontent.com/65407488/119010385-592a8c80-b9d7-11eb-9aa1-eb7051004843.png) From e7090e6a7d4136032e5a5326d4f9fbbeb98aab8e Mon Sep 17 00:00:00 2001 From: Linden Date: Tue, 29 Jun 2021 19:24:56 +1000 Subject: [PATCH 08/60] fix(client): Enable and and right arrow keys while loop is active Due to hidePlayers now waiting until a skin has been set before disabling, these keys are now required. --- client/main.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/main.lua b/client/main.lua index f79c29b5..3d7aab44 100644 --- a/client/main.lua +++ b/client/main.lua @@ -44,7 +44,7 @@ if ESX.GetConfig().Multichar then hidePlayers = true MumbleSetVolumeOverride(PlayerId(), 0.0) Citizen.CreateThread(function() - local keys = {18, 27, 172, 173, 176, 177, 187, 188, 191, 201, 108, 109} + local keys = {18, 27, 172, 173, 174, 175, 176, 177, 187, 188, 191, 201, 108, 109} while hidePlayers do DisableAllControlActions(0) for i=1, #keys do From 5dcae7c4c5911aceb2621f688d98c971731aba64 Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Fri, 9 Jul 2021 19:39:15 +1000 Subject: [PATCH 09/60] Update readme.md --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 7fa5bfea..7cfa0536 100644 --- a/readme.md +++ b/readme.md @@ -1,5 +1,5 @@ ### Requirements (ensure you are using the latest) -- [ESX Legacy](https://github.com/esx-framework/es_extended/tree/legacy) +- [ESX Legacy](https://github.com/esx-framework/esx-legacy) - [MySQL Async 3.3.2](https://github.com/brouznouf/fivem-mysql-async/releases/tag/3.3.2) - [ESX Identity](https://github.com/esx-framework/esx_identity) - [ESX Skin](https://github.com/esx-framework/esx_skin) @@ -13,7 +13,7 @@ ### Conflicts * The following resources should not be used with ESX Legacy and can result in errors - - **essentialsmode** + - essentialmode - basic-gamemode - fivem-map-skater - fivem-map-hipster From 861dfcda88217566f5b2e0186b8203eecd841d19 Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Wed, 21 Jul 2021 13:30:44 +1000 Subject: [PATCH 10/60] Update main.lua --- server/main.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/main.lua b/server/main.lua index 4a52cac9..f13d9e82 100644 --- a/server/main.lua +++ b/server/main.lua @@ -1,5 +1,5 @@ if not ESX then - SetTimeout(3000, print('[^3WARNING^7] Unable to start Multicharacter - your version of ESX is not compatible ')) + SetTimeout(3000, function() print('[^3WARNING^7] Unable to start Multicharacter - your version of ESX is not compatible ') end) elseif ESX.GetConfig().Multichar == true then local IdentifierTables, Fetch = {}, -1 @@ -121,5 +121,5 @@ elseif ESX.GetConfig().Multichar == true then end, true) else - SetTimeout(3000, print('[^3WARNING^7] Multicharacter is disabled - please check your ESX configuration')) + SetTimeout(3000, function() print('[^3WARNING^7] Multicharacter is disabled - please check your ESX configuration') end) end From 5f142f6d1f78d2db6f79c379549761664790668a Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Mon, 26 Jul 2021 20:42:00 +1000 Subject: [PATCH 11/60] chore: Update screenshot in readme --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 7cfa0536..35360673 100644 --- a/readme.md +++ b/readme.md @@ -28,7 +28,7 @@ - You have not increased the VARCHAR size of the table holding identifiers - usually `owner` or `identifier` #### The menu interface is esx_menu_default - you can use any version if you want a different appearance -![image](https://user-images.githubusercontent.com/65407488/119010385-592a8c80-b9d7-11eb-9aa1-eb7051004843.png) +![image](https://user-images.githubusercontent.com/65407488/126976325-17cc3241-bb9e-451f-a6ed-610a8ef52fa5.png) ### Relogging - Do not enable this setting if you do not intend to properly set up relog support From 657dcc772828e76fc10e59da764a26756b7c5134 Mon Sep 17 00:00:00 2001 From: Linden Date: Mon, 26 Jul 2021 20:46:07 +1000 Subject: [PATCH 12/60] tweak(client): Modify camera behaviour and positioning --- client/main.lua | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/client/main.lua b/client/main.lua index 3d7aab44..122e5543 100644 --- a/client/main.lua +++ b/client/main.lua @@ -25,12 +25,17 @@ if ESX.GetConfig().Multichar then ESX.PlayerLoaded = false ESX.PlayerData = {} Spawned = false - cam = CreateCamWithParams("DEFAULT_SCRIPTED_CAMERA", Config.Spawn.x, Config.Spawn.y+1.6, Config.Spawn.z+1.3, 0.0, 0.0, 180.0, 100.00, false, 0) - SetEntityCoords(PlayerPedId(), Config.Spawn.x, Config.Spawn.y, Config.Spawn.z, true, false, false, false) - SetEntityHeading(PlayerPedId(), Config.Spawn.w) + cam = CreateCam("DEFAULT_SCRIPTED_CAMERA", true) + local playerPed = PlayerPedId() + SetEntityCoords(playerPed, Config.Spawn.x, Config.Spawn.y, Config.Spawn.z, true, false, false, false) + SetEntityHeading(playerPed, Config.Spawn.w) DoScreenFadeOut(0) SetCamActive(cam, true) RenderScriptCams(true, false, 1, true, true) + + SetCamCoord(cam, Config.Spawn.x, Config.Spawn.y + 1.6, Config.Spawn.z + 1.3) + PointCamAtCoord(cam, Config.Spawn.x, Config.Spawn.y, Config.Spawn.z + 1.3) + ESX.UI.HUD.SetDisplay(0.0) StartLoop() ShutdownLoadingScreen() From 9480853f6a88c2408616f69066986ac37df8a156 Mon Sep 17 00:00:00 2001 From: Linden Date: Mon, 26 Jul 2021 20:48:29 +1000 Subject: [PATCH 13/60] improvement: New players now select skin before teleporting to spawn - This also resolves the bug with esx_skin's camera being zoomed in --- client/main.lua | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/client/main.lua b/client/main.lua index 122e5543..84f6b52e 100644 --- a/client/main.lua +++ b/client/main.lua @@ -259,19 +259,7 @@ if ESX.GetConfig().Multichar then RegisterNetEvent('esx:playerLoaded') AddEventHandler('esx:playerLoaded', function(playerData, isNew, skin) - DoScreenFadeOut(100) local spawn = playerData.coords - local playerPed = PlayerPedId() - FreezeEntityPosition(playerPed, true) - SetEntityCoords(playerPed, spawn.x, spawn.y, spawn.z-1.3, true, false, false, false) - SetEntityHeading(playerPed, spawn.heading) - Citizen.Wait(300) - SetCamActive(cam, false) - RenderScriptCams(0, 0) - DestroyAllCams(true) - DoScreenFadeIn(400) - Citizen.Wait(400) - if isNew or not skin or #skin == 1 then local sex = skin.sex or 0 if sex == 0 then model = `mp_m_freemode_01` else model = `mp_f_freemode_01` end @@ -291,16 +279,21 @@ if ESX.GetConfig().Multichar then TriggerEvent('esx_skin:openSaveableMenu', function() finished = true end, function() finished = true end) - SetEntityHeading(playerPed, spawn.heading) end) while not finished do Citizen.Wait(200) end - else - local playerPed = PlayerPedId() - SetEntityHeading(playerPed, spawn.heading) - TriggerEvent('skinchanger:loadSkin', skin or Characters[Spawned].skin) - playerPed = PlayerPedId() - SetEntityVisible(playerPed, true, 0) end + DoScreenFadeOut(100) + if not isNew then TriggerEvent('skinchanger:loadSkin', skin or Characters[Spawned].skin) end + Citizen.Wait(300) + local playerPed = PlayerPedId() + FreezeEntityPosition(playerPed, true) + SetEntityCoords(playerPed, spawn.x, spawn.y, spawn.z-1.3, true, false, false, false) + SetEntityHeading(playerPed, spawn.heading) + SetCamActive(cam, false) + RenderScriptCams(false, false, 0, true, true) + cam = nil + DoScreenFadeIn(400) + Citizen.Wait(400) TriggerServerEvent('esx:onPlayerSpawn') TriggerEvent('esx:onPlayerSpawn') TriggerEvent('playerSpawned') From 83bae12c0f56fd48346fd4d7378e192934727fd8 Mon Sep 17 00:00:00 2001 From: Linden Date: Mon, 26 Jul 2021 20:59:11 +1000 Subject: [PATCH 14/60] fix: Ensure screenfades last long enough to hide transitions; set local variable for finish (new characters) --- client/main.lua | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/client/main.lua b/client/main.lua index 84f6b52e..9cb30868 100644 --- a/client/main.lua +++ b/client/main.lua @@ -115,7 +115,7 @@ if ESX.GetConfig().Multichar then TriggerEvent('skinchanger:loadSkin', skin) end DoScreenFadeIn(400) - while IsScreenFadedOut() do Citizen.Wait(100) end + repeat Citizen.Wait(200) until not IsScreenFadedOut() end) elseif Characters[index] and Characters[index].skin then @@ -261,6 +261,7 @@ if ESX.GetConfig().Multichar then AddEventHandler('esx:playerLoaded', function(playerData, isNew, skin) local spawn = playerData.coords if isNew or not skin or #skin == 1 then + local finished = false local sex = skin.sex or 0 if sex == 0 then model = `mp_m_freemode_01` else model = `mp_f_freemode_01` end RequestModel(model) @@ -280,20 +281,21 @@ if ESX.GetConfig().Multichar then finished = true end, function() finished = true end) end) - while not finished do Citizen.Wait(200) end + repeat Citizen.Wait(200) until finished end DoScreenFadeOut(100) - if not isNew then TriggerEvent('skinchanger:loadSkin', skin or Characters[Spawned].skin) end - Citizen.Wait(300) + repeat Citizen.Wait(200) until IsScreenFadedOut() + SetCamActive(cam, false) + RenderScriptCams(false, false, 0, true, true) + cam = nil local playerPed = PlayerPedId() FreezeEntityPosition(playerPed, true) SetEntityCoords(playerPed, spawn.x, spawn.y, spawn.z-1.3, true, false, false, false) SetEntityHeading(playerPed, spawn.heading) - SetCamActive(cam, false) - RenderScriptCams(false, false, 0, true, true) - cam = nil - DoScreenFadeIn(400) + if not isNew then TriggerEvent('skinchanger:loadSkin', skin or Characters[Spawned].skin) end Citizen.Wait(400) + DoScreenFadeIn(400) + repeat Citizen.Wait(200) until not IsScreenFadedOut() TriggerServerEvent('esx:onPlayerSpawn') TriggerEvent('esx:onPlayerSpawn') TriggerEvent('playerSpawned') From 9dbe1c5cbe70d9f39bd4e2960ad67e9e7d2ab61e Mon Sep 17 00:00:00 2001 From: Linden Date: Mon, 26 Jul 2021 20:59:58 +1000 Subject: [PATCH 15/60] fix: Ensure character is deleted on the server before refreshing client --- server/main.lua | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/server/main.lua b/server/main.lua index f13d9e82..ea74dafa 100644 --- a/server/main.lua +++ b/server/main.lua @@ -44,12 +44,20 @@ elseif ESX.GetConfig().Multichar == true then local DeleteCharacter = function(playerId, charid) local identifier = Config.Prefix..charid..':'..ESX.GetIdentifier(playerId) + local counter = 0 for k, v in pairs(IdentifierTables) do MySQL.Async.execute("DELETE FROM "..v.table.." WHERE "..v.column.." = ?", { identifier - }) + }, function() + counter = counter + 1 + if counter == #IdentifierTables then + print(('[^2INFO^7] Player [%s] %s has deleted a character (%s)'):format(GetPlayerName(playerId), playerId, identifier)) + Citizen.Wait(100) + SetupCharacters(playerId) + end + end) + Citizen.Wait(5) end - print(('[^2INFO^7] Player [%s] %s has deleted a character (%s)'):format(GetPlayerName(playerId), playerId, identifier)) end MySQL.ready(function() @@ -106,7 +114,6 @@ elseif ESX.GetConfig().Multichar == true then local src = source if type(charid) == "number" and string.len(charid) <= 2 then DeleteCharacter(src, charid) - SetupCharacters(src) end end) From 7bf936713533945d0943f6c4001e7215b08c859e Mon Sep 17 00:00:00 2001 From: Linden Date: Mon, 26 Jul 2021 21:10:59 +1000 Subject: [PATCH 16/60] tweak: Move fadeout check until after the spawnmanager export --- client/main.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/main.lua b/client/main.lua index 9cb30868..7c7eff0a 100644 --- a/client/main.lua +++ b/client/main.lua @@ -115,8 +115,8 @@ if ESX.GetConfig().Multichar then TriggerEvent('skinchanger:loadSkin', skin) end DoScreenFadeIn(400) - repeat Citizen.Wait(200) until not IsScreenFadedOut() end) + repeat Citizen.Wait(200) until not IsScreenFadedOut() elseif Characters[index] and Characters[index].skin then if Characters[Spawned] and Characters[Spawned].model then From b5b5f56a2b224f71efdc7d54c25544b04b806e24 Mon Sep 17 00:00:00 2001 From: Linden Date: Mon, 26 Jul 2021 21:21:57 +1000 Subject: [PATCH 17/60] Improvement: Set the camera position offset from the entity - Modifying the spawn location would previously require adjusting the camera parameters. - This, along with the previous camera change, ensures the camera is always facing the ped --- client/main.lua | 4 ++-- config.lua | 3 ++- fxmanifest.lua | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/client/main.lua b/client/main.lua index 7c7eff0a..242d159d 100644 --- a/client/main.lua +++ b/client/main.lua @@ -29,11 +29,11 @@ if ESX.GetConfig().Multichar then local playerPed = PlayerPedId() SetEntityCoords(playerPed, Config.Spawn.x, Config.Spawn.y, Config.Spawn.z, true, false, false, false) SetEntityHeading(playerPed, Config.Spawn.w) + local offset = GetOffsetFromEntityInWorldCoords(playerPed, 0, 1.7, 0.4) DoScreenFadeOut(0) SetCamActive(cam, true) RenderScriptCams(true, false, 1, true, true) - - SetCamCoord(cam, Config.Spawn.x, Config.Spawn.y + 1.6, Config.Spawn.z + 1.3) + SetCamCoord(cam, offset.x, offset.y, offset.z) PointCamAtCoord(cam, Config.Spawn.x, Config.Spawn.y, Config.Spawn.z + 1.3) ESX.UI.HUD.SetDisplay(0.0) diff --git a/config.lua b/config.lua index a444f660..0dff5211 100644 --- a/config.lua +++ b/config.lua @@ -2,7 +2,8 @@ Config = {} Config.Locale = 'en' Config.Slots = 4 -Config.Spawn = vector4(-113.7, 565.3, 195.2, 0) +Config.Spawn = vector4(-113.7, 565.3, 195.2, 0) -- Sets the location for character selection +-- To set the spawn location for new characters, modify the default value in the `users` SQL table -------------------- -- Do not use unless you are prepared to adjust your resources to correctly reset data diff --git a/fxmanifest.lua b/fxmanifest.lua index 57787aaa..617f933c 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -1,7 +1,7 @@ fx_version 'adamant' game 'gta5' description 'https://github.com/thelindat/esx_multicharacter' -version '1.2.1' +version '1.2.2' dependencies { 'es_extended', From 0ec17f081ec652b4218cf9dddbd420f352e37f39 Mon Sep 17 00:00:00 2001 From: Linden Date: Mon, 2 Aug 2021 22:13:36 +1000 Subject: [PATCH 18/60] fix: Close UI when setting up characters; spawn fallback position - If playerData.coords don't exist then there's an issue with that character - Instead of erroring out, spawn them at the character selection area todo: setup default spawn location instead of relying on database --- client/main.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/main.lua b/client/main.lua index 242d159d..87dccc3e 100644 --- a/client/main.lua +++ b/client/main.lua @@ -35,7 +35,7 @@ if ESX.GetConfig().Multichar then RenderScriptCams(true, false, 1, true, true) SetCamCoord(cam, offset.x, offset.y, offset.z) PointCamAtCoord(cam, Config.Spawn.x, Config.Spawn.y, Config.Spawn.z + 1.3) - + ESX.UI.Menu.CloseAll() ESX.UI.HUD.SetDisplay(0.0) StartLoop() ShutdownLoadingScreen() @@ -147,6 +147,7 @@ if ESX.GetConfig().Multichar then Characters = data local elements = {} local Character = next(Characters) + exports.spawnmanager:forceRespawn() if Character == nil then SendNUIMessage({ @@ -259,7 +260,7 @@ if ESX.GetConfig().Multichar then RegisterNetEvent('esx:playerLoaded') AddEventHandler('esx:playerLoaded', function(playerData, isNew, skin) - local spawn = playerData.coords + local spawn = playerData.coords or Config.Spawn if isNew or not skin or #skin == 1 then local finished = false local sex = skin.sex or 0 From 248b6e0b92664c40c8a2156770d1a8e688e4b2ad Mon Sep 17 00:00:00 2001 From: Demetrio <15928886+d3rp-jsx@users.noreply.github.com> Date: Mon, 9 Aug 2021 16:54:32 +0200 Subject: [PATCH 19/60] improvement: configurable character deletation (#35) Co-authored-by: d3rp-jsx <15928886+derp-jsx@users.noreply.github.com> --- client/main.lua | 2 +- config.lua | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/client/main.lua b/client/main.lua index 87dccc3e..28e33a4d 100644 --- a/client/main.lua +++ b/client/main.lua @@ -190,7 +190,7 @@ if ESX.GetConfig().Multichar then local elements = {} if not data.current.new then elements[1] = {label = _('char_play'), action = 'play', value = data.current.value} - elements[2] = {label = _('char_delete'), action = 'delete', value = data.current.value} + if Config.CanDelete then elements[2] = {label = _('char_delete'), action = 'delete', value = data.current.value} end ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'choosechar', { title = _('select_char'), align = 'top-left', diff --git a/config.lua b/config.lua index 0dff5211..f1874abf 100644 --- a/config.lua +++ b/config.lua @@ -5,6 +5,8 @@ Config.Slots = 4 Config.Spawn = vector4(-113.7, 565.3, 195.2, 0) -- Sets the location for character selection -- To set the spawn location for new characters, modify the default value in the `users` SQL table +Config.CanDelete = false -- Choose whether or not a user can self-delete its own characters + -------------------- -- Do not use unless you are prepared to adjust your resources to correctly reset data -- Information: https://github.com/thelindat/esx_multicharacter#relogging From 52250d0d495cba7cc569786a1d874faf85e40d7e Mon Sep 17 00:00:00 2001 From: Demetrio <15928886+d3rp-jsx@users.noreply.github.com> Date: Tue, 10 Aug 2021 19:31:30 +0200 Subject: [PATCH 20/60] Dynamic slots (#36) * Removed slots configuration * feature: dynamic slots * fixes + improvements * edited core functionality * added commands for slots management * added commands english locale Co-authored-by: d3rp-jsx <15928886+derp-jsx@users.noreply.github.com> --- client/main.lua | 7 +++--- config.lua | 2 +- esx_multicharacter.sql | 7 ++++++ locales/en.lua | 7 ++++++ server/main.lua | 48 ++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 esx_multicharacter.sql diff --git a/client/main.lua b/client/main.lua index 28e33a4d..15f6b391 100644 --- a/client/main.lua +++ b/client/main.lua @@ -142,9 +142,10 @@ if ESX.GetConfig().Multichar then end RegisterNetEvent('esx_multicharacter:SetupUI') - AddEventHandler('esx_multicharacter:SetupUI', function(data) + AddEventHandler('esx_multicharacter:SetupUI', function(data, slots) DoScreenFadeOut(0) Characters = data + local slots = slots local elements = {} local Character = next(Characters) exports.spawnmanager:forceRespawn() @@ -179,7 +180,7 @@ if ESX.GetConfig().Multichar then local label = v.firstname..' '..v.lastname elements[#elements+1] = {label = label, value = v.id} end - if #elements < Config.Slots then + if #elements < slots then elements[#elements+1] = {label = _('create_char'), value = (#elements+1), new = true} end ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'selectchar', { @@ -228,7 +229,7 @@ if ESX.GetConfig().Multichar then else ESX.UI.Menu.CloseAll() local GetSlot = function() - for i=1, Config.Slots do + for i=1, slots do if not Characters[i] then return i end diff --git a/config.lua b/config.lua index f1874abf..bda8e554 100644 --- a/config.lua +++ b/config.lua @@ -1,7 +1,7 @@ Config = {} Config.Locale = 'en' -Config.Slots = 4 +Config.Slots = 1 Config.Spawn = vector4(-113.7, 565.3, 195.2, 0) -- Sets the location for character selection -- To set the spawn location for new characters, modify the default value in the `users` SQL table diff --git a/esx_multicharacter.sql b/esx_multicharacter.sql new file mode 100644 index 00000000..9495bb5a --- /dev/null +++ b/esx_multicharacter.sql @@ -0,0 +1,7 @@ +CREATE TABLE `multicharacter_slots` ( + `identifier` VARCHAR(60) NOT NULL DEFAULT '' COLLATE 'utf8mb4_unicode_ci', + `slots` INT(11) NOT NULL DEFAULT '1' +) +COLLATE='utf8mb4_unicode_ci' +ENGINE=InnoDB +; diff --git a/locales/en.lua b/locales/en.lua index f814d78a..e61481b0 100644 --- a/locales/en.lua +++ b/locales/en.lua @@ -8,4 +8,11 @@ Locales['en'] = { ['char_delete'] = "Delete this character", ['cancel'] = "Cancel", ['confirm'] = "Confirm", + ['command_setslots'] = "Set multicharacter slots number of a player", + ['command_remslots'] = "Remove multicharacter slots number of a player", + ['command_identifier'] = "Player identifier", + ['command_slots'] = "# of slots", + ['slotsadd'] = "You added %s slots to %s", + ['slotsedit'] = "You set %s slots to %s", + ['slotsrem'] = "You removed slots to %s", } diff --git a/server/main.lua b/server/main.lua index ea74dafa..d8abff13 100644 --- a/server/main.lua +++ b/server/main.lua @@ -12,9 +12,15 @@ elseif ESX.GetConfig().Multichar == true then local SetupCharacters = function(playerId) while Fetch == -1 do Citizen.Wait(500) end local identifier = Config.Prefix..'%:'..ESX.GetIdentifier(playerId) + local slots = MySQL.Sync.fetchScalar("SELECT `slots` FROM `multicharacter_slots` WHERE identifier = ?", { + ESX.GetIdentifier(playerId) + }) + if not slots then + slots = Config.Slots + end MySQL.Async.fetchAll(Fetch, { identifier, - Config.Slots + slots }, function(result) local characters = {} for i=1, #result, 1 do @@ -38,7 +44,7 @@ elseif ESX.GetConfig().Multichar == true then } if result[i].sex == 'm' then characters[id].sex = _('male') else characters[id].sex = _('female') end end - TriggerClientEvent('esx_multicharacter:SetupUI', playerId, characters) + TriggerClientEvent('esx_multicharacter:SetupUI', playerId, characters, slots) end) end @@ -123,6 +129,44 @@ elseif ESX.GetConfig().Multichar == true then TriggerEvent('esx:playerLogout', src) end) + ESX.RegisterCommand('setslots', 'admin', function(xPlayer, args, showError) + local slots = MySQL.Sync.fetchScalar('SELECT `slots` FROM `multicharacter_slots` WHERE identifier = ?', { + args.identifier + }) + + if slots == nil then + MySQL.Async.execute('INSERT INTO `multicharacter_slots` (`identifier`, `slots`) VALUES (?, ?)', { + args.identifier, + args.slots + }) + xPlayer.triggerEvent('esx:showNotification', _U('slotsadd', args.slots, args.identifier)) + else + MySQL.Async.execute('UPDATE `multicharacter_slots` SET `slots` = ? WHERE `identifier` = ?', { + args.slots, + args.identifier + }) + xPlayer.triggerEvent('esx:showNotification', _U('slotsedit', args.slots, args.identifier)) + end + end, true, {help = _U('command_setslots'), validate = true, arguments = { + {name = 'identifier', help = _U('command_identifier'), type = 'string'}, + {name = 'slots', help = _U('command_slots'), type = 'number'} + }}) + + ESX.RegisterCommand('remslots', 'admin', function(xPlayer, args, showError) + local slots = MySQL.Sync.fetchScalar('SELECT `slots` FROM `multicharacter_slots` WHERE identifier = ?', { + args.identifier + }) + + if slots ~= nil then + MySQL.Async.execute('DELETE FROM `multicharacter_slots` WHERE `identifier` = ?', { + args.identifier + }) + xPlayer.triggerEvent('esx:showNotification', _U('slotsrem', args.identifier)) + end + end, true, {help = _U('command_remslots'), validate = true, arguments = { + {name = 'identifier', help = _U('command_identifier'), type = 'string'} + }}) + RegisterCommand('forcelog', function(source, args, rawCommand) TriggerEvent('esx:playerLogout', source) end, true) From e65a4cb181ffa633dcb271adb9adf3b138fa898e Mon Sep 17 00:00:00 2001 From: Demetrio <15928886+d3rp-jsx@users.noreply.github.com> Date: Wed, 11 Aug 2021 01:33:40 +0200 Subject: [PATCH 21/60] Italian language support + Doc for dynamic slots and minor improvements (#37) * Added Italian language support + usability improvements * cleaner sql file Co-authored-by: d3rp-jsx <15928886+derp-jsx@users.noreply.github.com> --- config.lua | 5 +++++ esx_multicharacter.sql | 9 +++------ locales/it.lua | 18 ++++++++++++++++++ readme.md | 1 + 4 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 locales/it.lua diff --git a/config.lua b/config.lua index bda8e554..ca56aec7 100644 --- a/config.lua +++ b/config.lua @@ -1,7 +1,12 @@ Config = {} Config.Locale = 'en' +-------------------- +-- This is the default number of slots for EVERY player +-- If you want to manage extra slots for specific players you can do it by using '/setslots' and '/remslots' commands Config.Slots = 1 +-------------------- + Config.Spawn = vector4(-113.7, 565.3, 195.2, 0) -- Sets the location for character selection -- To set the spawn location for new characters, modify the default value in the `users` SQL table diff --git a/esx_multicharacter.sql b/esx_multicharacter.sql index 9495bb5a..86fb2b36 100644 --- a/esx_multicharacter.sql +++ b/esx_multicharacter.sql @@ -1,7 +1,4 @@ CREATE TABLE `multicharacter_slots` ( - `identifier` VARCHAR(60) NOT NULL DEFAULT '' COLLATE 'utf8mb4_unicode_ci', - `slots` INT(11) NOT NULL DEFAULT '1' -) -COLLATE='utf8mb4_unicode_ci' -ENGINE=InnoDB -; + `identifier` VARCHAR(60) NOT NULL, + `slots` INT NOT NULL +); diff --git a/locales/it.lua b/locales/it.lua new file mode 100644 index 00000000..6bae31fb --- /dev/null +++ b/locales/it.lua @@ -0,0 +1,18 @@ +Locales['it'] = { + ['male'] = "Maschio", + ['female'] = "Femmina", + ['delete_label'] = "Cancella %s %s?", + ['select_char'] = "Seleziona Personaggio", + ['create_char'] = "Crea un nuovo personaggio", + ['char_play'] = "Gioca questo personaggio", + ['char_delete'] = "Cancella questo personaggio", + ['cancel'] = "Annulla", + ['confirm'] = "Conferma", + ['command_setslots'] = "Imposta il numero di slot di personaggi di un player", + ['command_remslots'] = "Rimuovi il numero di slot di personaggi di un player", + ['command_identifier'] = "Identifier del giocatore", + ['command_slots'] = "# di slot", + ['slotsadd'] = "Hai aggiunto %s slot a %s", + ['slotsedit'] = "Hai impostato %s slot a %s", + ['slotsrem'] = "Hai rimosso gli slot a %s", +} diff --git a/readme.md b/readme.md index 35360673..ccd8dc76 100644 --- a/readme.md +++ b/readme.md @@ -8,6 +8,7 @@ ### Installation - Modify your ESX config with `Config.Multichar = true` - Set your database name for `Config.Database` in server/main.lua +- Run `esx_multicharacter.sql` into your database - All owner and identifier columns should be set to `VARCHAR(60)` to ensure correct data entry - The resource will attempt to set columns automatically From 5f5b0138f056d1c21a8418f45befa6a911db9dfe Mon Sep 17 00:00:00 2001 From: Demetrio <15928886+d3rp-jsx@users.noreply.github.com> Date: Fri, 20 Aug 2021 16:56:48 +0200 Subject: [PATCH 22/60] feat(chars): enable or disable a specific character of a player (#43) --- client/main.lua | 14 ++++++-- esx_multicharacter.sql | 3 ++ locales/en.lua | 7 ++++ locales/it.lua | 11 ++++-- server/commands.lua | 79 ++++++++++++++++++++++++++++++++++++++++++ server/main.lua | 47 ++----------------------- 6 files changed, 112 insertions(+), 49 deletions(-) create mode 100644 server/commands.lua diff --git a/client/main.lua b/client/main.lua index 15f6b391..e5705d2f 100644 --- a/client/main.lua +++ b/client/main.lua @@ -178,7 +178,11 @@ if ESX.GetConfig().Multichar then end if Spawned == false then SetupCharacter(Character) end local label = v.firstname..' '..v.lastname - elements[#elements+1] = {label = label, value = v.id} + if Characters[k].disabled == 1 then + elements[#elements+1] = {label = label, value = v.id} + else + elements[#elements+1] = {label = label, value = v.id} + end end if #elements < slots then elements[#elements+1] = {label = _('create_char'), value = (#elements+1), new = true} @@ -190,7 +194,11 @@ if ESX.GetConfig().Multichar then }, function(data, menu) local elements = {} if not data.current.new then - elements[1] = {label = _('char_play'), action = 'play', value = data.current.value} + if Characters[data.current.value].disabled == 0 then + elements[1] = {label = _('char_play'), action = 'play', value = data.current.value} + else + elements[1] = {label = _('char_disabled'), value = data.current.value} + end if Config.CanDelete then elements[2] = {label = _('char_delete'), action = 'delete', value = data.current.value} end ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'choosechar', { title = _('select_char'), @@ -203,7 +211,7 @@ if ESX.GetConfig().Multichar then action = "closeui" }) TriggerServerEvent('esx_multicharacter:CharacterChosen', data.current.value, false) - else + elseif data.current.action == 'delete' then local elements2 = {} elements2[1] = {label = _('cancel')} elements2[2] = {label = _('confirm'), value = data.current.value} diff --git a/esx_multicharacter.sql b/esx_multicharacter.sql index 86fb2b36..6aba96ad 100644 --- a/esx_multicharacter.sql +++ b/esx_multicharacter.sql @@ -2,3 +2,6 @@ CREATE TABLE `multicharacter_slots` ( `identifier` VARCHAR(60) NOT NULL, `slots` INT NOT NULL ); + +ALTER TABLE `users` + ADD `disabled` BIT NOT NULL DEFAULT 0; \ No newline at end of file diff --git a/locales/en.lua b/locales/en.lua index e61481b0..b71de307 100644 --- a/locales/en.lua +++ b/locales/en.lua @@ -5,14 +5,21 @@ Locales['en'] = { ['select_char'] = "Select Character", ['create_char'] = "Create new character", ['char_play'] = "Play this character", + ['char_disabled'] = "This character is disabled", ['char_delete'] = "Delete this character", ['cancel'] = "Cancel", ['confirm'] = "Confirm", ['command_setslots'] = "Set multicharacter slots number of a player", ['command_remslots'] = "Remove multicharacter slots number of a player", + ['command_enablechar'] = "Enable a given character of a player", + ['command_disablechar'] = "Disable a given character of a player", + ['command_charslot'] = "Slot number of the character", ['command_identifier'] = "Player identifier", ['command_slots'] = "# of slots", ['slotsadd'] = "You added %s slots to %s", ['slotsedit'] = "You set %s slots to %s", ['slotsrem'] = "You removed slots to %s", + ['charenabled'] = "You enabled character #%s of %s", + ['chardisabled'] = "You disabled character #%s of %s", + ['charnotfound'] = "Character #%s of %s doesn't exist", } diff --git a/locales/it.lua b/locales/it.lua index 6bae31fb..1f2333e5 100644 --- a/locales/it.lua +++ b/locales/it.lua @@ -5,14 +5,21 @@ Locales['it'] = { ['select_char'] = "Seleziona Personaggio", ['create_char'] = "Crea un nuovo personaggio", ['char_play'] = "Gioca questo personaggio", + ['char_disabled'] = "Questo personaggio è disabilitato", ['char_delete'] = "Cancella questo personaggio", ['cancel'] = "Annulla", ['confirm'] = "Conferma", - ['command_setslots'] = "Imposta il numero di slot di personaggi di un player", - ['command_remslots'] = "Rimuovi il numero di slot di personaggi di un player", + ['command_setslots'] = "Imposta il numero di slot di personaggi di un giocatore", + ['command_remslots'] = "Rimuovi il numero di slot di personaggi di un giocatore", + ['command_enablechar'] = "Abilita un personaggio di un giocatore", + ['command_disablechar'] = "Disabilita un personaggio di un giocatore", + ['command_charslot'] = "Numero dello slot del personaggio", ['command_identifier'] = "Identifier del giocatore", ['command_slots'] = "# di slot", ['slotsadd'] = "Hai aggiunto %s slot a %s", ['slotsedit'] = "Hai impostato %s slot a %s", ['slotsrem'] = "Hai rimosso gli slot a %s", + ['charenabled'] = "Hai abilitato il %s° personaggio di %s", + ['chardisabled'] = "Hai disabilitato il %s° personaggio di %s", + ['charnotfound'] = "Il personaggio %s di %s non esiste", } diff --git a/server/commands.lua b/server/commands.lua new file mode 100644 index 00000000..b4fbc2d2 --- /dev/null +++ b/server/commands.lua @@ -0,0 +1,79 @@ +ESX.RegisterCommand('setslots', 'admin', function(xPlayer, args, showError) + local slots = MySQL.Sync.fetchScalar('SELECT `slots` FROM `multicharacter_slots` WHERE identifier = ?', { + args.identifier + }) + + if slots == nil then + MySQL.Async.execute('INSERT INTO `multicharacter_slots` (`identifier`, `slots`) VALUES (?, ?)', { + args.identifier, + args.slots + }) + xPlayer.triggerEvent('esx:showNotification', _U('slotsadd', args.slots, args.identifier)) + else + MySQL.Async.execute('UPDATE `multicharacter_slots` SET `slots` = ? WHERE `identifier` = ?', { + args.slots, + args.identifier + }) + xPlayer.triggerEvent('esx:showNotification', _U('slotsedit', args.slots, args.identifier)) + end +end, true, {help = _U('command_setslots'), validate = true, arguments = { + {name = 'identifier', help = _U('command_identifier'), type = 'string'}, + {name = 'slots', help = _U('command_slots'), type = 'number'} +}}) + +ESX.RegisterCommand('remslots', 'admin', function(xPlayer, args, showError) + local slots = MySQL.Sync.fetchScalar('SELECT `slots` FROM `multicharacter_slots` WHERE identifier = ?', { + args.identifier + }) + + if slots ~= nil then + MySQL.Async.execute('DELETE FROM `multicharacter_slots` WHERE `identifier` = ?', { + args.identifier + }) + xPlayer.triggerEvent('esx:showNotification', _U('slotsrem', args.identifier)) + end +end, true, {help = _U('command_remslots'), validate = true, arguments = { + {name = 'identifier', help = _U('command_identifier'), type = 'string'} +}}) + +ESX.RegisterCommand('enablechar', 'admin', function(xPlayer, args, showError) + + local selectedCharacter = 'char'..args.charslot..':'..args.identifier; + + MySQL.Async.execute('UPDATE `users` SET `disabled` = 0 WHERE identifier = ?', { + selectedCharacter + }, function(result) + if result > 0 then + xPlayer.triggerEvent('esx:showNotification', _U('charenabled', args.charslot, args.identifier)) + else + xPlayer.triggerEvent('esx:showNotification', _U('charnotfound', args.charslot, args.identifier)) + end + end) + +end, true, {help = _U('command_enablechar'), validate = true, arguments = { + {name = 'identifier', help = _U('command_identifier'), type = 'string'}, + {name = 'charslot', help = _U('command_charslot'), type = 'number'} +}}) + +ESX.RegisterCommand('disablechar', 'admin', function(xPlayer, args, showError) + + local selectedCharacter = 'char'..args.charslot..':'..args.identifier; + + MySQL.Async.execute('UPDATE `users` SET `disabled` = 1 WHERE identifier = ?', { + selectedCharacter + }, function(result) + if result > 0 then + xPlayer.triggerEvent('esx:showNotification', _U('chardisabled', args.charslot, args.identifier)) + else + xPlayer.triggerEvent('esx:showNotification', _U('charnotfound', args.charslot, args.identifier)) + end + end) + +end, true, {help = _U('command_disablechar'), validate = true, arguments = { + {name = 'identifier', help = _U('command_identifier'), type = 'string'}, + {name = 'charslot', help = _U('command_charslot'), type = 'number'} +}}) + +RegisterCommand('forcelog', function(source, args, rawCommand) + TriggerEvent('esx:playerLogout', source) +end, true) \ No newline at end of file diff --git a/server/main.lua b/server/main.lua index d8abff13..09617693 100644 --- a/server/main.lua +++ b/server/main.lua @@ -40,7 +40,8 @@ elseif ESX.GetConfig().Multichar == true then firstname = result[i].firstname, lastname = result[i].lastname, dateofbirth = result[i].dateofbirth, - skin = json.decode(result[i].skin) + skin = json.decode(result[i].skin), + disabled = result[i].disabled, } if result[i].sex == 'm' then characters[id].sex = _('male') else characters[id].sex = _('female') end end @@ -83,7 +84,7 @@ elseif ESX.GetConfig().Multichar == true then print(('[^2INFO^7] Attempted to update ^5%s^7 columns to use VARCHAR(60)'):format(varsize)) end if not next(ESX.Jobs) then ESX.Jobs = GetJobs() end - Fetch = MySQL.Sync.store("SELECT `identifier`, `accounts`, `job`, `job_grade`, `firstname`, `lastname`, `dateofbirth`, `sex`, `skin` FROM `users` WHERE identifier LIKE ? LIMIT ?") + Fetch = MySQL.Sync.store("SELECT `identifier`, `accounts`, `job`, `job_grade`, `firstname`, `lastname`, `dateofbirth`, `sex`, `skin`, `disabled` FROM `users` WHERE identifier LIKE ? LIMIT ?") end end) end) @@ -129,48 +130,6 @@ elseif ESX.GetConfig().Multichar == true then TriggerEvent('esx:playerLogout', src) end) - ESX.RegisterCommand('setslots', 'admin', function(xPlayer, args, showError) - local slots = MySQL.Sync.fetchScalar('SELECT `slots` FROM `multicharacter_slots` WHERE identifier = ?', { - args.identifier - }) - - if slots == nil then - MySQL.Async.execute('INSERT INTO `multicharacter_slots` (`identifier`, `slots`) VALUES (?, ?)', { - args.identifier, - args.slots - }) - xPlayer.triggerEvent('esx:showNotification', _U('slotsadd', args.slots, args.identifier)) - else - MySQL.Async.execute('UPDATE `multicharacter_slots` SET `slots` = ? WHERE `identifier` = ?', { - args.slots, - args.identifier - }) - xPlayer.triggerEvent('esx:showNotification', _U('slotsedit', args.slots, args.identifier)) - end - end, true, {help = _U('command_setslots'), validate = true, arguments = { - {name = 'identifier', help = _U('command_identifier'), type = 'string'}, - {name = 'slots', help = _U('command_slots'), type = 'number'} - }}) - - ESX.RegisterCommand('remslots', 'admin', function(xPlayer, args, showError) - local slots = MySQL.Sync.fetchScalar('SELECT `slots` FROM `multicharacter_slots` WHERE identifier = ?', { - args.identifier - }) - - if slots ~= nil then - MySQL.Async.execute('DELETE FROM `multicharacter_slots` WHERE `identifier` = ?', { - args.identifier - }) - xPlayer.triggerEvent('esx:showNotification', _U('slotsrem', args.identifier)) - end - end, true, {help = _U('command_remslots'), validate = true, arguments = { - {name = 'identifier', help = _U('command_identifier'), type = 'string'} - }}) - - RegisterCommand('forcelog', function(source, args, rawCommand) - TriggerEvent('esx:playerLogout', source) - end, true) - else SetTimeout(3000, function() print('[^3WARNING^7] Multicharacter is disabled - please check your ESX configuration') end) end From 6925f91a1e52062fd7f72638203233a4be81c26c Mon Sep 17 00:00:00 2001 From: IceCubePiso <33593306+icecubepiso@users.noreply.github.com> Date: Fri, 20 Aug 2021 19:06:47 +0200 Subject: [PATCH 23/60] Update cs.lua (#44) --- locales/cs.lua | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/locales/cs.lua b/locales/cs.lua index a9734076..d309e309 100644 --- a/locales/cs.lua +++ b/locales/cs.lua @@ -3,9 +3,23 @@ Locales['cs'] = { ['female'] = "Žena", ['delete_label'] = "Vymazat %s %s?", ['select_char'] = "Zvolit Postavu", - ['create_char'] = "Vytvořit novou postavu", + ['create_char'] = "Vytvořit Novou Postavu", ['char_play'] = "Hrát za postavu", + ['char_disabled'] = "Tato postava je zakázána!", ['char_delete'] = "Vymazat postavu", ['cancel'] = "Zrušit", ['confirm'] = "Potvrdit", + ['command_setslots'] = "Nastavit slot pro hráče v multicharacteru", + ['command_remslots'] = "Odebrat slot pro hráče v multicharacteru", + ['command_enablechar'] = "Povolit zovlený slot hráči", + ['command_disablechar'] = "Zakázat zvolený slot pro hračé", + ['command_charslot'] = "Číslo slotu", + ['command_identifier'] = "Identifier hráče", + ['command_slots'] = "# ze slotu", + ['slotsadd'] = "Přidal slot %s hráči %s", + ['slotsedit'] = "Nastavil si slot %s hráči %s", + ['slotsrem'] = "Odebral si slot %s", + ['charenabled'] = "Povolol si postavu #%s z %s", + ['chardisabled'] = "Zakázal si postavu #%s z %s", + ['charnotfound'] = "Postava #%s z %s nebyla nalezena/neexistuje", } From 6427a58ac74ba4ee10ccac24660c821c5010601a Mon Sep 17 00:00:00 2001 From: cjosephdc <87730839+cjosephdc@users.noreply.github.com> Date: Wed, 25 Aug 2021 19:38:26 +0800 Subject: [PATCH 24/60] Fix for stuck players (#45) Fix the issue where players get stuck after selecting character --- client/main.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/main.lua b/client/main.lua index e5705d2f..558e7937 100644 --- a/client/main.lua +++ b/client/main.lua @@ -294,7 +294,7 @@ if ESX.GetConfig().Multichar then repeat Citizen.Wait(200) until finished end DoScreenFadeOut(100) - repeat Citizen.Wait(200) until IsScreenFadedOut() + SetCamActive(cam, false) RenderScriptCams(false, false, 0, true, true) cam = nil From d412efab05d416cdf1020f5d8a456622ba3e7fd2 Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Wed, 1 Sep 2021 16:17:05 +1000 Subject: [PATCH 25/60] fix(client)? Maybe resolves issue with concealing players --- client/main.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/main.lua b/client/main.lua index 558e7937..cc3eb09f 100644 --- a/client/main.lua +++ b/client/main.lua @@ -63,7 +63,7 @@ if ESX.GetConfig().Multichar then HideHudComponentThisFrame(12) HideHudComponentThisFrame(21) HideHudAndRadarThisFrame() - Citizen.Wait(3) + Citizen.Wait(0) local vehicles = GetGamePool('CVehicle') for i=1, #vehicles do SetEntityLocallyInvisible(vehicles[i]) @@ -85,13 +85,13 @@ if ESX.GetConfig().Multichar then local player = players[i] if player ~= PlayerId() and not playerPool[player] then playerPool[player] = true - NetworkConcealPlayer(players[player], true, true) + NetworkConcealPlayer(player, true, true) end end Citizen.Wait(500) end - for i=1, #playerPool do - NetworkConcealPlayer(playerPool[i], false, false) + for k in pairs(playerPool) do + NetworkConcealPlayer(k, false, false) end end) end From 1984ef58a541566ef1de4ae5b0e064917d1c91f5 Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Fri, 3 Sep 2021 19:46:30 +1000 Subject: [PATCH 26/60] Update readme.md --- readme.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/readme.md b/readme.md index ccd8dc76..dcca296f 100644 --- a/readme.md +++ b/readme.md @@ -1,8 +1,6 @@ ### Requirements (ensure you are using the latest) - [ESX Legacy](https://github.com/esx-framework/esx-legacy) - [MySQL Async 3.3.2](https://github.com/brouznouf/fivem-mysql-async/releases/tag/3.3.2) -- [ESX Identity](https://github.com/esx-framework/esx_identity) -- [ESX Skin](https://github.com/esx-framework/esx_skin) - [Spawnmanager](https://github.com/citizenfx/cfx-server-data/tree/master/resources/%5Bmanagers%5D/spawnmanager) ### Installation From a1cb26da5d96fe8a132a890bbca9dd7c75e9e36d Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Sat, 16 Oct 2021 06:37:34 +1100 Subject: [PATCH 27/60] feat(sql): Setup proper keys and collation - These fields should only be referenced, so index them for faster queries --- esx_multicharacter.sql | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/esx_multicharacter.sql b/esx_multicharacter.sql index 6aba96ad..23c894a5 100644 --- a/esx_multicharacter.sql +++ b/esx_multicharacter.sql @@ -1,7 +1,9 @@ CREATE TABLE `multicharacter_slots` ( - `identifier` VARCHAR(60) NOT NULL, - `slots` INT NOT NULL -); - -ALTER TABLE `users` - ADD `disabled` BIT NOT NULL DEFAULT 0; \ No newline at end of file + `identifier` VARCHAR(60) NOT NULL COLLATE 'utf8mb4_unicode_ci', + `slots` INT(11) NOT NULL, + PRIMARY KEY (`identifier`) USING BTREE, + INDEX `slots` (`slots`) USING BTREE +) +COLLATE='utf8mb4_unicode_ci' +ENGINE=InnoDB +; From 54245704c5571e13a6b2bb0e0b906e9f47acd349 Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Sat, 16 Oct 2021 08:31:45 +1100 Subject: [PATCH 28/60] fix(sql): Add disabled user column back and change to TINYINT - Accidentaly removed it; change this to TINYINT for consistency --- esx_multicharacter.sql | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esx_multicharacter.sql b/esx_multicharacter.sql index 23c894a5..71049ffa 100644 --- a/esx_multicharacter.sql +++ b/esx_multicharacter.sql @@ -7,3 +7,6 @@ CREATE TABLE `multicharacter_slots` ( COLLATE='utf8mb4_unicode_ci' ENGINE=InnoDB ; + +ALTER TABLE `users` + `disabled` TINYINT(1) NULL DEFAULT '0', From a4d7d17251f5d47bd9beb39ebb9aa994ceb21114 Mon Sep 17 00:00:00 2001 From: Demetrio <15928886+d3rp-jsx@users.noreply.github.com> Date: Fri, 15 Oct 2021 23:32:54 +0200 Subject: [PATCH 29/60] Minor fixes and compatibility improvements (#54) --- client/main.lua | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/client/main.lua b/client/main.lua index cc3eb09f..b6fa90d8 100644 --- a/client/main.lua +++ b/client/main.lua @@ -178,11 +178,7 @@ if ESX.GetConfig().Multichar then end if Spawned == false then SetupCharacter(Character) end local label = v.firstname..' '..v.lastname - if Characters[k].disabled == 1 then - elements[#elements+1] = {label = label, value = v.id} - else - elements[#elements+1] = {label = label, value = v.id} - end + elements[#elements+1] = {label = label, value = v.id} end if #elements < slots then elements[#elements+1] = {label = _('create_char'), value = (#elements+1), new = true} @@ -194,10 +190,10 @@ if ESX.GetConfig().Multichar then }, function(data, menu) local elements = {} if not data.current.new then - if Characters[data.current.value].disabled == 0 then - elements[1] = {label = _('char_play'), action = 'play', value = data.current.value} - else + if Characters[data.current.value].disabled then elements[1] = {label = _('char_disabled'), value = data.current.value} + else + elements[1] = {label = _('char_play'), action = 'play', value = data.current.value} end if Config.CanDelete then elements[2] = {label = _('char_delete'), action = 'delete', value = data.current.value} end ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'choosechar', { From ef87a916acab03330c5fa3231cf2919e61ba5de0 Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Sat, 16 Oct 2021 08:34:14 +1100 Subject: [PATCH 30/60] fix(client): Entity coords should no longer set you into the ground --- client/main.lua | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/client/main.lua b/client/main.lua index b6fa90d8..db5fc7ec 100644 --- a/client/main.lua +++ b/client/main.lua @@ -16,15 +16,14 @@ if ESX.GetConfig().Multichar then end end) - - local canRelog, cam, Spawned = true + local canRelog, cam, spawned = true, nil, nil local Characters = {} RegisterNetEvent('esx_multicharacter:SetupCharacters') AddEventHandler('esx_multicharacter:SetupCharacters', function() ESX.PlayerLoaded = false ESX.PlayerData = {} - Spawned = false + spawned = false cam = CreateCam("DEFAULT_SCRIPTED_CAMERA", true) local playerPed = PlayerPedId() SetEntityCoords(playerPed, Config.Spawn.x, Config.Spawn.y, Config.Spawn.z, true, false, false, false) @@ -97,7 +96,7 @@ if ESX.GetConfig().Multichar then end SetupCharacter = function(index) - if Spawned == false then + if spawned == false then exports.spawnmanager:spawnPlayer({ x = Config.Spawn.x, y = Config.Spawn.y, @@ -117,9 +116,9 @@ if ESX.GetConfig().Multichar then DoScreenFadeIn(400) end) repeat Citizen.Wait(200) until not IsScreenFadedOut() - + elseif Characters[index] and Characters[index].skin then - if Characters[Spawned] and Characters[Spawned].model then + if Characters[spawned] and Characters[spawned].model then RequestModel(Characters[index].model) while not HasModelLoaded(Characters[index].model) do RequestModel(Characters[index].model) @@ -130,14 +129,14 @@ if ESX.GetConfig().Multichar then end TriggerEvent('skinchanger:loadSkin', Characters[index].skin) end - Spawned = index + spawned = index local playerPed = PlayerPedId() FreezeEntityPosition(PlayerPedId(), true) SetPedAoBlobRendering(playerPed, true) SetEntityAlpha(playerPed, 255) SendNUIMessage({ action = "openui", - character = Characters[Spawned] + character = Characters[spawned] }) end @@ -145,7 +144,7 @@ if ESX.GetConfig().Multichar then AddEventHandler('esx_multicharacter:SetupUI', function(data, slots) DoScreenFadeOut(0) Characters = data - local slots = slots + slots = slots local elements = {} local Character = next(Characters) exports.spawnmanager:forceRespawn() @@ -176,9 +175,13 @@ if ESX.GetConfig().Multichar then 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 Spawned == false then SetupCharacter(Character) end + if spawned == false then SetupCharacter(Character) end local label = v.firstname..' '..v.lastname - elements[#elements+1] = {label = label, value = v.id} + if Characters[k].disabled then + elements[#elements+1] = {label = label, value = v.id} + else + elements[#elements+1] = {label = label, value = v.id} + end end if #elements < slots then elements[#elements+1] = {label = _('create_char'), value = (#elements+1), new = true} @@ -190,10 +193,10 @@ if ESX.GetConfig().Multichar then }, function(data, menu) local elements = {} if not data.current.new then - if Characters[data.current.value].disabled then - elements[1] = {label = _('char_disabled'), value = data.current.value} + if not Characters[data.current.value].disabled then + elements[1] = {label = _('char_play'), action = 'play', value = data.current.value} else - elements[1] = {label = _('char_play'), action = 'play', value = data.current.value} + elements[1] = {label = _('char_disabled'), value = data.current.value} end if Config.CanDelete then elements[2] = {label = _('char_delete'), action = 'delete', value = data.current.value} end ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'choosechar', { @@ -218,7 +221,7 @@ if ESX.GetConfig().Multichar then }, function(data, menu) if data.current.value then TriggerServerEvent('esx_multicharacter:DeleteCharacter', data.current.value) - Spawned = false + spawned = false ESX.UI.Menu.CloseAll() else menu.close() @@ -290,15 +293,15 @@ if ESX.GetConfig().Multichar then repeat Citizen.Wait(200) until finished end DoScreenFadeOut(100) - + SetCamActive(cam, false) RenderScriptCams(false, false, 0, true, true) cam = nil local playerPed = PlayerPedId() FreezeEntityPosition(playerPed, true) - SetEntityCoords(playerPed, spawn.x, spawn.y, spawn.z-1.3, true, false, false, false) + 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 + if not isNew then TriggerEvent('skinchanger:loadSkin', skin or Characters[spawned].skin) end Citizen.Wait(400) DoScreenFadeIn(400) repeat Citizen.Wait(200) until not IsScreenFadedOut() @@ -312,7 +315,7 @@ if ESX.GetConfig().Multichar then RegisterNetEvent('esx:onPlayerLogout') AddEventHandler('esx:onPlayerLogout', function() DoScreenFadeOut(0) - Spawned = false + spawned = false TriggerEvent("esx_multicharacter:SetupCharacters") TriggerEvent('esx_skin:resetFirstSpawn') end) From ae41811d42fd316bf181dc8a9d76f1dd530cf0bb Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Sat, 16 Oct 2021 08:34:34 +1100 Subject: [PATCH 31/60] Delete functions.lua --- server/functions.lua | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 server/functions.lua diff --git a/server/functions.lua b/server/functions.lua deleted file mode 100644 index a1312e0a..00000000 --- a/server/functions.lua +++ /dev/null @@ -1,8 +0,0 @@ -GetJobs = function() - local jobs = {} - while next(jobs) == nil do - Citizen.Wait(250) - jobs = exports['es_extended']:getSharedObject().Jobs - end - return jobs -end \ No newline at end of file From 2164f3db72d97bf82c1d9a9565e02a857640218e Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Sat, 16 Oct 2021 08:34:55 +1100 Subject: [PATCH 32/60] style(commands): Indentation --- server/commands.lua | 108 ++++++++++++++++++++++---------------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/server/commands.lua b/server/commands.lua index b4fbc2d2..6c0ff047 100644 --- a/server/commands.lua +++ b/server/commands.lua @@ -1,79 +1,79 @@ ESX.RegisterCommand('setslots', 'admin', function(xPlayer, args, showError) - local slots = MySQL.Sync.fetchScalar('SELECT `slots` FROM `multicharacter_slots` WHERE identifier = ?', { - args.identifier - }) + local slots = MySQL.Sync.fetchScalar('SELECT `slots` FROM `multicharacter_slots` WHERE identifier = ?', { + args.identifier + }) - if slots == nil then - MySQL.Async.execute('INSERT INTO `multicharacter_slots` (`identifier`, `slots`) VALUES (?, ?)', { - args.identifier, - args.slots - }) - xPlayer.triggerEvent('esx:showNotification', _U('slotsadd', args.slots, args.identifier)) - else - MySQL.Async.execute('UPDATE `multicharacter_slots` SET `slots` = ? WHERE `identifier` = ?', { - args.slots, - args.identifier - }) - xPlayer.triggerEvent('esx:showNotification', _U('slotsedit', args.slots, args.identifier)) - end + if slots == nil then + MySQL.Async.execute('INSERT INTO `multicharacter_slots` (`identifier`, `slots`) VALUES (?, ?)', { + args.identifier, + args.slots + }) + xPlayer.triggerEvent('esx:showNotification', _U('slotsadd', args.slots, args.identifier)) + else + MySQL.Async.execute('UPDATE `multicharacter_slots` SET `slots` = ? WHERE `identifier` = ?', { + args.slots, + args.identifier + }) + xPlayer.triggerEvent('esx:showNotification', _U('slotsedit', args.slots, args.identifier)) + end end, true, {help = _U('command_setslots'), validate = true, arguments = { - {name = 'identifier', help = _U('command_identifier'), type = 'string'}, - {name = 'slots', help = _U('command_slots'), type = 'number'} + {name = 'identifier', help = _U('command_identifier'), type = 'string'}, + {name = 'slots', help = _U('command_slots'), type = 'number'} }}) ESX.RegisterCommand('remslots', 'admin', function(xPlayer, args, showError) - local slots = MySQL.Sync.fetchScalar('SELECT `slots` FROM `multicharacter_slots` WHERE identifier = ?', { - args.identifier - }) + local slots = MySQL.Sync.fetchScalar('SELECT `slots` FROM `multicharacter_slots` WHERE identifier = ?', { + args.identifier + }) - if slots ~= nil then - MySQL.Async.execute('DELETE FROM `multicharacter_slots` WHERE `identifier` = ?', { - args.identifier - }) - xPlayer.triggerEvent('esx:showNotification', _U('slotsrem', args.identifier)) - end + if slots ~= nil then + MySQL.Async.execute('DELETE FROM `multicharacter_slots` WHERE `identifier` = ?', { + args.identifier + }) + xPlayer.triggerEvent('esx:showNotification', _U('slotsrem', args.identifier)) + end end, true, {help = _U('command_remslots'), validate = true, arguments = { - {name = 'identifier', help = _U('command_identifier'), type = 'string'} + {name = 'identifier', help = _U('command_identifier'), type = 'string'} }}) ESX.RegisterCommand('enablechar', 'admin', function(xPlayer, args, showError) - local selectedCharacter = 'char'..args.charslot..':'..args.identifier; + local selectedCharacter = 'char'..args.charslot..':'..args.identifier; - MySQL.Async.execute('UPDATE `users` SET `disabled` = 0 WHERE identifier = ?', { - selectedCharacter - }, function(result) - if result > 0 then - xPlayer.triggerEvent('esx:showNotification', _U('charenabled', args.charslot, args.identifier)) - else - xPlayer.triggerEvent('esx:showNotification', _U('charnotfound', args.charslot, args.identifier)) - end - end) + MySQL.Async.execute('UPDATE `users` SET `disabled` = 0 WHERE identifier = ?', { + selectedCharacter + }, function(result) + if result > 0 then + xPlayer.triggerEvent('esx:showNotification', _U('charenabled', args.charslot, args.identifier)) + else + xPlayer.triggerEvent('esx:showNotification', _U('charnotfound', args.charslot, args.identifier)) + end + end) end, true, {help = _U('command_enablechar'), validate = true, arguments = { - {name = 'identifier', help = _U('command_identifier'), type = 'string'}, - {name = 'charslot', help = _U('command_charslot'), type = 'number'} + {name = 'identifier', help = _U('command_identifier'), type = 'string'}, + {name = 'charslot', help = _U('command_charslot'), type = 'number'} }}) ESX.RegisterCommand('disablechar', 'admin', function(xPlayer, args, showError) - local selectedCharacter = 'char'..args.charslot..':'..args.identifier; + local selectedCharacter = 'char'..args.charslot..':'..args.identifier; - MySQL.Async.execute('UPDATE `users` SET `disabled` = 1 WHERE identifier = ?', { - selectedCharacter - }, function(result) - if result > 0 then - xPlayer.triggerEvent('esx:showNotification', _U('chardisabled', args.charslot, args.identifier)) - else - xPlayer.triggerEvent('esx:showNotification', _U('charnotfound', args.charslot, args.identifier)) - end - end) + MySQL.Async.execute('UPDATE `users` SET `disabled` = 1 WHERE identifier = ?', { + selectedCharacter + }, function(result) + if result > 0 then + xPlayer.triggerEvent('esx:showNotification', _U('chardisabled', args.charslot, args.identifier)) + else + xPlayer.triggerEvent('esx:showNotification', _U('charnotfound', args.charslot, args.identifier)) + end + end) end, true, {help = _U('command_disablechar'), validate = true, arguments = { - {name = 'identifier', help = _U('command_identifier'), type = 'string'}, - {name = 'charslot', help = _U('command_charslot'), type = 'number'} + {name = 'identifier', help = _U('command_identifier'), type = 'string'}, + {name = 'charslot', help = _U('command_charslot'), type = 'number'} }}) RegisterCommand('forcelog', function(source, args, rawCommand) - TriggerEvent('esx:playerLogout', source) -end, true) \ No newline at end of file + TriggerEvent('esx:playerLogout', source) +end, true) From 95e9f2bbe881922c487bcf58a69f5298a2d14ad0 Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Sat, 16 Oct 2021 08:43:51 +1100 Subject: [PATCH 33/60] feat(server): Parse mysql_connection_string for database --- server/main.lua | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/server/main.lua b/server/main.lua index 09617693..d167a5fe 100644 --- a/server/main.lua +++ b/server/main.lua @@ -1,13 +1,32 @@ +local function DATABASE() + 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 string.match(connectionString, 'mysql://') then + connectionString = string.sub(connectionString, string.find(connectionString, "/[^/]*$")+1) + return connectionString + else + connectionString = {string.strsplit(';', connectionString)} + for i=1, #connectionString do + local v = connectionString[i] + if v:match('database') then + return v:sub(10, #v) + end + end + end +end + if not ESX then - SetTimeout(3000, function() print('[^3WARNING^7] Unable to start Multicharacter - your version of ESX is not compatible ') end) + error('\n^1WARNING: Unable to start Multicharacter - you must be using ESX Legacy^0') elseif ESX.GetConfig().Multichar == true then - local IdentifierTables, Fetch = {}, -1 - - -- enter the name of your database here - Config.Database = 'es_extended' - + DATABASE = DATABASE() + local DB_TABLES = {} + local FETCH = nil + local PREFIX = 'char' -- enter a prefix to prepend to all user identifiers (keep it short) - Config.Prefix = 'char' + -- if you modify this, you will need to modify es_extended due to an oversight! + -- https://github.com/esx-framework/esx-legacy/blob/main/%5Besx%5D/es_extended/server/classes/player.lua#L17 + -- if Config.Multichar then self.license = 'license'..identifier:sub(identifier:find(':')) else self.license = 'license:'..identifier end local SetupCharacters = function(playerId) while Fetch == -1 do Citizen.Wait(500) end From f46277e2620d5dccdc348f431c1c9256cf3fcee4 Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Sat, 16 Oct 2021 09:03:31 +1100 Subject: [PATCH 34/60] feat(server): Improve GetJobs function - If using an ESX version with the getter, use it instead for better performance - General refactor and ensure compatibility with oxmysql --- server/main.lua | 53 +++++++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/server/main.lua b/server/main.lua index d167a5fe..70fdff55 100644 --- a/server/main.lua +++ b/server/main.lua @@ -29,18 +29,15 @@ elseif ESX.GetConfig().Multichar == true then -- if Config.Multichar then self.license = 'license'..identifier:sub(identifier:find(':')) else self.license = 'license:'..identifier end local SetupCharacters = function(playerId) - while Fetch == -1 do Citizen.Wait(500) end - local identifier = Config.Prefix..'%:'..ESX.GetIdentifier(playerId) - local slots = MySQL.Sync.fetchScalar("SELECT `slots` FROM `multicharacter_slots` WHERE identifier = ?", { - ESX.GetIdentifier(playerId) - }) - if not slots then - slots = Config.Slots - end - MySQL.Async.fetchAll(Fetch, { - identifier, - slots - }, function(result) + repeat Citizen.Wait(200) until FETCH + local identifier = ESX.GetIdentifier(playerId) + local slots = MySQL.Sync.fetchScalar("SELECT slots FROM multicharacter_slots WHERE identifier = ?", { + identifier + }) or Config.Slots + + identifier = PREFIX..'%:'..identifier + + MySQL.Async.fetchAll(FETCH, {identifier, slots}, function(result) local characters = {} for i=1, #result, 1 do local job, grade = result[i].job or 'unemployed', tostring(result[i].job_grade) @@ -49,7 +46,7 @@ elseif ESX.GetConfig().Multichar == true then job = ESX.Jobs[job].label end local accounts = json.decode(result[i].accounts) - local id = tonumber(string.sub(result[i].identifier, #Config.Prefix+1, string.find(result[i].identifier, ':')-1)) + local id = tonumber(string.sub(result[i].identifier, #PREFIX+1, string.find(result[i].identifier, ':')-1)) characters[id] = { id = id, bank = accounts.bank, @@ -69,14 +66,14 @@ elseif ESX.GetConfig().Multichar == true then end local DeleteCharacter = function(playerId, charid) - local identifier = Config.Prefix..charid..':'..ESX.GetIdentifier(playerId) + local identifier = ('%s%s:%s'):format(PREFIX, charid, ESX.GetIdentifier(playerId)) local counter = 0 - for k, v in pairs(IdentifierTables) do + for k, v in pairs(DB_TABLES) do MySQL.Async.execute("DELETE FROM "..v.table.." WHERE "..v.column.." = ?", { identifier }, function() counter = counter + 1 - if counter == #IdentifierTables then + if counter == #DB_TABLES then print(('[^2INFO^7] Player [%s] %s has deleted a character (%s)'):format(GetPlayerName(playerId), playerId, identifier)) Citizen.Wait(100) SetupCharacters(playerId) @@ -87,14 +84,14 @@ elseif ESX.GetConfig().Multichar == true then end MySQL.ready(function() - MySQL.Async.fetchAll('SELECT `TABLE_NAME`, `COLUMN_NAME`, `CHARACTER_MAXIMUM_LENGTH` FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = ? AND (COLUMN_NAME = ? OR COLUMN_NAME = ?) ', { - Config.Database, 'identifier', 'owner' + MySQL.Async.fetchAll('SELECT TABLE_NAME, COLUMN_NAME, CHARACTER_MAXIMUM_LENGTH FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = ? AND (COLUMN_NAME = ? OR COLUMN_NAME = ?) ', { + DATABASE, 'identifier', 'owner' }, function(result) if result then local varchar, varsize = {}, 0 for k, v in pairs(result) do if v.CHARACTER_MAXIMUM_LENGTH and v.CHARACTER_MAXIMUM_LENGTH >= 40 and v.CHARACTER_MAXIMUM_LENGTH < 60 then varchar[v.TABLE_NAME] = v.COLUMN_NAME varsize = varsize+1 end - table.insert(IdentifierTables, {table = v.TABLE_NAME, column = v.COLUMN_NAME}) + table.insert(DB_TABLES, {table = v.TABLE_NAME, column = v.COLUMN_NAME}) end if next(varchar) then for k, v in pairs(varchar) do @@ -102,8 +99,16 @@ elseif ESX.GetConfig().Multichar == true then end print(('[^2INFO^7] Attempted to update ^5%s^7 columns to use VARCHAR(60)'):format(varsize)) end - if not next(ESX.Jobs) then ESX.Jobs = GetJobs() end - Fetch = MySQL.Sync.store("SELECT `identifier`, `accounts`, `job`, `job_grade`, `firstname`, `lastname`, `dateofbirth`, `sex`, `skin`, `disabled` FROM `users` WHERE identifier LIKE ? LIMIT ?") + ESX.Jobs = {} + local function GetJobs() + if ESX.GetJobs then return ESX.GetJobs() + else return exports['es_extended']:getSharedObject().Jobs end + end + repeat + ESX.Jobs = GetJobs() + Citizen.Wait(50) + until next(ESX.Jobs) + FETCH = MySQL.Sync.store("SELECT identifier, accounts, job, job_grade, firstname, lastname, dateofbirth, sex, skin, disabled FROM users WHERE identifier LIKE ? LIMIT ?") end end) end) @@ -121,13 +126,13 @@ elseif ESX.GetConfig().Multichar == true then if isNew then awaitingRegistration[src] = charid else - TriggerEvent('esx:onPlayerJoined', src, Config.Prefix..charid) + TriggerEvent('esx:onPlayerJoined', src, PREFIX..charid) end end end) AddEventHandler('esx_identity:completedRegistration', function(playerId, data) - TriggerEvent('esx:onPlayerJoined', playerId, Config.Prefix..awaitingRegistration[playerId], data) + TriggerEvent('esx:onPlayerJoined', playerId, PREFIX..awaitingRegistration[playerId], data) awaitingRegistration[playerId] = nil end) @@ -150,5 +155,5 @@ elseif ESX.GetConfig().Multichar == true then end) else - SetTimeout(3000, function() print('[^3WARNING^7] Multicharacter is disabled - please check your ESX configuration') end) + assert(nil, '^3WARNING: Multicharacter is disabled - please check your ESX configuration^0') end From d594c5db82aa53090119140502dc48acb0a3a43a Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Sat, 16 Oct 2021 09:04:38 +1100 Subject: [PATCH 35/60] chore(manifest): Update versions --- fxmanifest.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fxmanifest.lua b/fxmanifest.lua index 617f933c..92bd87ad 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -1,7 +1,8 @@ -fx_version 'adamant' +fx_version 'cerulean' game 'gta5' description 'https://github.com/thelindat/esx_multicharacter' -version '1.2.2' +version '1.3' +lua54 'yes' dependencies { 'es_extended', @@ -19,7 +20,7 @@ shared_scripts { server_scripts { '@es_extended/imports.lua', '@mysql-async/lib/MySQL.lua', - 'server/*.lua' + 'server/*.lua', } client_scripts { From 749dfd0ec3391b580b451485480224ce862989b2 Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Sat, 16 Oct 2021 09:05:13 +1100 Subject: [PATCH 36/60] chore(config): Change defaults --- config.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.lua b/config.lua index ca56aec7..f3d9d763 100644 --- a/config.lua +++ b/config.lua @@ -4,13 +4,13 @@ Config.Locale = 'en' -------------------- -- This is the default number of slots for EVERY player -- If you want to manage extra slots for specific players you can do it by using '/setslots' and '/remslots' commands -Config.Slots = 1 +Config.Slots = 4 -------------------- Config.Spawn = vector4(-113.7, 565.3, 195.2, 0) -- Sets the location for character selection -- To set the spawn location for new characters, modify the default value in the `users` SQL table -Config.CanDelete = false -- Choose whether or not a user can self-delete its own characters +Config.CanDelete = true -- Choose whether or not a user can self-delete its own characters -------------------- -- Do not use unless you are prepared to adjust your resources to correctly reset data From 7995739cecd6a3cc68cbec4e6438b87c2317afc9 Mon Sep 17 00:00:00 2001 From: DokaDoka Date: Sat, 16 Oct 2021 10:06:33 +1100 Subject: [PATCH 37/60] fix(sql): fix disabled user column query --- esx_multicharacter.sql | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/esx_multicharacter.sql b/esx_multicharacter.sql index 71049ffa..b3a76af7 100644 --- a/esx_multicharacter.sql +++ b/esx_multicharacter.sql @@ -8,5 +8,6 @@ COLLATE='utf8mb4_unicode_ci' ENGINE=InnoDB ; -ALTER TABLE `users` - `disabled` TINYINT(1) NULL DEFAULT '0', +ALTER TABLE `users` ADD COLUMN + `disabled` TINYINT(1) NULL DEFAULT '0' +; From 565c5d0e35b2749479b40722d1abe004aff36554 Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Mon, 18 Oct 2021 07:33:01 +1100 Subject: [PATCH 38/60] Update readme.md --- readme.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index dcca296f..f7f30478 100644 --- a/readme.md +++ b/readme.md @@ -1,8 +1,9 @@ ### Requirements (ensure you are using the latest) - [ESX Legacy](https://github.com/esx-framework/esx-legacy) -- [MySQL Async 3.3.2](https://github.com/brouznouf/fivem-mysql-async/releases/tag/3.3.2) - [Spawnmanager](https://github.com/citizenfx/cfx-server-data/tree/master/resources/%5Bmanagers%5D/spawnmanager) +- [OxMySQL](https://github.com/overextended/oxmysql/) or [MySQL Async 3.3.2](https://github.com/brouznouf/fivem-mysql-async/releases/tag/3.3.2) + ### Installation - Modify your ESX config with `Config.Multichar = true` - Set your database name for `Config.Database` in server/main.lua From ebae4354cec182d21475bd6bf4d5031f55cbe93a Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Sat, 23 Oct 2021 11:58:18 +1100 Subject: [PATCH 39/60] fix(server): Correctly resolve database name for URI connection strings URI-connection strings would incorrectly parse the database name when using additional options as part of the string (i.e. "ESXLegacy?charset=utf8mb4"). Correctly check for these options and match the string accordingly. Also just better pattern matching in general. --- server/main.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/server/main.lua b/server/main.lua index 70fdff55..b6eec4d5 100644 --- a/server/main.lua +++ b/server/main.lua @@ -2,9 +2,12 @@ local function DATABASE() 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 string.match(connectionString, 'mysql://') then - connectionString = string.sub(connectionString, string.find(connectionString, "/[^/]*$")+1) - return connectionString + elseif connectionString:find('mysql://') then + if connectionString:find('?*$') then + return connectionString:match('[^/][%w]*[?$]'):sub(0, -2) + else + return connectionString:match('[^/][%w]*$') + end else connectionString = {string.strsplit(';', connectionString)} for i=1, #connectionString do From 870e1887b0e8d77d1aac23eef29809ef374ecf01 Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Sat, 23 Oct 2021 11:58:59 +1100 Subject: [PATCH 40/60] Update fxmanifest.lua --- fxmanifest.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fxmanifest.lua b/fxmanifest.lua index 92bd87ad..7f98c72d 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -1,7 +1,7 @@ fx_version 'cerulean' game 'gta5' description 'https://github.com/thelindat/esx_multicharacter' -version '1.3' +version '1.3.1' lua54 'yes' dependencies { From 8255fa2014715e7c7c0edfc2a2eecfdba86c1225 Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Mon, 25 Oct 2021 15:47:57 +1100 Subject: [PATCH 41/60] feat(deferrals): Prevent connection when identifier is in use Only recently came to my attention that this was a concern. Since ESX and identity only check a raw identifier vs a player identifier (which contains a prefix) it doesn't prevent multiple logins. --- server/main.lua | 51 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/server/main.lua b/server/main.lua index b6eec4d5..4c482dd1 100644 --- a/server/main.lua +++ b/server/main.lua @@ -31,9 +31,25 @@ elseif ESX.GetConfig().Multichar == true then -- https://github.com/esx-framework/esx-legacy/blob/main/%5Besx%5D/es_extended/server/classes/player.lua#L17 -- if Config.Multichar then self.license = 'license'..identifier:sub(identifier:find(':')) else self.license = 'license:'..identifier end - local SetupCharacters = function(playerId) + local function GetIdentifier(playerId) + local identifier = 'license:' + for _, v in pairs(GetPlayerIdentifiers(playerId)) do + if string.match(v, identifier) then + identifier = string.gsub(v, identifier, '') + return identifier + end + end + end + + if next(ESX.Players) then + for k, v in pairs(ESX.Players) do + ESX.Players[k] = GetIdentifier(v.source) + end + else ESX.Players = {} end + + local function SetupCharacters(playerId) repeat Citizen.Wait(200) until FETCH - local identifier = ESX.GetIdentifier(playerId) + local identifier = GetIdentifier(playerId) local slots = MySQL.Sync.fetchScalar("SELECT slots FROM multicharacter_slots WHERE identifier = ?", { identifier }) or Config.Slots @@ -68,16 +84,29 @@ elseif ESX.GetConfig().Multichar == true then end) end - local DeleteCharacter = function(playerId, charid) - local identifier = ('%s%s:%s'):format(PREFIX, charid, ESX.GetIdentifier(playerId)) + AddEventHandler('playerConnecting', function(playerName, setKickReason, deferrals) + deferrals.defer() + local identifier = GetIdentifier(source) + Citizen.Wait(100) + if ESX.Players[identifier] then + deferrals.done('A player is already connected to the server with this identifier.') + else + ESX.Players[identifier] = true + deferrals.done() + end + end) + + local function DeleteCharacter(playerId, charid) + local identifier = GetIdentifier(playerId) + local character = ('%s%s:%s'):format(PREFIX, charid, identifier) local counter = 0 - for k, v in pairs(DB_TABLES) do + for _, v in pairs(DB_TABLES) do MySQL.Async.execute("DELETE FROM "..v.table.." WHERE "..v.column.." = ?", { - identifier + character }, function() counter = counter + 1 if counter == #DB_TABLES then - print(('[^2INFO^7] Player [%s] %s has deleted a character (%s)'):format(GetPlayerName(playerId), playerId, identifier)) + print(('[^2INFO^7] Player [%s] %s has deleted a character (%s)'):format(GetPlayerName(playerId), playerId, character)) Citizen.Wait(100) SetupCharacters(playerId) end @@ -124,12 +153,12 @@ elseif ESX.GetConfig().Multichar == true then local awaitingRegistration = {} RegisterServerEvent("esx_multicharacter:CharacterChosen") AddEventHandler('esx_multicharacter:CharacterChosen', function(charid, isNew) - local src = source if type(charid) == 'number' and string.len(charid) <= 2 and type(isNew) == 'boolean' then if isNew then - awaitingRegistration[src] = charid + awaitingRegistration[source] = charid else - TriggerEvent('esx:onPlayerJoined', src, PREFIX..charid) + TriggerEvent('esx:onPlayerJoined', source, PREFIX..charid) + ESX.Players[GetIdentifier(source)] = source end end end) @@ -137,10 +166,12 @@ elseif ESX.GetConfig().Multichar == true then AddEventHandler('esx_identity:completedRegistration', function(playerId, data) TriggerEvent('esx:onPlayerJoined', playerId, PREFIX..awaitingRegistration[playerId], data) awaitingRegistration[playerId] = nil + ESX.Players[GetIdentifier(source)] = playerId end) AddEventHandler('playerDropped', function(reason) awaitingRegistration[source] = nil + ESX.Players[GetIdentifier(source)] = nil end) RegisterServerEvent("esx_multicharacter:DeleteCharacter") From 4f3ce99e94eb9b73fdcf70b3bbad7ac8e8b91f93 Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Tue, 26 Oct 2021 00:19:49 +1100 Subject: [PATCH 42/60] refactor(config): Remove redundant values from being stored on server --- client/main.lua | 4 +- config.lua | 227 +++++++++++++++++++++++++----------------------- 2 files changed, 118 insertions(+), 113 deletions(-) diff --git a/client/main.lua b/client/main.lua index db5fc7ec..9aa1a919 100644 --- a/client/main.lua +++ b/client/main.lua @@ -7,7 +7,7 @@ if ESX.GetConfig().Multichar then if NetworkIsPlayerActive(PlayerId()) then exports.spawnmanager:setAutoSpawn(false) DoScreenFadeOut(0) - while not GetResourceState('esx_menu_default') == 'started' do + while not GetResourceState('esx_menu_default') == 'started' do Citizen.Wait(0) end TriggerEvent("esx_multicharacter:SetupCharacters") @@ -139,7 +139,7 @@ if ESX.GetConfig().Multichar then character = Characters[spawned] }) end - + RegisterNetEvent('esx_multicharacter:SetupUI') AddEventHandler('esx_multicharacter:SetupUI', function(data, slots) DoScreenFadeOut(0) diff --git a/config.lua b/config.lua index f3d9d763..460c7e44 100644 --- a/config.lua +++ b/config.lua @@ -1,117 +1,122 @@ Config = {} Config.Locale = 'en' --------------------- --- This is the default number of slots for EVERY player --- If you want to manage extra slots for specific players you can do it by using '/setslots' and '/remslots' commands -Config.Slots = 4 --------------------- +if IsDuplicityVersion() then + -- This is the default number of slots for EVERY player + -- If you want to manage extra slots for specific players you can do it by using '/setslots' and '/remslots' commands + Config.Slots = 4 + -------------------- +else + -- Sets the location for the character selection scene + -- To set the spawn location for new characters, modify the default value in the `users` SQL table + Config.Spawn = vector4(-113.7, 565.3, 195.2, 0) + -------------------- -Config.Spawn = vector4(-113.7, 565.3, 195.2, 0) -- Sets the location for character selection --- To set the spawn location for new characters, modify the default value in the `users` SQL table + -- Allows players to delete their characters + Config.CanDelete = true + -------------------- -Config.CanDelete = true -- Choose whether or not a user can self-delete its own characters + -- Do not use unless you are prepared to adjust your resources to correctly reset data + -- Information: https://github.com/thelindat/esx_multicharacter#relogging + Config.Relog = false + -------------------- --------------------- --- Do not use unless you are prepared to adjust your resources to correctly reset data --- Information: https://github.com/thelindat/esx_multicharacter#relogging -Config.Relog = false --------------------- - -Config.Default = { - mom = 21, - dad = 0, - face_md_weight = 50, - skin_md_weight = 50, - nose_1 = 0, - nose_2 = 0, - nose_3 = 0, - nose_4 = 0, - nose_5 = 0, - nose_6 = 0, - cheeks_1 = 0, - cheeks_2 = 0, - cheeks_3 = 0, - lip_thickness = 0, - jaw_1 = 0, - jaw_2 = 0, - chin_1 = 0, - chin_2 = 0, - chin_13 = 0, - chin_4 = 0, - neck_thickness = 0, - hair_1 = 0, - hair_2 = 0, - hair_color_1 = 0, - hair_color_2 = 0, - tshirt_1 = 0, - tshirt_2 = 0, - torso_1 = 0, - torso_2 = 0, - decals_1 = 0, - decals_2 = 0, - arms = 0, - arms_2 = 0, - pants_1 = 0, - pants_2 = 0, - shoes_1 = 0, - shoes_2 = 0, - mask_1 = 0, - mask_2 = 0, - bproof_1 = 0, - bproof_2 = 0, - chain_1 = 0, - chain_2 = 0, - helmet_1 = -1, - helmet_2 = 0, - glasses_1 = 0, - glasses_2 = 0, - watches_1 = -1, - watches_2 = 0, - bracelets_1 = -1, - bracelets_2 = 0, - bags_1 = 0, - bags_2 = 0, - eye_color = 0, - eye_squint = 0, - eyebrows_2 = 0, - eyebrows_1 = 0, - eyebrows_3 = 0, - eyebrows_4 = 0, - eyebrows_5 = 0, - eyebrows_6 = 0, - makeup_1 = 0, - makeup_2 = 0, - makeup_3 = 0, - makeup_4 = 0, - lipstick_1 = 0, - lipstick_2 = 0, - lipstick_3 = 0, - lipstick_4 = 0, - ears_1 = -1, - ears_2 = 0, - chest_1 = 0, - chest_2 = 0, - chest_3 = 0, - bodyb_1 = -1, - bodyb_2 = 0, - bodyb_3 = -1, - bodyb_4 = 0, - age_1 = 0, - age_2 = 0, - blemishes_1 = 0, - blemishes_2 = 0, - blush_1 = 0, - blush_2 = 0, - blush_3 = 0, - complexion_1 = 0, - complexion_2 = 0, - sun_1 = 0, - sun_2 = 0, - moles_1 = 0, - moles_2 = 0, - beard_1 = 0, - beard_2 = 0, - beard_3 = 0, - beard_4 = 0 -} + -- Default appearance for new characters + Config.Default = { + mom = 21, + dad = 0, + face_md_weight = 50, + skin_md_weight = 50, + nose_1 = 0, + nose_2 = 0, + nose_3 = 0, + nose_4 = 0, + nose_5 = 0, + nose_6 = 0, + cheeks_1 = 0, + cheeks_2 = 0, + cheeks_3 = 0, + lip_thickness = 0, + jaw_1 = 0, + jaw_2 = 0, + chin_1 = 0, + chin_2 = 0, + chin_13 = 0, + chin_4 = 0, + neck_thickness = 0, + hair_1 = 0, + hair_2 = 0, + hair_color_1 = 0, + hair_color_2 = 0, + tshirt_1 = 0, + tshirt_2 = 0, + torso_1 = 0, + torso_2 = 0, + decals_1 = 0, + decals_2 = 0, + arms = 0, + arms_2 = 0, + pants_1 = 0, + pants_2 = 0, + shoes_1 = 0, + shoes_2 = 0, + mask_1 = 0, + mask_2 = 0, + bproof_1 = 0, + bproof_2 = 0, + chain_1 = 0, + chain_2 = 0, + helmet_1 = -1, + helmet_2 = 0, + glasses_1 = 0, + glasses_2 = 0, + watches_1 = -1, + watches_2 = 0, + bracelets_1 = -1, + bracelets_2 = 0, + bags_1 = 0, + bags_2 = 0, + eye_color = 0, + eye_squint = 0, + eyebrows_2 = 0, + eyebrows_1 = 0, + eyebrows_3 = 0, + eyebrows_4 = 0, + eyebrows_5 = 0, + eyebrows_6 = 0, + makeup_1 = 0, + makeup_2 = 0, + makeup_3 = 0, + makeup_4 = 0, + lipstick_1 = 0, + lipstick_2 = 0, + lipstick_3 = 0, + lipstick_4 = 0, + ears_1 = -1, + ears_2 = 0, + chest_1 = 0, + chest_2 = 0, + chest_3 = 0, + bodyb_1 = -1, + bodyb_2 = 0, + bodyb_3 = -1, + bodyb_4 = 0, + age_1 = 0, + age_2 = 0, + blemishes_1 = 0, + blemishes_2 = 0, + blush_1 = 0, + blush_2 = 0, + blush_3 = 0, + complexion_1 = 0, + complexion_2 = 0, + sun_1 = 0, + sun_2 = 0, + moles_1 = 0, + moles_2 = 0, + beard_1 = 0, + beard_2 = 0, + beard_3 = 0, + beard_4 = 0 + } +end \ No newline at end of file From c9749c0b4079d8c32a03c7641a665fc06dc04a0c Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Tue, 26 Oct 2021 00:20:59 +1100 Subject: [PATCH 43/60] refactor(server): Remove redundant local values and check result size --- server/main.lua | 76 +++++++++++++++++++++++++------------------------ 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/server/main.lua b/server/main.lua index 4c482dd1..e6179a71 100644 --- a/server/main.lua +++ b/server/main.lua @@ -31,9 +31,9 @@ elseif ESX.GetConfig().Multichar == true then -- https://github.com/esx-framework/esx-legacy/blob/main/%5Besx%5D/es_extended/server/classes/player.lua#L17 -- if Config.Multichar then self.license = 'license'..identifier:sub(identifier:find(':')) else self.license = 'license:'..identifier end - local function GetIdentifier(playerId) + local function GetIdentifier(source) local identifier = 'license:' - for _, v in pairs(GetPlayerIdentifiers(playerId)) do + for _, v in pairs(GetPlayerIdentifiers(source)) do if string.match(v, identifier) then identifier = string.gsub(v, identifier, '') return identifier @@ -47,40 +47,44 @@ elseif ESX.GetConfig().Multichar == true then end else ESX.Players = {} end - local function SetupCharacters(playerId) - repeat Citizen.Wait(200) until FETCH - local identifier = GetIdentifier(playerId) + local function SetupCharacters(source) + while not FETCH do Citizen.Wait(100) end + local identifier = GetIdentifier(source) local slots = MySQL.Sync.fetchScalar("SELECT slots FROM multicharacter_slots WHERE identifier = ?", { identifier }) or Config.Slots - - identifier = PREFIX..'%:'..identifier + identifier = ('%s:%s'):format(PREFIX, identifier) MySQL.Async.fetchAll(FETCH, {identifier, slots}, function(result) - local characters = {} - for i=1, #result, 1 do - local job, grade = result[i].job or 'unemployed', tostring(result[i].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 + local characters + if result then + local characterCount = #result + characters = table.create(0, characterCount) + for i=1, characterCount, 1 do + local i = result[i] + local job, grade = i.job or 'unemployed', tostring(i.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(i.accounts) + local id = tonumber(string.sub(i.identifier, #PREFIX+1, string.find(i.identifier, ':')-1)) + characters[id] = { + id = id, + bank = accounts.bank, + money = accounts.money, + job = job, + job_grade = grade, + firstname = i.firstname, + lastname = i.lastname, + dateofbirth = i.dateofbirth, + skin = json.decode(i.skin), + disabled = i.disabled, + sex = i.sex == 'm' and _('male') or _('female') + } end - local accounts = json.decode(result[i].accounts) - local id = tonumber(string.sub(result[i].identifier, #PREFIX+1, string.find(result[i].identifier, ':')-1)) - characters[id] = { - id = id, - bank = accounts.bank, - money = accounts.money, - job = job, - job_grade = grade, - firstname = result[i].firstname, - lastname = result[i].lastname, - dateofbirth = result[i].dateofbirth, - skin = json.decode(result[i].skin), - disabled = result[i].disabled, - } - if result[i].sex == 'm' then characters[id].sex = _('male') else characters[id].sex = _('female') end end - TriggerClientEvent('esx_multicharacter:SetupUI', playerId, characters, slots) + TriggerClientEvent('esx_multicharacter:SetupUI', source, characters, slots) end) end @@ -163,10 +167,10 @@ elseif ESX.GetConfig().Multichar == true then end end) - AddEventHandler('esx_identity:completedRegistration', function(playerId, data) - TriggerEvent('esx:onPlayerJoined', playerId, PREFIX..awaitingRegistration[playerId], data) - awaitingRegistration[playerId] = nil - ESX.Players[GetIdentifier(source)] = playerId + AddEventHandler('esx_identity:completedRegistration', function(source, data) + TriggerEvent('esx:onPlayerJoined', source, PREFIX..awaitingRegistration[source], data) + awaitingRegistration[source] = nil + ESX.Players[GetIdentifier(source)] = source end) AddEventHandler('playerDropped', function(reason) @@ -176,16 +180,14 @@ elseif ESX.GetConfig().Multichar == true then RegisterServerEvent("esx_multicharacter:DeleteCharacter") AddEventHandler('esx_multicharacter:DeleteCharacter', function(charid) - local src = source if type(charid) == "number" and string.len(charid) <= 2 then - DeleteCharacter(src, charid) + DeleteCharacter(source, charid) end end) RegisterServerEvent("esx_multicharacter:relog") AddEventHandler('esx_multicharacter:relog', function() - local src = source - TriggerEvent('esx:playerLogout', src) + TriggerEvent('esx:playerLogout', source) end) else From e3c7e5ce5c2dc1de56d5102dc01f33698a001293 Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Tue, 26 Oct 2021 00:53:06 +1100 Subject: [PATCH 44/60] feat(server): Utilise transactions to reduce server load --- fxmanifest.lua | 2 +- server/main.lua | 50 ++++++++++++++++++++++++++++++------------------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/fxmanifest.lua b/fxmanifest.lua index 7f98c72d..992b8ce1 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -1,7 +1,7 @@ fx_version 'cerulean' game 'gta5' description 'https://github.com/thelindat/esx_multicharacter' -version '1.3.1' +version '1.4' lua54 'yes' dependencies { diff --git a/server/main.lua b/server/main.lua index e6179a71..f4137076 100644 --- a/server/main.lua +++ b/server/main.lua @@ -53,7 +53,7 @@ elseif ESX.GetConfig().Multichar == true then local slots = MySQL.Sync.fetchScalar("SELECT slots FROM multicharacter_slots WHERE identifier = ?", { identifier }) or Config.Slots - identifier = ('%s:%s'):format(PREFIX, identifier) + identifier = PREFIX..'%:'..identifier MySQL.Async.fetchAll(FETCH, {identifier, slots}, function(result) local characters @@ -100,23 +100,25 @@ elseif ESX.GetConfig().Multichar == true then end end) - local function DeleteCharacter(playerId, charid) - local identifier = GetIdentifier(playerId) - local character = ('%s%s:%s'):format(PREFIX, charid, identifier) - local counter = 0 - for _, v in pairs(DB_TABLES) do - MySQL.Async.execute("DELETE FROM "..v.table.." WHERE "..v.column.." = ?", { - character - }, function() - counter = counter + 1 - if counter == #DB_TABLES then - print(('[^2INFO^7] Player [%s] %s has deleted a character (%s)'):format(GetPlayerName(playerId), playerId, character)) - Citizen.Wait(100) - SetupCharacters(playerId) - end - end) - Citizen.Wait(5) + local function DeleteCharacter(source, charid) + local identifier = ('%s%s:%s'):format(PREFIX, charid, GetIdentifier(source)) + local query = "DELETE FROM ?? WHERE ?? = ?" + local tableCount = #DB_TABLES + local queries = table.create(tableCount, 0) + + for i=1, tableCount do + local v = DB_TABLES[i] + queries[i] = {query = query, values = {v.table, v.column, identifier}} end + + MySQL.Async.transaction(queries, function(result) + if result then + print(('[^2INFO^7] Player [%s] %s has deleted a character (%s)'):format(GetPlayerName(source), source, identifier)) + SetupCharacters(source) + else + print(result) + end + end) end MySQL.ready(function() @@ -130,10 +132,20 @@ elseif ESX.GetConfig().Multichar == true then table.insert(DB_TABLES, {table = v.TABLE_NAME, column = v.COLUMN_NAME}) end if next(varchar) then + local query = "ALTER TABLE ?? MODIFY COLUMN ?? VARCHAR(60)" + local queries = table.create(varsize, 0) + for k, v in pairs(varchar) do - MySQL.Sync.execute("ALTER TABLE "..k.." MODIFY COLUMN "..v.." VARCHAR(60)") + queries[#queries+1] = {query = query, values = {k, v}} end - print(('[^2INFO^7] Attempted to update ^5%s^7 columns to use VARCHAR(60)'):format(varsize)) + + MySQL.Async.transaction(queries, function(result) + if result then + print(('[^2INFO^7] Updated ^5%s^7 columns to use VARCHAR(60)'):format(varsize)) + else + print(('[^2INFO^7] Unable to update ^5%s^7 columns to use VARCHAR(60)'):format(varsize)) + end + end) end ESX.Jobs = {} local function GetJobs() From 3fe1f438df6ccd2d2473fc532e0759324909327a Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Tue, 26 Oct 2021 02:24:06 +1100 Subject: [PATCH 45/60] chore(server): Update error messages and manifest --- fxmanifest.lua | 2 +- server/main.lua | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/fxmanifest.lua b/fxmanifest.lua index 992b8ce1..f39c2340 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -1,7 +1,7 @@ fx_version 'cerulean' game 'gta5' description 'https://github.com/thelindat/esx_multicharacter' -version '1.4' +version '1.4.0' lua54 'yes' dependencies { diff --git a/server/main.lua b/server/main.lua index f4137076..7c3dc1ea 100644 --- a/server/main.lua +++ b/server/main.lua @@ -20,7 +20,7 @@ local function DATABASE() end if not ESX then - error('\n^1WARNING: Unable to start Multicharacter - you must be using ESX Legacy^0') + error('\n^1Unable to start Multicharacter - you must be using ESX Legacy^0') elseif ESX.GetConfig().Multichar == true then DATABASE = DATABASE() local DB_TABLES = {} @@ -114,9 +114,10 @@ elseif ESX.GetConfig().Multichar == true then MySQL.Async.transaction(queries, function(result) if result then print(('[^2INFO^7] Player [%s] %s has deleted a character (%s)'):format(GetPlayerName(source), source, identifier)) + Citizen.Wait(50) SetupCharacters(source) else - print(result) + error('\n^1Transaction failed while trying to delete '..identifier..'^0') end end) end @@ -204,4 +205,4 @@ elseif ESX.GetConfig().Multichar == true then else assert(nil, '^3WARNING: Multicharacter is disabled - please check your ESX configuration^0') -end +end \ No newline at end of file From a9960ef3f5716aa442df84b2b2732cfd3f554977 Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Fri, 29 Oct 2021 11:09:30 +1100 Subject: [PATCH 46/60] refactor: move server settings to config Added support for sv_lan to allow use with FxDK. Added support for ESX forks with Config.Identifier --- config.lua | 11 +++++++++++ server/main.lua | 12 +++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/config.lua b/config.lua index 460c7e44..b5af0abc 100644 --- a/config.lua +++ b/config.lua @@ -6,6 +6,17 @@ if IsDuplicityVersion() then -- If you want to manage extra slots for specific players you can do it by using '/setslots' and '/remslots' commands Config.Slots = 4 -------------------- + + -- Text to prepend to each character (char#:identifier) - keep it short + -- if you modify this, you will need to modify es_extended due to an oversight! + -- https://github.com/esx-framework/esx-legacy/blob/main/%5Besx%5D/es_extended/server/classes/player.lua#L17 + -- if Config.Multichar then self.license = 'license'..identifier:sub(identifier:find(':')) else self.license = 'license:'..identifier end + Config.Prefix = 'char' + -------------------- + + -- Default identifier to store for characters - this should always match es_extended (recommended: license) + Config.Identifier = 'license' + else -- Sets the location for the character selection scene -- To set the spawn location for new characters, modify the default value in the `users` SQL table diff --git a/server/main.lua b/server/main.lua index 7c3dc1ea..27f29eb9 100644 --- a/server/main.lua +++ b/server/main.lua @@ -25,14 +25,12 @@ elseif ESX.GetConfig().Multichar == true then DATABASE = DATABASE() local DB_TABLES = {} local FETCH = nil - local PREFIX = 'char' - -- enter a prefix to prepend to all user identifiers (keep it short) - -- if you modify this, you will need to modify es_extended due to an oversight! - -- https://github.com/esx-framework/esx-legacy/blob/main/%5Besx%5D/es_extended/server/classes/player.lua#L17 - -- if Config.Multichar then self.license = 'license'..identifier:sub(identifier:find(':')) else self.license = 'license:'..identifier end + 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 Config.Identifier local function GetIdentifier(source) - local identifier = 'license:' + local identifier = PRIMARY_IDENTIFIER..':' for _, v in pairs(GetPlayerIdentifiers(source)) do if string.match(v, identifier) then identifier = string.gsub(v, identifier, '') @@ -52,7 +50,7 @@ elseif ESX.GetConfig().Multichar == true then local identifier = GetIdentifier(source) local slots = MySQL.Sync.fetchScalar("SELECT slots FROM multicharacter_slots WHERE identifier = ?", { identifier - }) or Config.Slots + }) or SLOTS identifier = PREFIX..'%:'..identifier MySQL.Async.fetchAll(FETCH, {identifier, slots}, function(result) From 8402cf423659a6b92182354265a062c3b002950d Mon Sep 17 00:00:00 2001 From: Olvidate <82069828+Olvidate@users.noreply.github.com> Date: Mon, 8 Nov 2021 18:55:42 -0300 Subject: [PATCH 47/60] fix(server): fixed error with identifiers Fixed error when you don't have a identifier, Steam open for example. --- server/main.lua | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/server/main.lua b/server/main.lua index 27f29eb9..eb88b6bc 100644 --- a/server/main.lua +++ b/server/main.lua @@ -90,11 +90,15 @@ elseif ESX.GetConfig().Multichar == true then deferrals.defer() local identifier = GetIdentifier(source) Citizen.Wait(100) - if ESX.Players[identifier] then - deferrals.done('A player is already connected to the server with this identifier.') + if identifier then + if ESX.Players[identifier] then + deferrals.done('A player is already connected to the server with this identifier.') + else + ESX.Players[identifier] = true + deferrals.done() + end else - ESX.Players[identifier] = true - deferrals.done() + deferrals.done("You don't have identifier") end end) @@ -203,4 +207,4 @@ elseif ESX.GetConfig().Multichar == true then else assert(nil, '^3WARNING: Multicharacter is disabled - please check your ESX configuration^0') -end \ No newline at end of file +end From e19f99fdcab8ea3c6af700efb5dcb8b497f90396 Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Tue, 9 Nov 2021 14:12:04 +1100 Subject: [PATCH 48/60] refactor(server): update deferral messages --- server/main.lua | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/server/main.lua b/server/main.lua index eb88b6bc..d966d528 100644 --- a/server/main.lua +++ b/server/main.lua @@ -41,14 +41,16 @@ elseif ESX.GetConfig().Multichar == true then if next(ESX.Players) then for k, v in pairs(ESX.Players) do - ESX.Players[k] = GetIdentifier(v.source) + ESX.Players[GetIdentifier(v.source)] = true end else ESX.Players = {} end local function SetupCharacters(source) while not FETCH do Citizen.Wait(100) end local identifier = GetIdentifier(source) - local slots = MySQL.Sync.fetchScalar("SELECT slots FROM multicharacter_slots WHERE identifier = ?", { + ESX.Players[identifier] = true + + local slots = MySQL.Sync.fetchScalar("SELECT slots FROM multicharacter_slots WHERE identifier = ?", { identifier }) or SLOTS identifier = PREFIX..'%:'..identifier @@ -92,13 +94,12 @@ elseif ESX.GetConfig().Multichar == true then Citizen.Wait(100) if identifier then if ESX.Players[identifier] then - deferrals.done('A player is already connected to the server with this identifier.') + deferrals.done(('A player is already connected to the server with this identifier.\nYour identifier: %s:%s'):format(PRIMARY_IDENTIFIER, identifier) else - ESX.Players[identifier] = true deferrals.done() end else - deferrals.done("You don't have identifier") + deferrals.done(('Unable to retrieve player identifier.\nIdentifier type: %s'):format(PRIMARY_IDENTIFIER) end end) @@ -177,7 +178,7 @@ elseif ESX.GetConfig().Multichar == true then awaitingRegistration[source] = charid else TriggerEvent('esx:onPlayerJoined', source, PREFIX..charid) - ESX.Players[GetIdentifier(source)] = source + ESX.Players[GetIdentifier(source)] = true end end end) @@ -185,7 +186,7 @@ elseif ESX.GetConfig().Multichar == true then AddEventHandler('esx_identity:completedRegistration', function(source, data) TriggerEvent('esx:onPlayerJoined', source, PREFIX..awaitingRegistration[source], data) awaitingRegistration[source] = nil - ESX.Players[GetIdentifier(source)] = source + ESX.Players[GetIdentifier(source)] = true end) AddEventHandler('playerDropped', function(reason) From 2635ce9fbb3138b75d0c6c14ca59c76f54d2f07e Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Thu, 11 Nov 2021 06:28:56 +1100 Subject: [PATCH 49/60] fix(server): added missing symbols --- config.lua | 2 +- server/main.lua | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config.lua b/config.lua index b5af0abc..e7290c3f 100644 --- a/config.lua +++ b/config.lua @@ -29,7 +29,7 @@ else -- Do not use unless you are prepared to adjust your resources to correctly reset data -- Information: https://github.com/thelindat/esx_multicharacter#relogging - Config.Relog = false + Config.Relog = true -------------------- -- Default appearance for new characters diff --git a/server/main.lua b/server/main.lua index d966d528..f411692a 100644 --- a/server/main.lua +++ b/server/main.lua @@ -49,7 +49,7 @@ elseif ESX.GetConfig().Multichar == true then while not FETCH do Citizen.Wait(100) end local identifier = GetIdentifier(source) ESX.Players[identifier] = true - + local slots = MySQL.Sync.fetchScalar("SELECT slots FROM multicharacter_slots WHERE identifier = ?", { identifier }) or SLOTS @@ -94,12 +94,12 @@ elseif ESX.GetConfig().Multichar == true then Citizen.Wait(100) if identifier then if ESX.Players[identifier] then - deferrals.done(('A player is already connected to the server with this identifier.\nYour identifier: %s:%s'):format(PRIMARY_IDENTIFIER, identifier) + deferrals.done(('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(('Unable to retrieve player identifier.\nIdentifier type: %s'):format(PRIMARY_IDENTIFIER) + deferrals.done(('Unable to retrieve player identifier.\nIdentifier type: %s'):format(PRIMARY_IDENTIFIER)) end end) From 6c7408b02edaaee7b76e1683253c39ca711ae21e Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Fri, 12 Nov 2021 18:49:13 +1100 Subject: [PATCH 50/60] Update fxmanifest.lua --- fxmanifest.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fxmanifest.lua b/fxmanifest.lua index f39c2340..503a7fb8 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -1,7 +1,7 @@ fx_version 'cerulean' game 'gta5' description 'https://github.com/thelindat/esx_multicharacter' -version '1.4.0' +version '1.4.1' lua54 'yes' dependencies { From 6a7d0ad56a56c35445464016f80adb2afb90d0b9 Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Sun, 21 Nov 2021 17:24:09 +1100 Subject: [PATCH 51/60] update readme --- config.lua | 4 +--- readme.md | 11 +++++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/config.lua b/config.lua index e7290c3f..503c9cfb 100644 --- a/config.lua +++ b/config.lua @@ -8,9 +8,7 @@ if IsDuplicityVersion() then -------------------- -- Text to prepend to each character (char#:identifier) - keep it short - -- if you modify this, you will need to modify es_extended due to an oversight! - -- https://github.com/esx-framework/esx-legacy/blob/main/%5Besx%5D/es_extended/server/classes/player.lua#L17 - -- if Config.Multichar then self.license = 'license'..identifier:sub(identifier:find(':')) else self.license = 'license:'..identifier end + -- if you change the length, you will need to modify es_extended (refer to the readme) Config.Prefix = 'char' -------------------- diff --git a/readme.md b/readme.md index f7f30478..e4520790 100644 --- a/readme.md +++ b/readme.md @@ -11,6 +11,15 @@ - All owner and identifier columns should be set to `VARCHAR(60)` to ensure correct data entry - The resource will attempt to set columns automatically +### Changing character prefix +- Each character is created a modified identifier (char#:identifier) +- Due to an oversight, ESX Legacy only supports a prefix with a length of 4 +- If you change the length, refer to https://github.com/esx-framework/esx-legacy/blob/main/%5Besx%5D/es_extended/server/classes/player.lua#L17 +- Modify this line to the following +```lua +if Config.Multichar then self.license = 'license:'..identifier:sub(identifier:find(':'), identifier:len()) else self.license = 'license:'..identifier end +``` + ### Conflicts * The following resources should not be used with ESX Legacy and can result in errors - essentialmode @@ -24,8 +33,6 @@ - Download and run all requirements - Use a fresh spawnmanager as many people alter the code - Ensure none of the conflicting resources are enabled -#### mysql-async duplicate entry - - You have not increased the VARCHAR size of the table holding identifiers - usually `owner` or `identifier` #### The menu interface is esx_menu_default - you can use any version if you want a different appearance ![image](https://user-images.githubusercontent.com/65407488/126976325-17cc3241-bb9e-451f-a6ed-610a8ef52fa5.png) From 150ec165fcd92c5815d952fdf9d67ffdbf9ee625 Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Sun, 21 Nov 2021 17:26:29 +1100 Subject: [PATCH 52/60] fix(server): clone and wipe the playerlist when restarting the resource Since we're looping through the table, we end up trying to index new values as they are added. --- server/main.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/server/main.lua b/server/main.lua index f411692a..5becd5c1 100644 --- a/server/main.lua +++ b/server/main.lua @@ -40,7 +40,9 @@ elseif ESX.GetConfig().Multichar == true then end if next(ESX.Players) then - for k, v in pairs(ESX.Players) do + 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 @@ -48,9 +50,9 @@ elseif ESX.GetConfig().Multichar == true then local function SetupCharacters(source) while not FETCH do Citizen.Wait(100) end local identifier = GetIdentifier(source) - ESX.Players[identifier] = true + ESX.Players[identifier] = true - local slots = MySQL.Sync.fetchScalar("SELECT slots FROM multicharacter_slots WHERE identifier = ?", { + local slots = MySQL.Sync.fetchScalar("SELECT slots FROM multicharacter_slots WHERE identifier = ?", { identifier }) or SLOTS identifier = PREFIX..'%:'..identifier From 874eeef86c10e9fcb188d8520f871879a1ff58bb Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Thu, 2 Dec 2021 01:47:35 +1100 Subject: [PATCH 53/60] fix(server): connection string parser --- fxmanifest.lua | 2 +- server/main.lua | 40 ++++++++++++++++++---------------------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/fxmanifest.lua b/fxmanifest.lua index 503a7fb8..f93b5e33 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -1,7 +1,7 @@ fx_version 'cerulean' game 'gta5' description 'https://github.com/thelindat/esx_multicharacter' -version '1.4.1' +version '1.4.2' lua54 'yes' dependencies { diff --git a/server/main.lua b/server/main.lua index 5becd5c1..cb4ad80d 100644 --- a/server/main.lua +++ b/server/main.lua @@ -1,28 +1,24 @@ -local function DATABASE() - 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 - if connectionString:find('?*$') then - return connectionString:match('[^/][%w]*[?$]'):sub(0, -2) - else - return connectionString:match('[^/][%w]*$') - end - else - connectionString = {string.strsplit(';', connectionString)} - for i=1, #connectionString do - local v = connectionString[i] - if v:match('database') then - return v:sub(10, #v) - end - end - end -end - if not ESX then error('\n^1Unable to start Multicharacter - you must be using ESX Legacy^0') elseif ESX.GetConfig().Multichar == true then - DATABASE = DATABASE() + 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]*$', '') + else + connectionString = {string.strsplit(';', connectionString)} + for i=1, #connectionString do + local v = connectionString[i] + if v:match('database') then + DATABASE = v:sub(10, #v) + end + end + end + end + local DB_TABLES = {} local FETCH = nil local SLOTS = Config.Slots or 4 From d6a56057d8535127065450bddc5c199d57e8457c Mon Sep 17 00:00:00 2001 From: Night Owls Roleplay <85649796+nightowlsrp@users.noreply.github.com> Date: Mon, 13 Dec 2021 22:08:43 -0800 Subject: [PATCH 54/60] removed margin-right to just use "right" value Removed margin-right as "right" handles this usage just fine and margin messes with the actual container. --- html/css/main.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/html/css/main.css b/html/css/main.css index 60090d94..15fb9bc5 100644 --- a/html/css/main.css +++ b/html/css/main.css @@ -25,10 +25,9 @@ body { display:none; position: absolute; top: 50%; - right: 0; + right: 25; transform: translate(0, -50%); background: rgba(0,0,0,0.7); - margin-right: 1.5rem; } .header { From 72450dfcd67dd4db09829cb1ce78bb0542dc314d Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Sat, 12 Feb 2022 05:58:45 +1100 Subject: [PATCH 55/60] refactor: swap to oxmysql ESX officially uses oxmysql with new syntax, so follow suite. --- client/main.lua | 8 ++-- config.lua | 8 ++-- esx_multicharacter.sql | 10 ++--- fxmanifest.lua | 2 +- readme.md | 46 ++++++---------------- server/commands.lua | 14 +++---- server/main.lua | 87 +++++++++++++++++++++--------------------- 7 files changed, 74 insertions(+), 101 deletions(-) diff --git a/client/main.lua b/client/main.lua index 9aa1a919..cb10ca8f 100644 --- a/client/main.lua +++ b/client/main.lua @@ -51,7 +51,7 @@ if ESX.GetConfig().Multichar then local keys = {18, 27, 172, 173, 174, 175, 176, 177, 187, 188, 191, 201, 108, 109} while hidePlayers do DisableAllControlActions(0) - for i=1, #keys do + for i = 1, #keys do EnableControlAction(0, keys[i], true) end SetEntityVisible(PlayerPedId(), 0, 0) @@ -64,7 +64,7 @@ if ESX.GetConfig().Multichar then HideHudAndRadarThisFrame() Citizen.Wait(0) local vehicles = GetGamePool('CVehicle') - for i=1, #vehicles do + for i = 1, #vehicles do SetEntityLocallyInvisible(vehicles[i]) end end @@ -80,7 +80,7 @@ if ESX.GetConfig().Multichar then local playerPool = {} while hidePlayers do local players = GetActivePlayers() - for i=1, #players do + for i = 1, #players do local player = players[i] if player ~= PlayerId() and not playerPool[player] then playerPool[player] = true @@ -236,7 +236,7 @@ if ESX.GetConfig().Multichar then else ESX.UI.Menu.CloseAll() local GetSlot = function() - for i=1, slots do + for i = 1, slots do if not Characters[i] then return i end diff --git a/config.lua b/config.lua index 503c9cfb..fe18b776 100644 --- a/config.lua +++ b/config.lua @@ -1,6 +1,9 @@ Config = {} Config.Locale = 'en' +-- Allows players to delete their characters +Config.CanDelete = true + if IsDuplicityVersion() then -- This is the default number of slots for EVERY player -- If you want to manage extra slots for specific players you can do it by using '/setslots' and '/remslots' commands @@ -8,7 +11,6 @@ if IsDuplicityVersion() then -------------------- -- Text to prepend to each character (char#:identifier) - keep it short - -- if you change the length, you will need to modify es_extended (refer to the readme) Config.Prefix = 'char' -------------------- @@ -21,10 +23,6 @@ else Config.Spawn = vector4(-113.7, 565.3, 195.2, 0) -------------------- - -- Allows players to delete their characters - Config.CanDelete = true - -------------------- - -- Do not use unless you are prepared to adjust your resources to correctly reset data -- Information: https://github.com/thelindat/esx_multicharacter#relogging Config.Relog = true diff --git a/esx_multicharacter.sql b/esx_multicharacter.sql index b3a76af7..f19f7610 100644 --- a/esx_multicharacter.sql +++ b/esx_multicharacter.sql @@ -1,13 +1,9 @@ CREATE TABLE `multicharacter_slots` ( - `identifier` VARCHAR(60) NOT NULL COLLATE 'utf8mb4_unicode_ci', + `identifier` VARCHAR(60) NOT NULL, `slots` INT(11) NOT NULL, PRIMARY KEY (`identifier`) USING BTREE, INDEX `slots` (`slots`) USING BTREE -) -COLLATE='utf8mb4_unicode_ci' -ENGINE=InnoDB -; +) ENGINE=InnoDB; ALTER TABLE `users` ADD COLUMN - `disabled` TINYINT(1) NULL DEFAULT '0' -; + `disabled` TINYINT(1) NULL DEFAULT '0'; diff --git a/fxmanifest.lua b/fxmanifest.lua index f93b5e33..0b928219 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -19,7 +19,7 @@ shared_scripts { server_scripts { '@es_extended/imports.lua', - '@mysql-async/lib/MySQL.lua', + '@oxmysql/lib/MySQL.lua', 'server/*.lua', } diff --git a/readme.md b/readme.md index e4520790..fa8c8748 100644 --- a/readme.md +++ b/readme.md @@ -1,24 +1,6 @@ ### Requirements (ensure you are using the latest) - [ESX Legacy](https://github.com/esx-framework/esx-legacy) -- [Spawnmanager](https://github.com/citizenfx/cfx-server-data/tree/master/resources/%5Bmanagers%5D/spawnmanager) - -- [OxMySQL](https://github.com/overextended/oxmysql/) or [MySQL Async 3.3.2](https://github.com/brouznouf/fivem-mysql-async/releases/tag/3.3.2) - -### Installation -- Modify your ESX config with `Config.Multichar = true` -- Set your database name for `Config.Database` in server/main.lua -- Run `esx_multicharacter.sql` into your database -- All owner and identifier columns should be set to `VARCHAR(60)` to ensure correct data entry - - The resource will attempt to set columns automatically - -### Changing character prefix -- Each character is created a modified identifier (char#:identifier) -- Due to an oversight, ESX Legacy only supports a prefix with a length of 4 -- If you change the length, refer to https://github.com/esx-framework/esx-legacy/blob/main/%5Besx%5D/es_extended/server/classes/player.lua#L17 -- Modify this line to the following -```lua -if Config.Multichar then self.license = 'license:'..identifier:sub(identifier:find(':'), identifier:len()) else self.license = 'license:'..identifier end -``` +- [OxMySQL](https://github.com/overextended/oxmysql/releases) ### Conflicts * The following resources should not be used with ESX Legacy and can result in errors @@ -28,13 +10,15 @@ if Config.Multichar then self.license = 'license:'..identifier:sub(identifier:fi - fivem-map-hipster - default_spawnpoint -### Common issues -#### Black screen / loading scripts - - Download and run all requirements - - Use a fresh spawnmanager as many people alter the code - - Ensure none of the conflicting resources are enabled +### Installation +- Modify your ESX config with `Config.Multichar = true` +- Set your database name for `Config.Database` in server/main.lua +- Run `esx_multicharacter.sql` into your database +- All owner and identifier columns should be set to `VARCHAR(60)` to ensure correct data entry + - The resource will attempt to set columns automatically -#### The menu interface is esx_menu_default - you can use any version if you want a different appearance + +#### The menu is esx_menu_default - you can use any version if you want a different appearance ![image](https://user-images.githubusercontent.com/65407488/126976325-17cc3241-bb9e-451f-a6ed-610a8ef52fa5.png) ### Relogging @@ -43,22 +27,16 @@ if Config.Multichar then self.license = 'license:'..identifier:sub(identifier:fi - Add the following events to resources that require support for relogging, or - Add them to [@esx/imports.lua](https://github.com/esx-framework/es_extended/blob/legacy/imports.lua) (and use the imports in your resources) ```lua -RegisterNetEvent('esx:playerLoaded') -AddEventHandler('esx:playerLoaded', function(xPlayer) +RegisterNetEvent('esx:playerLoaded', function(xPlayer) ESX.PlayerData = xPlayer ESX.PlayerLoaded = true end) -RegisterNetEvent('esx:onPlayerLogout') -AddEventHandler('esx:onPlayerLogout', function() +RegisterNetEvent('esx:onPlayerLogout', function() ESX.PlayerLoaded = false ESX.PlayerData = {} end) ``` -- Any threads using ESX.PlayerData in a loop should check if ESX.PlayerLoaded is true - - This ensures the resource does not error after relogging, or while on character selection - - Setup correctly you can break your loops and trigger them again after loading - - Refer to my [boilerplate](https://github.com/thelindat/esx_legacy_boilerplate) for more information and usage examples ### Notes - This resource is not compatible with ExtendedMode or previous versions of ESX @@ -76,7 +54,7 @@ end) ## Notice -Copyright© 2021 Linden and KASH +Copyright © 2021 Linden and KASH 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 diff --git a/server/commands.lua b/server/commands.lua index 6c0ff047..3cfa98d5 100644 --- a/server/commands.lua +++ b/server/commands.lua @@ -1,16 +1,16 @@ ESX.RegisterCommand('setslots', 'admin', function(xPlayer, args, showError) - local slots = MySQL.Sync.fetchScalar('SELECT `slots` FROM `multicharacter_slots` WHERE identifier = ?', { + local slots = MySQL.scalar('SELECT `slots` FROM `multicharacter_slots` WHERE identifier = ?', { args.identifier }) if slots == nil then - MySQL.Async.execute('INSERT INTO `multicharacter_slots` (`identifier`, `slots`) VALUES (?, ?)', { + MySQL.update('INSERT INTO `multicharacter_slots` (`identifier`, `slots`) VALUES (?, ?)', { args.identifier, args.slots }) xPlayer.triggerEvent('esx:showNotification', _U('slotsadd', args.slots, args.identifier)) else - MySQL.Async.execute('UPDATE `multicharacter_slots` SET `slots` = ? WHERE `identifier` = ?', { + MySQL.update('UPDATE `multicharacter_slots` SET `slots` = ? WHERE `identifier` = ?', { args.slots, args.identifier }) @@ -22,12 +22,12 @@ end, true, {help = _U('command_setslots'), validate = true, arguments = { }}) ESX.RegisterCommand('remslots', 'admin', function(xPlayer, args, showError) - local slots = MySQL.Sync.fetchScalar('SELECT `slots` FROM `multicharacter_slots` WHERE identifier = ?', { + local slots = MySQL.scalar('SELECT `slots` FROM `multicharacter_slots` WHERE identifier = ?', { args.identifier }) if slots ~= nil then - MySQL.Async.execute('DELETE FROM `multicharacter_slots` WHERE `identifier` = ?', { + MySQL.update('DELETE FROM `multicharacter_slots` WHERE `identifier` = ?', { args.identifier }) xPlayer.triggerEvent('esx:showNotification', _U('slotsrem', args.identifier)) @@ -40,7 +40,7 @@ ESX.RegisterCommand('enablechar', 'admin', function(xPlayer, args, showError) local selectedCharacter = 'char'..args.charslot..':'..args.identifier; - MySQL.Async.execute('UPDATE `users` SET `disabled` = 0 WHERE identifier = ?', { + MySQL.update('UPDATE `users` SET `disabled` = 0 WHERE identifier = ?', { selectedCharacter }, function(result) if result > 0 then @@ -59,7 +59,7 @@ ESX.RegisterCommand('disablechar', 'admin', function(xPlayer, args, showError) local selectedCharacter = 'char'..args.charslot..':'..args.identifier; - MySQL.Async.execute('UPDATE `users` SET `disabled` = 1 WHERE identifier = ?', { + MySQL.update('UPDATE `users` SET `disabled` = 1 WHERE identifier = ?', { selectedCharacter }, function(result) if result > 0 then diff --git a/server/main.lua b/server/main.lua index cb4ad80d..a0c13e15 100644 --- a/server/main.lua +++ b/server/main.lua @@ -10,7 +10,7 @@ elseif ESX.GetConfig().Multichar == true then DATABASE = connectionString:sub(connectionString:find('/')+1, -1):gsub('[%?]+[%w%p]*$', '') else connectionString = {string.strsplit(';', connectionString)} - for i=1, #connectionString do + for i = 1, #connectionString do local v = connectionString[i] if v:match('database') then DATABASE = v:sub(10, #v) @@ -48,48 +48,51 @@ elseif ESX.GetConfig().Multichar == true then local identifier = GetIdentifier(source) ESX.Players[identifier] = true - local slots = MySQL.Sync.fetchScalar("SELECT slots FROM multicharacter_slots WHERE identifier = ?", { - identifier - }) or SLOTS + local slots = MySQL.scalar('SELECT slots FROM multicharacter_slots WHERE identifier = ?', { identifier }) or SLOTS identifier = PREFIX..'%:'..identifier - MySQL.Async.fetchAll(FETCH, {identifier, slots}, function(result) - local characters - if result then - local characterCount = #result - characters = table.create(0, characterCount) - for i=1, characterCount, 1 do - local i = result[i] - local job, grade = i.job or 'unemployed', tostring(i.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(i.accounts) - local id = tonumber(string.sub(i.identifier, #PREFIX+1, string.find(i.identifier, ':')-1)) - characters[id] = { - id = id, - bank = accounts.bank, - money = accounts.money, - job = job, - job_grade = grade, - firstname = i.firstname, - lastname = i.lastname, - dateofbirth = i.dateofbirth, - skin = json.decode(i.skin), - disabled = i.disabled, - sex = i.sex == 'm' and _('male') or _('female') - } + local result = MySQL.query.await(FETCH, {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 _('male') or _('female') + } end - TriggerClientEvent('esx_multicharacter:SetupUI', source, characters, slots) - end) + end + + TriggerClientEvent('esx_multicharacter:SetupUI', source, characters, slots) end AddEventHandler('playerConnecting', function(playerName, setKickReason, deferrals) deferrals.defer() local identifier = GetIdentifier(source) - Citizen.Wait(100) + if identifier then if ESX.Players[identifier] then deferrals.done(('A player is already connected to the server with this identifier.\nYour identifier: %s:%s'):format(PRIMARY_IDENTIFIER, identifier)) @@ -112,7 +115,7 @@ elseif ESX.GetConfig().Multichar == true then queries[i] = {query = query, values = {v.table, v.column, identifier}} end - MySQL.Async.transaction(queries, function(result) + MySQL.transaction(queries, function(result) if result then print(('[^2INFO^7] Player [%s] %s has deleted a character (%s)'):format(GetPlayerName(source), source, identifier)) Citizen.Wait(50) @@ -163,14 +166,14 @@ elseif ESX.GetConfig().Multichar == true then end) end) - RegisterServerEvent("esx_multicharacter:SetupCharacters") + RegisterNetEvent('esx_multicharacter:SetupCharacters', function() AddEventHandler('esx_multicharacter:SetupCharacters', function() SetupCharacters(source) end) local awaitingRegistration = {} - RegisterServerEvent("esx_multicharacter:CharacterChosen") - AddEventHandler('esx_multicharacter:CharacterChosen', function(charid, isNew) + + 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 @@ -192,15 +195,13 @@ elseif ESX.GetConfig().Multichar == true then ESX.Players[GetIdentifier(source)] = nil end) - RegisterServerEvent("esx_multicharacter:DeleteCharacter") - AddEventHandler('esx_multicharacter:DeleteCharacter', function(charid) - if type(charid) == "number" and string.len(charid) <= 2 then + RegisterNetEvent('esx_multicharacter:DeleteCharacter', function(charid) + if Config.CanDelete and type(charid) == 'number' and string.len(charid) <= 2 then DeleteCharacter(source, charid) end end) - RegisterServerEvent("esx_multicharacter:relog") - AddEventHandler('esx_multicharacter:relog', function() + RegisterNetEvent('esx_multicharacter:relog', function() TriggerEvent('esx:playerLogout', source) end) From b7c521c8e7856fb8e1ce08c8f006c461bdfb014e Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Sat, 12 Feb 2022 06:03:23 +1100 Subject: [PATCH 56/60] feat(server): database optimisations Limited testing, but works in a fairly clean ESX environment. Sets up foreign keys in the database to allow for easy and optimised character deletion. Foreign keys will only be set where owner and identifier columns exist, the field cannot be NULL and uses VARCHAR. --- server/main.lua | 119 +++++++++++++++++++++++++++++++----------------- 1 file changed, 77 insertions(+), 42 deletions(-) diff --git a/server/main.lua b/server/main.lua index a0c13e15..5b672bc7 100644 --- a/server/main.lua +++ b/server/main.lua @@ -19,7 +19,7 @@ elseif ESX.GetConfig().Multichar == true then end end - local DB_TABLES = {} + local DB_TABLES = {users = 'identifier'} local FETCH = nil local SLOTS = Config.Slots or 4 local PREFIX = Config.Prefix or 'char' @@ -106,13 +106,13 @@ elseif ESX.GetConfig().Multichar == true then local function DeleteCharacter(source, charid) local identifier = ('%s%s:%s'):format(PREFIX, charid, GetIdentifier(source)) - local query = "DELETE FROM ?? WHERE ?? = ?" - local tableCount = #DB_TABLES - local queries = table.create(tableCount, 0) + local query = 'DELETE FROM %s WHERE %s = ?' + local queries = {} + local count = 0 - for i=1, tableCount do - local v = DB_TABLES[i] - queries[i] = {query = query, values = {v.table, v.column, identifier}} + for table, column in pairs(DB_TABLES) do + count += 1 + queries[count] = {query = query:format(table, column), values = {identifier}} end MySQL.transaction(queries, function(result) @@ -127,47 +127,82 @@ elseif ESX.GetConfig().Multichar == true then end MySQL.ready(function() - MySQL.Async.fetchAll('SELECT TABLE_NAME, COLUMN_NAME, CHARACTER_MAXIMUM_LENGTH FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = ? AND (COLUMN_NAME = ? OR COLUMN_NAME = ?) ', { - DATABASE, 'identifier', 'owner' - }, function(result) - if result then - local varchar, varsize = {}, 0 - for k, v in pairs(result) do - if v.CHARACTER_MAXIMUM_LENGTH and v.CHARACTER_MAXIMUM_LENGTH >= 40 and v.CHARACTER_MAXIMUM_LENGTH < 60 then varchar[v.TABLE_NAME] = v.COLUMN_NAME varsize = varsize+1 end - table.insert(DB_TABLES, {table = v.TABLE_NAME, column = v.COLUMN_NAME}) - end - if next(varchar) then - local query = "ALTER TABLE ?? MODIFY COLUMN ?? VARCHAR(60)" - local queries = table.create(varsize, 0) + local length = 42 + #PREFIX + local DB_COLUMNS = MySQL.query.await(('SELECT TABLE_NAME, COLUMN_NAME, CHARACTER_MAXIMUM_LENGTH, IS_NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = "%s" AND DATA_TYPE = "varchar" AND COLUMN_NAME IN (?)'):format(DATABASE, length), { + {'identifier', 'owner'} + }) - for k, v in pairs(varchar) do - queries[#queries+1] = {query = query, values = {k, v}} - end + if DB_COLUMNS then + local FK = MySQL.query.await(('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_TYPE = "FOREIGN KEY" AND TABLE_SCHEMA = "%s"'):format(DATABASE)) + local FOREIGN_KEY = {} - MySQL.Async.transaction(queries, function(result) - if result then - print(('[^2INFO^7] Updated ^5%s^7 columns to use VARCHAR(60)'):format(varsize)) - else - print(('[^2INFO^7] Unable to update ^5%s^7 columns to use VARCHAR(60)'):format(varsize)) - end - end) - end - ESX.Jobs = {} - local function GetJobs() - if ESX.GetJobs then return ESX.GetJobs() - else return exports['es_extended']:getSharedObject().Jobs end - end - repeat - ESX.Jobs = GetJobs() - Citizen.Wait(50) - until next(ESX.Jobs) - FETCH = MySQL.Sync.store("SELECT identifier, accounts, job, job_grade, firstname, lastname, dateofbirth, sex, skin, disabled FROM users WHERE identifier LIKE ? LIMIT ?") + for i = 1, #FK do + FOREIGN_KEY[FK[i].TABLE_NAME] = true end - end) + + local columns = {} + local count = 0 + + for i = 1, #DB_COLUMNS do + local v = DB_COLUMNS[i] + + if v?.CHARACTER_MAXIMUM_LENGTH ~= length then + count += 1 + columns[v.TABLE_NAME] = v.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 VARCHAR(%s)'):format(count, length)) + else + print(('[^2INFO^7] Unable to update ^5%s^7 columns to use VARCHAR(%s)'):format(count, length)) + end + end + + for i = 1, #DB_COLUMNS do + local v = DB_COLUMNS[i] + + if not FOREIGN_KEY[v.TABLE_NAME] then + DB_TABLES[v.TABLE_NAME] = v.COLUMN_NAME + + if v.TABLE_NAME ~= 'users' and v.IS_NULLABLE == 'NO' then + MySQL.update(([[ALTER TABLE `%s` + ADD CONSTRAINT `FK_%s_users` FOREIGN KEY (`%s`) REFERENCES `users` (`identifier`) ON UPDATE CASCADE ON DELETE CASCADE]]):format(v.TABLE_NAME, v.TABLE_NAME, v.COLUMN_NAME), function(result) + if result then + print(('[^2INFO^7] Updated ^5"%s.%s"^7 to use FOREIGN_KEY referencing ^5"users.identifier"^7'):format(v.TABLE_NAME, v.COLUMN_NAME)) + end + end) + end + end + end + + ESX.Jobs = {} + + local function GetJobs() + if ESX.GetJobs then + return ESX.GetJobs() + end + return exports.es_extended:getSharedObject().Jobs + end + + repeat + ESX.Jobs = GetJobs() + Citizen.Wait(50) + until next(ESX.Jobs) + + FETCH = 'SELECT identifier, accounts, job, job_grade, firstname, lastname, dateofbirth, sex, skin, disabled FROM users WHERE identifier LIKE ? LIMIT ?' + end end) RegisterNetEvent('esx_multicharacter:SetupCharacters', function() - AddEventHandler('esx_multicharacter:SetupCharacters', function() SetupCharacters(source) end) From fca8927b384713d0cdd8327a2a60f53ba324bd43 Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Mon, 14 Feb 2022 20:18:59 +1100 Subject: [PATCH 57/60] revert(server): foreign keys Can result in some errors. Still keeping the other optimisations from previous commit (namely adjusting all identifier fields to the correct varchar length). --- server/main.lua | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/server/main.lua b/server/main.lua index 5b672bc7..6b3fd4f8 100644 --- a/server/main.lua +++ b/server/main.lua @@ -128,18 +128,11 @@ elseif ESX.GetConfig().Multichar == true then MySQL.ready(function() local length = 42 + #PREFIX - local DB_COLUMNS = MySQL.query.await(('SELECT TABLE_NAME, COLUMN_NAME, CHARACTER_MAXIMUM_LENGTH, IS_NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = "%s" AND DATA_TYPE = "varchar" AND COLUMN_NAME IN (?)'):format(DATABASE, length), { + 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 FK = MySQL.query.await(('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_TYPE = "FOREIGN KEY" AND TABLE_SCHEMA = "%s"'):format(DATABASE)) - local FOREIGN_KEY = {} - - for i = 1, #FK do - FOREIGN_KEY[FK[i].TABLE_NAME] = true - end - local columns = {} local count = 0 @@ -167,23 +160,6 @@ elseif ESX.GetConfig().Multichar == true then end end - for i = 1, #DB_COLUMNS do - local v = DB_COLUMNS[i] - - if not FOREIGN_KEY[v.TABLE_NAME] then - DB_TABLES[v.TABLE_NAME] = v.COLUMN_NAME - - if v.TABLE_NAME ~= 'users' and v.IS_NULLABLE == 'NO' then - MySQL.update(([[ALTER TABLE `%s` - ADD CONSTRAINT `FK_%s_users` FOREIGN KEY (`%s`) REFERENCES `users` (`identifier`) ON UPDATE CASCADE ON DELETE CASCADE]]):format(v.TABLE_NAME, v.TABLE_NAME, v.COLUMN_NAME), function(result) - if result then - print(('[^2INFO^7] Updated ^5"%s.%s"^7 to use FOREIGN_KEY referencing ^5"users.identifier"^7'):format(v.TABLE_NAME, v.COLUMN_NAME)) - end - end) - end - end - end - ESX.Jobs = {} local function GetJobs() From 67d28ee2682591a44fac7e27786483e4d8e9bece Mon Sep 17 00:00:00 2001 From: CoachYT1 <69034647+CoachYT1@users.noreply.github.com> Date: Mon, 21 Feb 2022 09:17:49 +0100 Subject: [PATCH 58/60] Update readme.md --- readme.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/readme.md b/readme.md index fa8c8748..6e2393c9 100644 --- a/readme.md +++ b/readme.md @@ -12,7 +12,6 @@ ### Installation - Modify your ESX config with `Config.Multichar = true` -- Set your database name for `Config.Database` in server/main.lua - Run `esx_multicharacter.sql` into your database - All owner and identifier columns should be set to `VARCHAR(60)` to ensure correct data entry - The resource will attempt to set columns automatically @@ -25,7 +24,7 @@ - Do not enable this setting if you do not intend to properly set up relog support - Requires the latest update for ESX Status (prevents multiple status ticks from running) - Add the following events to resources that require support for relogging, or -- Add them to [@esx/imports.lua](https://github.com/esx-framework/es_extended/blob/legacy/imports.lua) (and use the imports in your resources) +- Add them to [@es_extended/imports.lua](https://github.com/esx-framework/esx-legacy/blob/main/[esx]/es_extended/imports.lua) (and use the imports in your resources) ```lua RegisterNetEvent('esx:playerLoaded', function(xPlayer) ESX.PlayerData = xPlayer From 09ce85123d693ddfe54c1bb764b95864418b12b7 Mon Sep 17 00:00:00 2001 From: Dolu <47056777+dolutattoo@users.noreply.github.com> Date: Sat, 12 Mar 2022 03:44:30 +0100 Subject: [PATCH 59/60] fix(server): await player slots --- server/main.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/main.lua b/server/main.lua index 6b3fd4f8..266c560c 100644 --- a/server/main.lua +++ b/server/main.lua @@ -48,7 +48,7 @@ elseif ESX.GetConfig().Multichar == true then local identifier = GetIdentifier(source) ESX.Players[identifier] = true - local slots = MySQL.scalar('SELECT slots FROM multicharacter_slots WHERE identifier = ?', { identifier }) or SLOTS + local slots = MySQL.scalar.await('SELECT slots FROM multicharacter_slots WHERE identifier = ?', { identifier }) or SLOTS identifier = PREFIX..'%:'..identifier local result = MySQL.query.await(FETCH, {identifier, slots}) From 1be411b3d1504eab0bd62be2114176cd055c41fd Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Wed, 23 Mar 2022 20:34:16 +1100 Subject: [PATCH 60/60] chore: move esx_multicharacter files --- .../esx_multicharacter/LICENSE.md | 0 .../esx_multicharacter/client}/main.lua | 0 .../esx_multicharacter/config.lua | 0 .../esx_multicharacter/esx_multicharacter.sql | 0 .../esx_multicharacter/fxmanifest.lua | 0 .../esx_multicharacter/html}/css/main.css | 0 .../esx_multicharacter/html}/js/app.js | 0 .../esx_multicharacter/html}/locales/cs.js | 0 .../esx_multicharacter/html}/locales/de.js | 0 .../esx_multicharacter/html}/locales/en.js | 0 .../esx_multicharacter/html}/locales/fr.js | 0 .../esx_multicharacter/html}/locales/pl.js | 0 .../esx_multicharacter/html}/locales/pt.js | 0 .../esx_multicharacter/html}/locales/tr.js | 0 .../esx_multicharacter/html}/ui.html | 0 .../esx_multicharacter/locales}/cs.lua | 0 .../esx_multicharacter/locales}/de.lua | 0 .../esx_multicharacter/locales}/en.lua | 0 .../esx_multicharacter/locales}/it.lua | 0 .../esx_multicharacter/locales}/pt.lua | 0 .../esx_multicharacter/locales}/tr.lua | 0 .../esx_multicharacter/readme.md | 144 +++++++++--------- .../esx_multicharacter/server}/commands.lua | 0 .../esx_multicharacter/server}/main.lua | 0 24 files changed, 72 insertions(+), 72 deletions(-) rename LICENSE.md => [esx]/esx_multicharacter/LICENSE.md (100%) rename {client => [esx]/esx_multicharacter/client}/main.lua (100%) rename config.lua => [esx]/esx_multicharacter/config.lua (100%) rename esx_multicharacter.sql => [esx]/esx_multicharacter/esx_multicharacter.sql (100%) rename fxmanifest.lua => [esx]/esx_multicharacter/fxmanifest.lua (100%) rename {html => [esx]/esx_multicharacter/html}/css/main.css (100%) rename {html => [esx]/esx_multicharacter/html}/js/app.js (100%) rename {html => [esx]/esx_multicharacter/html}/locales/cs.js (100%) rename {html => [esx]/esx_multicharacter/html}/locales/de.js (100%) rename {html => [esx]/esx_multicharacter/html}/locales/en.js (100%) rename {html => [esx]/esx_multicharacter/html}/locales/fr.js (100%) rename {html => [esx]/esx_multicharacter/html}/locales/pl.js (100%) rename {html => [esx]/esx_multicharacter/html}/locales/pt.js (100%) rename {html => [esx]/esx_multicharacter/html}/locales/tr.js (100%) rename {html => [esx]/esx_multicharacter/html}/ui.html (100%) rename {locales => [esx]/esx_multicharacter/locales}/cs.lua (100%) rename {locales => [esx]/esx_multicharacter/locales}/de.lua (100%) rename {locales => [esx]/esx_multicharacter/locales}/en.lua (100%) rename {locales => [esx]/esx_multicharacter/locales}/it.lua (100%) rename {locales => [esx]/esx_multicharacter/locales}/pt.lua (100%) rename {locales => [esx]/esx_multicharacter/locales}/tr.lua (100%) rename readme.md => [esx]/esx_multicharacter/readme.md (97%) rename {server => [esx]/esx_multicharacter/server}/commands.lua (100%) rename {server => [esx]/esx_multicharacter/server}/main.lua (100%) diff --git a/LICENSE.md b/[esx]/esx_multicharacter/LICENSE.md similarity index 100% rename from LICENSE.md rename to [esx]/esx_multicharacter/LICENSE.md diff --git a/client/main.lua b/[esx]/esx_multicharacter/client/main.lua similarity index 100% rename from client/main.lua rename to [esx]/esx_multicharacter/client/main.lua diff --git a/config.lua b/[esx]/esx_multicharacter/config.lua similarity index 100% rename from config.lua rename to [esx]/esx_multicharacter/config.lua diff --git a/esx_multicharacter.sql b/[esx]/esx_multicharacter/esx_multicharacter.sql similarity index 100% rename from esx_multicharacter.sql rename to [esx]/esx_multicharacter/esx_multicharacter.sql diff --git a/fxmanifest.lua b/[esx]/esx_multicharacter/fxmanifest.lua similarity index 100% rename from fxmanifest.lua rename to [esx]/esx_multicharacter/fxmanifest.lua diff --git a/html/css/main.css b/[esx]/esx_multicharacter/html/css/main.css similarity index 100% rename from html/css/main.css rename to [esx]/esx_multicharacter/html/css/main.css diff --git a/html/js/app.js b/[esx]/esx_multicharacter/html/js/app.js similarity index 100% rename from html/js/app.js rename to [esx]/esx_multicharacter/html/js/app.js diff --git a/html/locales/cs.js b/[esx]/esx_multicharacter/html/locales/cs.js similarity index 100% rename from html/locales/cs.js rename to [esx]/esx_multicharacter/html/locales/cs.js diff --git a/html/locales/de.js b/[esx]/esx_multicharacter/html/locales/de.js similarity index 100% rename from html/locales/de.js rename to [esx]/esx_multicharacter/html/locales/de.js diff --git a/html/locales/en.js b/[esx]/esx_multicharacter/html/locales/en.js similarity index 100% rename from html/locales/en.js rename to [esx]/esx_multicharacter/html/locales/en.js diff --git a/html/locales/fr.js b/[esx]/esx_multicharacter/html/locales/fr.js similarity index 100% rename from html/locales/fr.js rename to [esx]/esx_multicharacter/html/locales/fr.js diff --git a/html/locales/pl.js b/[esx]/esx_multicharacter/html/locales/pl.js similarity index 100% rename from html/locales/pl.js rename to [esx]/esx_multicharacter/html/locales/pl.js diff --git a/html/locales/pt.js b/[esx]/esx_multicharacter/html/locales/pt.js similarity index 100% rename from html/locales/pt.js rename to [esx]/esx_multicharacter/html/locales/pt.js diff --git a/html/locales/tr.js b/[esx]/esx_multicharacter/html/locales/tr.js similarity index 100% rename from html/locales/tr.js rename to [esx]/esx_multicharacter/html/locales/tr.js diff --git a/html/ui.html b/[esx]/esx_multicharacter/html/ui.html similarity index 100% rename from html/ui.html rename to [esx]/esx_multicharacter/html/ui.html diff --git a/locales/cs.lua b/[esx]/esx_multicharacter/locales/cs.lua similarity index 100% rename from locales/cs.lua rename to [esx]/esx_multicharacter/locales/cs.lua diff --git a/locales/de.lua b/[esx]/esx_multicharacter/locales/de.lua similarity index 100% rename from locales/de.lua rename to [esx]/esx_multicharacter/locales/de.lua diff --git a/locales/en.lua b/[esx]/esx_multicharacter/locales/en.lua similarity index 100% rename from locales/en.lua rename to [esx]/esx_multicharacter/locales/en.lua diff --git a/locales/it.lua b/[esx]/esx_multicharacter/locales/it.lua similarity index 100% rename from locales/it.lua rename to [esx]/esx_multicharacter/locales/it.lua diff --git a/locales/pt.lua b/[esx]/esx_multicharacter/locales/pt.lua similarity index 100% rename from locales/pt.lua rename to [esx]/esx_multicharacter/locales/pt.lua diff --git a/locales/tr.lua b/[esx]/esx_multicharacter/locales/tr.lua similarity index 100% rename from locales/tr.lua rename to [esx]/esx_multicharacter/locales/tr.lua diff --git a/readme.md b/[esx]/esx_multicharacter/readme.md similarity index 97% rename from readme.md rename to [esx]/esx_multicharacter/readme.md index 6e2393c9..c25c77cd 100644 --- a/readme.md +++ b/[esx]/esx_multicharacter/readme.md @@ -1,72 +1,72 @@ -### Requirements (ensure you are using the latest) -- [ESX Legacy](https://github.com/esx-framework/esx-legacy) -- [OxMySQL](https://github.com/overextended/oxmysql/releases) - -### Conflicts -* The following resources should not be used with ESX Legacy and can result in errors - - essentialmode - - basic-gamemode - - fivem-map-skater - - fivem-map-hipster - - default_spawnpoint - -### Installation -- Modify your ESX config with `Config.Multichar = true` -- Run `esx_multicharacter.sql` into your database -- All owner and identifier columns should be set to `VARCHAR(60)` to ensure correct data entry - - The resource will attempt to set columns automatically - - -#### The menu is esx_menu_default - you can use any version if you want a different appearance -![image](https://user-images.githubusercontent.com/65407488/126976325-17cc3241-bb9e-451f-a6ed-610a8ef52fa5.png) - -### Relogging -- Do not enable this setting if you do not intend to properly set up relog support -- Requires the latest update for ESX Status (prevents multiple status ticks from running) -- Add the following events to resources that require support for relogging, or -- Add them to [@es_extended/imports.lua](https://github.com/esx-framework/esx-legacy/blob/main/[esx]/es_extended/imports.lua) (and use the imports in your resources) -```lua -RegisterNetEvent('esx:playerLoaded', function(xPlayer) - ESX.PlayerData = xPlayer - ESX.PlayerLoaded = true -end) - -RegisterNetEvent('esx:onPlayerLogout', function() - ESX.PlayerLoaded = false - ESX.PlayerData = {} -end) -``` - -### Notes -- This resource is not compatible with ExtendedMode or previous versions of ESX -- Legacy, skin, and identity must be updated to at least the minimum commits specified above -- Characters are stored in the users table as `char#:license` - if you need to use a different identifier then you need to modify ESX itself -- Character deletion does not require manual entries for the tables to remove -- As characters are stored with unique identifiers, there is no excessive queries being executed - -### Kashacters -- This project is forked from the [kashacters multicharacter resource](https://github.com/FiveEYZ/esx_kashacter) -- Most of the code has been entirely rewritten -- KASH has given permission for this resource to use his code and the addition of a license -- The license obviously does not apply to previous versions and KASH has stated his resource is free to be used however - - - -## Notice -Copyright © 2021 Linden and KASH - -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 https://www.gnu.org/licenses. - - -### Thanks to KASH, XxFri3ndlyxX, and all those who have contributed +### Requirements (ensure you are using the latest) +- [ESX Legacy](https://github.com/esx-framework/esx-legacy) +- [OxMySQL](https://github.com/overextended/oxmysql/releases) + +### Conflicts +* The following resources should not be used with ESX Legacy and can result in errors + - essentialmode + - basic-gamemode + - fivem-map-skater + - fivem-map-hipster + - default_spawnpoint + +### Installation +- Modify your ESX config with `Config.Multichar = true` +- Run `esx_multicharacter.sql` into your database +- All owner and identifier columns should be set to `VARCHAR(60)` to ensure correct data entry + - The resource will attempt to set columns automatically + + +#### The menu is esx_menu_default - you can use any version if you want a different appearance +![image](https://user-images.githubusercontent.com/65407488/126976325-17cc3241-bb9e-451f-a6ed-610a8ef52fa5.png) + +### Relogging +- Do not enable this setting if you do not intend to properly set up relog support +- Requires the latest update for ESX Status (prevents multiple status ticks from running) +- Add the following events to resources that require support for relogging, or +- Add them to [@es_extended/imports.lua](https://github.com/esx-framework/esx-legacy/blob/main/[esx]/es_extended/imports.lua) (and use the imports in your resources) +```lua +RegisterNetEvent('esx:playerLoaded', function(xPlayer) + ESX.PlayerData = xPlayer + ESX.PlayerLoaded = true +end) + +RegisterNetEvent('esx:onPlayerLogout', function() + ESX.PlayerLoaded = false + ESX.PlayerData = {} +end) +``` + +### Notes +- This resource is not compatible with ExtendedMode or previous versions of ESX +- Legacy, skin, and identity must be updated to at least the minimum commits specified above +- Characters are stored in the users table as `char#:license` - if you need to use a different identifier then you need to modify ESX itself +- Character deletion does not require manual entries for the tables to remove +- As characters are stored with unique identifiers, there is no excessive queries being executed + +### Kashacters +- This project is forked from the [kashacters multicharacter resource](https://github.com/FiveEYZ/esx_kashacter) +- Most of the code has been entirely rewritten +- KASH has given permission for this resource to use his code and the addition of a license +- The license obviously does not apply to previous versions and KASH has stated his resource is free to be used however + + + +## Notice +Copyright © 2021 Linden and KASH + +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 https://www.gnu.org/licenses. + + +### Thanks to KASH, XxFri3ndlyxX, and all those who have contributed diff --git a/server/commands.lua b/[esx]/esx_multicharacter/server/commands.lua similarity index 100% rename from server/commands.lua rename to [esx]/esx_multicharacter/server/commands.lua diff --git a/server/main.lua b/[esx]/esx_multicharacter/server/main.lua similarity index 100% rename from server/main.lua rename to [esx]/esx_multicharacter/server/main.lua