diff --git a/config.lua b/config.lua index acba2923..42c24a05 100644 --- a/config.lua +++ b/config.lua @@ -2,9 +2,9 @@ Config = {} Config.Locale = 'en' Config.Accounts = { - bank = _U('account_bank'), + bank = _U('account_bank'), black_money = _U('account_black_money'), - money = _U('account_money') + money = _U('account_money') } Config.StartingAccountMoney = {bank = 50000} @@ -17,5 +17,5 @@ Config.MaxWeight = 24 -- the max inventory weight without backpack Config.PaycheckInterval = 7 * 60000 -- how often to recieve pay checks in milliseconds -Config.EnableDebug = false +Config.EnableDebug = false Config.InventoryKey = 'REPLAY_START_STOP_RECORDING_SECONDARY' -- Key F2 by default diff --git a/fxmanifest.lua b/fxmanifest.lua index 25c204f7..4f9427a6 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -11,16 +11,7 @@ server_scripts { '@mysql-async/lib/MySQL.lua', 'locale.lua', - 'locales/de.lua', - 'locales/br.lua', - 'locales/fr.lua', - 'locales/en.lua', - 'locales/fi.lua', - 'locales/sv.lua', - 'locales/pl.lua', - 'locales/cs.lua', - 'locales/sc.lua', - 'locales/tc.lua', + 'locales/*.lua', 'config.lua', 'config.weapons.lua', @@ -40,17 +31,9 @@ server_scripts { } client_scripts { + 'locale.lua', - 'locales/de.lua', - 'locales/br.lua', - 'locales/fr.lua', - 'locales/en.lua', - 'locales/fi.lua', - 'locales/sv.lua', - 'locales/pl.lua', - 'locales/cs.lua', - 'locales/sc.lua', - 'locales/tc.lua', + 'locales/*.lua', 'config.lua', 'config.weapons.lua', @@ -134,6 +117,7 @@ esxmodule 'menu_list' -- List menu -- Misc esxmodule 'skin' -- Skin management +esxmodule 'accessories' -- Skin accessories management -- Jobs esxmodule 'job_police' -- Job police diff --git a/server/classes/player.lua b/server/classes/player.lua index 8d4ede4f..3d173a91 100644 --- a/server/classes/player.lua +++ b/server/classes/player.lua @@ -1,19 +1,13 @@ -function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory, weight, job, loadout, name, coords) - local self = {} +function CreateExtendedPlayer(data) - self.accounts = accounts - self.coords = coords - self.group = group - self.identifier = identifier - self.inventory = inventory - self.job = job - self.loadout = loadout - self.name = name - self.playerId = playerId - self.source = playerId - self.variables = {} - self.weight = weight - self.maxWeight = Config.MaxWeight + local self = {} + + self.source = data.playerId + self.maxWeight = Config.MaxWeight + + for k,v in pairs(data) do + self[k] = v + end ExecuteCommand(('add_principal identifier.license:%s group.%s'):format(self.identifier, self.group)) @@ -76,11 +70,11 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory, end self.set = function(k, v) - self.variables[k] = v + self[k] = v end self.get = function(k) - return self.variables[k] + return self[k] end self.getAccounts = function(minimal) @@ -270,7 +264,7 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory, return newWeight <= self.maxWeight end - + self.maxCarryItem = function(name) local count = 0 local currentWeight, itemWeight = self.getWeight(), ESX.Items[name].weight @@ -326,7 +320,7 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory, else self.job.skin_female = {} end - + TriggerEvent('esx:setJob', self.source, self.job, lastJob) self.triggerEvent('esx:setJob', self.job) else @@ -507,5 +501,317 @@ function CreateExtendedPlayer(playerId, identifier, group, accounts, inventory, self.triggerEvent('esx:showHelpNotification', msg, thisFrame, beep, duration) end - return self + self.serialize = function() + + local data = { + accounts = self.getAccounts(), + coords = self.getCoords(), + identifier = self.getIdentifier(), + inventory = self.getInventory(), + job = self.getJob(), + loadout = self.getLoadout(), + maxWeight = self.getMaxWeight(), + money = self.getMoney() + } + + TriggerEvent('esx:player:serialize', self, function(extraData) + + for k,v in pairs(extraData) do + data[k] = v + end + + end) + + return data + + end + + self.serializeDB = function() + + local job = self.getJob() + + local data = { + identifier = self.getIdentifier(), + accounts = json.encode(self.getAccounts(true)), + group = self.getGroup(), + inventory = json.encode(self.getInventory(true)), + job = job.name, + job_grade = job.grade, + loadout = json.encode(self.getLoadout(true)), + position = json.encode(self.getCoords()) + } + + TriggerEvent('esx:player:serialize:db', self, function(extraData) + + for k,v in pairs(extraData) do + data[k] = v + end + + end) + + return data + + end + + TriggerEvent('esx:player:create', self) -- You can hook this event to extend xPlayer + + return self + end + +function LoadExtendedPlayer(identifier, playerId, cb) + + MySQL.Async.fetchAll('SELECT * FROM users WHERE identifier = @identifier', { ['@identifier'] = identifier }, function(result) + + local tasks = {} + + local userData = { + playerId = playerId, + identifier = identifier, + weight = 0, + } + + for entryName, entryValue in pairs(result[1]) do + + TriggerEvent('esx:player:load:' .. entryName, identifier, playerId, result[1], userData, function(task) + tasks[#tasks + 1] = task + end) + + end + + Async.parallelLimit(tasks, 5, function(results) + + for i=1, #results, 1 do + + local result = results[i] + + for k,v in pairs(result) do + userData[k] = v + end + + end + + local xPlayer = CreateExtendedPlayer(userData) + + ESX.Players[playerId] = xPlayer + + TriggerEvent('esx:playerLoaded', playerId, xPlayer) + + xPlayer.triggerEvent('esx:playerLoaded', xPlayer.serialize()) + xPlayer.triggerEvent('esx:createMissingPickups', ESX.Pickups) + xPlayer.triggerEvent('esx:registerSuggestions', ESX.RegisteredCommands) + + if cb ~= nil then + cb() + end + + end) + + end) + +end + +AddEventHandler('esx:player:load:accounts', function(identifier, playerId, row, userData, addTask) + + addTask(function(cb) + + local data = {} + local foundAccounts = {} + + if row.accounts and row.accounts ~= '' then + + local accounts = json.decode(row.accounts) + + for account, money in pairs(accounts) do + foundAccounts[account] = money + end + + end + + for account, label in pairs(Config.Accounts) do + + table.insert(data, { + name = account, + money = foundAccounts[account] or Config.StartingAccountMoney[account] or 0, + label = label + }) + + end + + cb({accounts = data}) + + end) + +end) + +AddEventHandler('esx:player:load:job', function(identifier, playerId, row, userData, addTask) + + addTask(function(cb) + + local data = {} + local jobObject, gradeObject = {}, {} + + if ESX.DoesJobExist(row.job, row.job_grade) then + jobObject, gradeObject = ESX.Jobs[row.job], ESX.Jobs[row.job].grades[tostring(row.job_grade)] + else + + print(('[es_extended] [^3WARNING^7] Ignoring invalid job for %s [job: %s, grade: %s]'):format(identifier, job, grade)) + + job, grade = 'unemployed', '0' + jobObject, gradeObject = ESX.Jobs[row.job], ESX.Jobs[row.job].grades[tostring(rrow.job_grade)] + + end + + data.id = jobObject.id + data.name = jobObject.name + data.label = jobObject.label + + data.grade = row.job_grade + data.grade_name = gradeObject.name + data.grade_label = gradeObject.label + data.grade_salary = gradeObject.salary + + data.skin_male = {} + data.skin_female = {} + + if gradeObject.skin_male then data.skin_male = json.decode(gradeObject.skin_male) end + if gradeObject.skin_female then data.skin_female = json.decode(gradeObject.skin_female) end + + cb({job = data}) + + end) + +end) + +AddEventHandler('esx:player:load:inventory', function(identifier, playerId, row, userData, addTask) + + addTask(function(cb) + + local data = { + weight = userData.weight, + inventory = {} + } + + local foundItems = {} + + if row.inventory and row.inventory ~= '' then + + local inventory = json.decode(row.inventory) + + for name,count in pairs(inventory) do + + local item = ESX.Items[name] + + if item then + foundItems[name] = count + else + print(('[es_extended] [^3WARNING^7] Ignoring invalid item "%s" for "%s"'):format(name, identifier)) + end + end + + end + + for name,item in pairs(ESX.Items) do + + local count = foundItems[name] or 0 + + if count > 0 then + data.weight = data.weight + (item.weight * count) + end + + table.insert(data.inventory, { + name = name, + count = count, + label = item.label, + weight = item.weight, + usable = ESX.UsableItemsCallbacks[name] ~= nil, + rare = item.rare, + canRemove = item.canRemove + }) + + end + + table.sort(data.inventory, function(a, b) + return a.label < b.label + end) + + cb(data) + + end) + +end) + +AddEventHandler('esx:player:load:group', function(identifier, playerId, row, userData, addTask) + + addTask(function(cb) + + local data = {} + + if row.group then + data = row.group + else + data.group = 'user' + end + + cb({group = data}) + + end) + +end) + +AddEventHandler('esx:player:load:loadout', function(identifier, playerId, row, userData, addTask) + + addTask(function(cb) + + local data = {} + + if row.loadout and row.loadout ~= '' then + + local loadout = json.decode(row.loadout) + + for name,weapon in pairs(loadout) do + + local label = ESX.GetWeaponLabel(name) + + if label then + + if not weapon.components then weapon.components = {} end + if not weapon.tintIndex then weapon.tintIndex = 0 end + + table.insert(data, { + name = name, + ammo = weapon.ammo, + label = label, + components = weapon.components, + tintIndex = weapon.tintIndex + }) + + end + end + + end + + cb({loadout = data}) + + end) + +end) + +AddEventHandler('esx:player:load:position', function(identifier, playerId, row, userData, addTask) + + addTask(function(cb) + + local data = {} + + if row.position and row.position ~= '' then + data = json.decode(row.position) + else + print('[es_extended] [^3WARNING^7] Column "position" in "users" table is missing required default value. Using backup coords, fix your database.') + data = {x = -269.4, y = -955.3, z = 31.2, heading = 205.8} + end + + cb({coords = data}) + + end) + +end) diff --git a/server/functions.lua b/server/functions.lua index b4515a50..8e02c736 100644 --- a/server/functions.lua +++ b/server/functions.lua @@ -231,30 +231,41 @@ ESX.TriggerServerCallback = function(name, requestId, source, cb, ...) end ESX.SavePlayer = function(xPlayer, cb) - local asyncTasks = {} - table.insert(asyncTasks, function(cb2) - MySQL.Async.execute('UPDATE users SET accounts = @accounts, job = @job, job_grade = @job_grade, `group` = @group, loadout = @loadout, position = @position, inventory = @inventory WHERE identifier = @identifier', { - ['@accounts'] = json.encode(xPlayer.getAccounts(true)), - ['@job'] = xPlayer.job.name, - ['@job_grade'] = xPlayer.job.grade, - ['@group'] = xPlayer.getGroup(), - ['@loadout'] = json.encode(xPlayer.getLoadout(true)), - ['@position'] = json.encode(xPlayer.getCoords()), - ['@identifier'] = xPlayer.getIdentifier(), - ['@inventory'] = json.encode(xPlayer.getInventory(true)) - }, function(rowsChanged) - cb2() - end) - end) + local data = xPlayer.serializeDB() + local statement = 'UPDATE users SET' + local fields = {} + + local first = true + + for k,v in pairs(data) do + + if first then + statement = statement .. ' `' .. k .. '` = ' .. '@' .. k + first = false + else + statement = statement .. ', `' .. k .. '` = ' .. '@' .. k + end + + fields['@' .. k] = v + + end + + statement = statement .. ' WHERE `identifier` = @identifier' + + print(statement) + print(ESX.DumpTable(fields)) + + MySQL.Async.execute(statement, fields, function(rowsChanged) - Async.parallel(asyncTasks, function(results) print(('[es_extended] [^2INFO^7] Saved player "%s^7"'):format(xPlayer.getName())) - if cb then + if cb then cb() - end - end) + end + + end) + end ESX.SavePlayers = function(cb) diff --git a/server/main.lua b/server/main.lua index f698a3a9..519b5404 100644 --- a/server/main.lua +++ b/server/main.lua @@ -50,9 +50,13 @@ function onPlayerJoined(playerId) MySQL.Async.fetchScalar('SELECT 1 FROM users WHERE identifier = @identifier', { ['@identifier'] = identifier }, function(result) - if result then - loadESXPlayer(identifier, playerId) - else + + if result then + + LoadExtendedPlayer(identifier, playerId) + + else + local accounts = {} for account,money in pairs(Config.StartingAccountMoney) do @@ -63,8 +67,9 @@ function onPlayerJoined(playerId) ['@accounts'] = json.encode(accounts), ['@identifier'] = identifier }, function(rowsChanged) - loadESXPlayer(identifier, playerId) - end) + LoadExtendedPlayer(identifier, playerId) + end) + end end) end @@ -96,163 +101,6 @@ AddEventHandler('playerConnecting', function(name, setCallback, deferrals) end end) - -function loadESXPlayer(identifier, playerId) - local tasks = {} - - local userData = { - accounts = {}, - inventory = {}, - job = {}, - loadout = {}, - playerName = GetPlayerName(playerId), - weight = 0 - } - - table.insert(tasks, function(cb) - MySQL.Async.fetchAll('SELECT accounts, job, job_grade, `group`, loadout, position, inventory FROM users WHERE identifier = @identifier', { - ['@identifier'] = identifier - }, function(result) - local job, grade, jobObject, gradeObject = result[1].job, tostring(result[1].job_grade) - local foundAccounts, foundItems = {}, {} - - -- Accounts - if result[1].accounts and result[1].accounts ~= '' then - local accounts = json.decode(result[1].accounts) - - for account,money in pairs(accounts) do - foundAccounts[account] = money - end - end - - for account,label in pairs(Config.Accounts) do - table.insert(userData.accounts, { - name = account, - money = foundAccounts[account] or Config.StartingAccountMoney[account] or 0, - label = label - }) - end - - -- Job - if ESX.DoesJobExist(job, grade) then - jobObject, gradeObject = ESX.Jobs[job], ESX.Jobs[job].grades[grade] - else - print(('[es_extended] [^3WARNING^7] Ignoring invalid job for %s [job: %s, grade: %s]'):format(identifier, job, grade)) - job, grade = 'unemployed', '0' - jobObject, gradeObject = ESX.Jobs[job], ESX.Jobs[job].grades[grade] - end - - userData.job.id = jobObject.id - userData.job.name = jobObject.name - userData.job.label = jobObject.label - - userData.job.grade = tonumber(grade) - userData.job.grade_name = gradeObject.name - userData.job.grade_label = gradeObject.label - userData.job.grade_salary = gradeObject.salary - - userData.job.skin_male = {} - userData.job.skin_female = {} - - if gradeObject.skin_male then userData.job.skin_male = json.decode(gradeObject.skin_male) end - if gradeObject.skin_female then userData.job.skin_female = json.decode(gradeObject.skin_female) end - - -- Inventory - if result[1].inventory and result[1].inventory ~= '' then - local inventory = json.decode(result[1].inventory) - - for name,count in pairs(inventory) do - local item = ESX.Items[name] - - if item then - foundItems[name] = count - else - print(('[es_extended] [^3WARNING^7] Ignoring invalid item "%s" for "%s"'):format(name, identifier)) - end - end - end - - for name,item in pairs(ESX.Items) do - local count = foundItems[name] or 0 - if count > 0 then userData.weight = userData.weight + (item.weight * count) end - - table.insert(userData.inventory, { - name = name, - count = count, - label = item.label, - weight = item.weight, - usable = ESX.UsableItemsCallbacks[name] ~= nil, - rare = item.rare, - canRemove = item.canRemove - }) - end - - table.sort(userData.inventory, function(a, b) - return a.label < b.label - end) - - -- Group - if result[1].group then - userData.group = result[1].group - else - userData.group = 'user' - end - - -- Loadout - if result[1].loadout and result[1].loadout ~= '' then - local loadout = json.decode(result[1].loadout) - - for name,weapon in pairs(loadout) do - local label = ESX.GetWeaponLabel(name) - - if label then - if not weapon.components then weapon.components = {} end - if not weapon.tintIndex then weapon.tintIndex = 0 end - - table.insert(userData.loadout, { - name = name, - ammo = weapon.ammo, - label = label, - components = weapon.components, - tintIndex = weapon.tintIndex - }) - end - end - end - - -- Position - if result[1].position and result[1].position ~= '' then - userData.coords = json.decode(result[1].position) - else - print('[es_extended] [^3WARNING^7] Column "position" in "users" table is missing required default value. Using backup coords, fix your database.') - userData.coords = {x = -269.4, y = -955.3, z = 31.2, heading = 205.8} - end - - cb() - end) - end) - - Async.parallel(tasks, function(results) - local xPlayer = CreateExtendedPlayer(playerId, identifier, userData.group, userData.accounts, userData.inventory, userData.weight, userData.job, userData.loadout, userData.playerName, userData.coords) - ESX.Players[playerId] = xPlayer - TriggerEvent('esx:playerLoaded', playerId, xPlayer) - - xPlayer.triggerEvent('esx:playerLoaded', { - accounts = xPlayer.getAccounts(), - coords = xPlayer.getCoords(), - identifier = xPlayer.getIdentifier(), - inventory = xPlayer.getInventory(), - job = xPlayer.getJob(), - loadout = xPlayer.getLoadout(), - maxWeight = xPlayer.getMaxWeight(), - money = xPlayer.getMoney() - }) - - xPlayer.triggerEvent('esx:createMissingPickups', ESX.Pickups) - xPlayer.triggerEvent('esx:registerSuggestions', ESX.RegisteredCommands) - end) -end - AddEventHandler('chatMessage', function(playerId, author, message) if message:sub(1, 1) == '/' and playerId > 0 then CancelEvent()