attempt to index a nil value (local 'xPlayer'): исправление ESX
xPlayer равна nil в вашем скрипте ESX? Почему ESX.GetPlayerFromId(source) возвращает nil: игрок не загружен, source потерян после Wait, строковые id, playerDropped. С паттернами охраны.
Консоль сервера показывает:
SCRIPT ERROR: @my_script/server/main.lua:23: attempt to index a nil value (local 'xPlayer')local xPlayer = ESX.GetPlayerFromId(source)
xPlayer.addMoney(100) -- xPlayer is nilESX.GetPlayerFromId не нашла игрока для этого id и вернула nil, и следующая строка индексировала его. Вызов в порядке. Id, которые вы ей дали, не совпадает с загруженным игроком ESX. Вот обычные причины и как защитить каждую.
1. Игрок еще не загружен
Игрок подключен к серверу, прежде чем ESX закончит загрузку их персонажа. Во время этого промежутка и пока открыт экран мультииграка, ESX.GetPlayerFromId(source) возвращает nil.
Это попадает в код, который запускается рано, такой как общие события FiveM playerJoining или playerConnecting, или поток, который запускается в момент запуска вашего ресурса.
Используйте событие ESX, которое срабатывает, когда игрок готов, на сервере:
AddEventHandler('esx:playerLoaded', function(playerId, xPlayer, isNew)
print(('%s is loaded'):format(xPlayer.getName()))
end)На клиенте то же имя события говорит вашему клиентскому скрипту, что игрок может быть использован:
RegisterNetEvent('esx:playerLoaded', function(xPlayer)
PlayerLoaded = true
end)Если ваш скрипт может быть перезагружен, пока игроки в сети, это событие уже для них сработало. При запуске зациклитесь над игроками, которые уже есть у ESX:
CreateThread(function()
for _, playerId in ipairs(GetPlayers()) do
local xPlayer = ESX.GetPlayerFromId(tonumber(playerId))
if xPlayer then
-- set up the player
end
end
end)2. source изменился после Wait
source — это специальное глобальное значение, которое FiveM устанавливает для работающего события. Если вы ждете внутри обработчика, другое событие может запуститься тем временем и source больше не принадлежит вашему игроку.
RegisterNetEvent('my_script:buy', function(item)
Wait(500)
local xPlayer = ESX.GetPlayerFromId(source) -- source may be someone else, or invalid
end)Храните его в локальном перед чем-либо еще:
RegisterNetEvent('my_script:buy', function(item)
local src = source
Wait(500)
local xPlayer = ESX.GetPlayerFromId(src)
if not xPlayer then return end
end)То же применяется внутри обратного вызова или функции, которой вы передаете source позже: передайте сохраненное значение, а не глобальное.
3. Id неправильный
ESX.GetPlayerFromId нужна серверная id как число игрока, который в сети.
- Строковая id. Аргументы команды — это текст.
ESX.GetPlayerFromId(args[1])может вернуть nil, гдеESX.GetPlayerFromId(tonumber(args[1]))работает. - Неправильная id с клиента. На клиенте
PlayerId()— это локальный индекс, а не серверная id. Серверная id — этоGetPlayerServerId(PlayerId()). Отправка неправильной дает игрока, которого не существует, или неправильного игрока. - Консоль. Команда, введенная в консоль сервера, имеет
sourceравный0, что не является игроком. - Id из поиска по идентификатору.
ESX.GetPlayerFromIdentifier(identifier)только находит игроков, которые в сети. Для оффлайн игрока читайте базу данных вместо этого, смотрите oxmysql queries guide.
RegisterCommand('givecash', function(source, args)
local target = tonumber(args[1])
local amount = tonumber(args[2])
if not target or not amount then
return print('Usage: /givecash [id] [amount]')
end
local xTarget = ESX.GetPlayerFromId(target)
if not xTarget then
return print('Player not found or not loaded')
end
xTarget.addMoney(amount)
end, true)Не доверяйте id, отправленной с клиента для того, что имеет значение. Используйте source, который не может быть поддельным, как объяснено в securing server events.
4. Игрок уехал
Если игрок отключится, пока ваш код ждет, например в длинном цикле или таймере, GetPlayerFromId возвращает nil, когда он наконец запустится. Это нормальный случай, не ошибка, и ему нужна та же охрана.
Внутри playerDropped ситуация менее ясна, потому что ESX также обрабатывает это событие для сохранения и удаления игрока, и порядок обработчиков — это не то, на что полагаться. ESX Legacy также запускает свое собственное событие esx:playerDropped, но проверьте, что у вашей версии оно есть. Наиболее надежный подход — держать данные сами, пока игрок в сети:
local sessions = {}
AddEventHandler('esx:playerLoaded', function(playerId, xPlayer)
sessions[playerId] = {
identifier = xPlayer.identifier,
job = xPlayer.job.name,
}
end)
AddEventHandler('playerDropped', function()
local data = sessions[source]
if data then
print(('%s left, job %s'):format(data.identifier, data.job))
sessions[source] = nil
end
end)Паттерн охраны
Начните каждый обработчик сервера, который использует игрока, одинаково:
RegisterNetEvent('my_script:sell', function(item, count)
local src = source
local xPlayer = ESX.GetPlayerFromId(src)
if not xPlayer then return end
-- from here xPlayer is safe to use
xPlayer.addMoney(100)
end)Та же проверка идет внутри серверных обратных вызовов, зарегистрированных с помощью ESX.RegisterServerCallback, где первый аргумент — это источник игрока:
ESX.RegisterServerCallback('my_script:getJob', function(source, cb)
local xPlayer = ESX.GetPlayerFromId(source)
if not xPlayer then return cb(nil) end
cb(xPlayer.job.name)
end)Проверьте также сторону клиента: она должна принять nil ответ от обратного вызова.
Совет: если ошибка вместо этого
attempt to index a nil value (global 'ESX'), проблема в объекте ESX, а не в игроке. Смотрите ESX is nil: fixing esx:getSharedObject.
Контрольный список
| Симптом | Исправление |
|---|---|
| Nil сразу после присоединения игрока | Ждите esx:playerLoaded вместо playerJoining |
Nil после Wait |
Сохраните local src = source первым и используйте src |
| Nil из команды | Преобразуйте аргумент с помощью tonumber; source равен 0 в консоли |
| Nil с id, отправленной клиентом | Используйте source; на клиенте серверная id — это GetPlayerServerId(PlayerId()) |
Nil в playerDropped |
Держите нужные вам данные в своей таблице, пока игрок в сети |
| Nil для оффлайн игрока | GetPlayerFromId только находит онлайн игроков; запросите базу данных |
| Любой nil | Добавьте if not xPlayer then return end в верх обработчика |
Короткие ответы
Почему xPlayer равна nil в ESX?
ESX.GetPlayerFromId(source) возвращает nil, когда ESX не имеет загруженного игрока для этого id. Игрок может еще не закончить загрузку, id может быть неправильным или строкой, или игрок уже уехал.
Должен ли я проверять xPlayer в каждом событии?
Да. Любое событие сервера может быть вызвано с игроком, который не загружен, поэтому начните обработчик с if not xPlayer then return end. Это ничего не стоит и предотвращает ошибку.
Могу ли я использовать xPlayer внутри playerDropped?
На это полагаться не безопасно, потому что ESX может уже удалить игрока. Держите нужные вам данные в своей таблице, пока игрок в сети, и читайте их оттуда.
Скрипты без этой проблемы
Shop CreatorМагазин меньше чем за минуту — владельцы, сотрудники, сейфы и ограбления в комплекте.Смотреть скрипт →
Pawn Shop AppЛомбард между игроками прямо в lb-phone.Смотреть скрипт →
Drug Dealer AppУличная торговля в виде приложения lb-phone: зоны, NPC-покупатели, уровни и вызовы полиции.Смотреть скрипт →