- 78
- 127
- Версия MoonLoader
- .026-beta
РЕШЕНИЕ: Избавился от потоков по совету wojciech? и ошибка перестала появляться.
Сделал скрипт, улучшающий интерфейс на Arizona RP, и теперь стабильно ловлю краши, помогите, люди добрые :')
Как я предполагаю, это случается из-за отмены рендера диалога через sampev.onShowDialog, когда возвращаешь false в качестве результата, причём без какой-то закономерности, но глубже докопаться не могу, ибо до этого ничего под SAMP не писал ( к слову, буду рад конструктивной критике от олдов скриптописания ).
Скрипт:
Логи moonloader с дебагом ( нас интересует в основном самое начало и конец ): https://pastebin.com/Cu8X9LKX
Отчёт краша: https://crash.sr.team/?uid=52e1786e-46482b32-56e669f9-79dcefad-dae3f8a8-7f24385d-ef42c033-999e6ed5
Скриншот краша:
Сделал скрипт, улучшающий интерфейс на Arizona RP, и теперь стабильно ловлю краши, помогите, люди добрые :')
Как я предполагаю, это случается из-за отмены рендера диалога через sampev.onShowDialog, когда возвращаешь false в качестве результата, причём без какой-то закономерности, но глубже докопаться не могу, ибо до этого ничего под SAMP не писал ( к слову, буду рад конструктивной критике от олдов скриптописания ).
Скрипт:
Lua:
-- Imports
require 'lib.moonloader'
local sampev = require 'lib.samp.events'
local gui = require 'mimgui'
local icons = require 'lib.fAwesome6'
local encoding = require 'encoding'
encoding.default = 'CP1251'
local u8 = encoding.UTF8
-- Script directives
script_name('Player Information Collector')
script_description('Display useful information about account statistics in interface')
script_version('1.0.0b')
script_author('NyashMyash99')
-- Variables
local updatePeriod = 60 * 1000
local isUpdateEnabled = false
local quests = {}
local questsKeys = {}
local selectedQuest = nil
local selectedQuestIndex = -1
local playerData = {
level = 0,
exp = 0,
nextExp = 0,
bankMoney = 0,
depositeMoney = 0
}
local font = {}
local targetDialog = -1
local isLoaded = false
local debug = true
function main()
sendDebug('[main] Waiting for game client to be ready.')
while not isSampAvailable() do wait(500) end
sendDebug('[main] Game client is ready, start initializing script.')
sendMessage('')
sendMessage(script.this.name .. ' - ' .. table.concat(script.this.authors, ', ') .. ' (' .. script.this.version .. ')')
sendMessage(script.this.description)
sendMessage('')
while true do
-- switch active quest
if isKeyJustPressed(VK_NUMPAD4) then
sendDebug('[NUMPAD 4] Key is pressed.')
sendDebug('[NUMPAD 4] Past index of active quest: ' .. tostring(selectedQuestIndex) .. '.')
selectedQuestIndex = selectedQuestIndex - 1
sendDebug('[NUMPAD 4] Expected index of active quest: ' .. tostring(selectedQuestIndex) .. '.')
-- go to opposite end of list
if selectedQuestIndex < 1 then
sendDebug('[NUMPAD 4] Expected active quest index is less than one, change to "' .. tostring(#questsKeys) .. '".')
selectedQuestIndex = #questsKeys
end
sendDebug('[NUMPAD 4] New index of active quest: ' .. tostring(selectedQuestIndex) .. '/' .. tostring(#questsKeys) .. '.')
sendDebug('[NUMPAD 4] New active quest: ' .. tostring(questsKeys[selectedQuestIndex]) .. '.')
selectedQuest = questsKeys[selectedQuestIndex]
end
-- switch active quest
if isKeyJustPressed(VK_NUMPAD6) then
sendDebug('[NUMPAD 6] Key is pressed.')
sendDebug('[NUMPAD 6] Past index of active quest: ' .. tostring(selectedQuestIndex) .. '.')
selectedQuestIndex = selectedQuestIndex + 1
sendDebug('[NUMPAD 6] Expected index of active quest: ' .. tostring(selectedQuestIndex) .. '.')
-- go to opposite end of list
if selectedQuestIndex > #questsKeys then
sendDebug('[NUMPAD 6] Expected active quest index is greater than number of quests, change to "1".')
selectedQuestIndex = 1
end
sendDebug('[NUMPAD 6] New index of active quest: ' .. tostring(selectedQuestIndex) .. '/' .. tostring(#questsKeys) .. '.')
sendDebug('[NUMPAD 6] New active quest: ' .. tostring(questsKeys[selectedQuestIndex]) .. '.')
selectedQuest = questsKeys[selectedQuestIndex]
end
-- toggle auto-update function
if isKeyJustPressed(VK_NUMPAD5) then
sendDebug('[NUMPAD 5] Key is pressed.')
sendDebug('[NUMPAD 5] Past "isUpdateEnabled" value: ' .. tostring(isUpdateEnabled) .. '.')
isUpdateEnabled = not isUpdateEnabled
sendDebug('[NUMPAD 5] New "isUpdateEnabled" value: ' .. tostring(isUpdateEnabled) .. '.')
local statusMessage = isUpdateEnabled and '{00FF00}enabled' or '{FF0000}disabled'
sendMessage('Automatic data update ' .. statusMessage)
-- if task has been disabled - start it
if isUpdateEnabled then
sendDebug('[NUMPAD 5] Update task was previously disabled - enable it.')
updatePlayerData()
end
end
-- early data update
if isKeyJustPressed(VK_NUMPAD0) then
sendDebug('[NUMPAD 0] Key is pressed.')
sendDebug('[NUMPAD 0] Force updating player data.')
updatePlayerData(true)
sendMessage("Updating player data..")
end
-- switch interface visibility (mostly needed for script hot rebooting)
if isKeyJustPressed(VK_SUBTRACT) then
sendDebug('[NUMPAD -] Key is pressed.')
sendDebug('[NUMPAD -] Past "isLoaded" value: ' .. tostring(isLoaded) .. '.')
isLoaded = not isLoaded
sendDebug('[NUMPAD -] New "isLoaded" value: ' .. tostring(isLoaded) .. '.')
end
wait(0)
end
end
-- GUI
gui.OnInitialize(function()
-- disable ini config. By default it is saved to moonloader/config/mimgui/%scriptfilename%.ini
gui.GetIO().IniFilename = nil
-- clear loaded fonts to make ours main font
sendDebug('[OnInitialize] Cleaning up old fonts.')
gui.GetIO().Fonts:Clear()
sendDebug('[OnInitialize] Old fonts cleaned.')
-- pulling font from arizona launcher
local fontPath = getWorkingDirectory() .. '\\..\\frontend\\vue_js\\fonts\\'
local fontFile = 'SFProDisplay-Semibold.0538ddc9.ttf'
sendDebug('[OnInitialize] Installing "' .. tostring(fontPath) .. tostring(fontFile) .. '" font.')
font[18] = gui.GetIO().Fonts:AddFontFromFileTTF(
fontPath .. fontFile,
18,
_, gui.GetIO().Fonts:GetGlyphRangesCyrillic()
)
sendDebug('[OnInitialize] Font "' .. tostring(fontPath) .. tostring(fontFile) .. '" installed.')
fontFile = 'SFProDisplay-Regular.6987bcc4.ttf'
sendDebug('[OnInitialize] Installing "' .. tostring(fontPath) .. tostring(fontFile) .. '" font.')
font[30] = gui.GetIO().Fonts:AddFontFromFileTTF(
fontPath .. fontFile,
30,
_, gui.GetIO().Fonts:GetGlyphRangesCyrillic()
)
sendDebug('[OnInitialize] Font "' .. tostring(fontPath) .. tostring(fontFile) .. '" installed.')
loadIconicFont()
-- font texture invalidation forces the font texture to rebuild. It is necessary after font modifications
sendDebug('[OnInitialize] Invalidating fonts texture.')
gui.InvalidateFontsTexture()
sendDebug('[OnInitialize] Fonts texture invalidated.')
-- run interface update
sendDebug('[OnInitialize] Updating player data.')
updatePlayerData()
end)
--- render interface if player has already loaded and isn't in game menu
gui.OnFrame(
function() return not isPauseMenuActive() and isLoaded end,
function() renderInterface() end
).HideCursor = true
function loadIconicFont()
local config = gui.ImFontConfig()
config.MergeMode = true
config.PixelSnapH = true
sendDebug('[loadIconicFont] Installing "fa-icons" font.')
font['icon'] = gui.GetIO().Fonts:AddFontFromMemoryCompressedBase85TTF(
icons.get_font_data_base85('bold'),
24,
config,
gui.new.ImWchar[3](icons.min_range, icons.max_range, 0)
)
sendDebug('[loadIconicFont] "fa-icons" font installed.')
end
-- Event handlers
--- waits for a special message to activate interface display
function sampev.onServerMessage(_, text)
if stripColors(text) == '[Подсказка] На сервере есть инвентарь, используйте клавишу Y для работы с ним.' then
sendDebug('[onServerMessage] Successful load message received.')
isLoaded = true
end
end
--- handles dialogs without showing them to player if they were called by a script
function sampev.onShowDialog(id, _, _, _, _, text)
sendDebug('[onShowDialog] (' .. tostring(id) .. ') Dialog received.')
if id ~= targetDialog then
sendDebug('[onShowDialog] (' .. tostring(id) .. ') Showing dialog.')
return true
end
sendDebug('[onShowDialog] (' .. tostring(id) .. ') Proccessing dialog.')
proccessTargetDialog(id, text)
sendDebug('[onShowDialog] (' .. tostring(id) .. ') Canceling dialog.')
return false
end
-- Functions
--- starts task of updating data once in updatePeriod
function updatePlayerData(force)
lua_thread.create(
function()
sendDebug('[updatePlayerData] Update request received.')
sendDebug('[updatePlayerData] Starting collecting statistics.')
openStatisticDialog()
-- waiting for dialog to be processed
while targetDialog ~= -1 do
sendDebug('[updatePlayerData] Statistics collection isn\'t yet complete, pending.')
wait(300)
end
sendDebug('[updatePlayerData] Starting collecting quests.')
openActiveQuestsDialog()
-- waiting for dialog to be processed
while targetDialog ~= -1 do
sendDebug('[updatePlayerData] Quests collection isn\'t yet complete, pending.')
wait(300)
end
-- waiting for next renewal period
sendDebug('[updatePlayerData] Waiting for next update task.')
wait(updatePeriod)
sendDebug('[updatePlayerData] Waited for next update task.')
-- if auto-update is enabled - start task again
if not force and isUpdateEnabled then
sendDebug('[updatePlayerData] Starting new update task (force = ' .. tostring(force) .. ', isUpdateEnabled = ' .. tostring(isUpdateEnabled) .. ').')
updatePlayerData()
end
end
)
end
function openActiveQuestsDialog()
lua_thread.create(
function()
sendDebug('[openActiveQuestsDialog] Discovery request received.')
-- preventing dialog race
while targetDialog ~= -1 do
sendDebug('[openActiveQuestsDialog] Waiting for queue to be released.')
wait(50)
end
-- target 'Quests' dialog and enter command
targetDialog = 25862
sendDebug('[openActiveQuestsDialog] Dispatching command.')
sampSendChat('/quest')
sendDebug('[openActiveQuestsDialog] Command dispatched.')
end
)
end
function openStatisticDialog()
lua_thread.create(
function()
sendDebug('[openStatisticDialog] Discovery request received.')
-- preventing dialog race
while targetDialog ~= -1 do
sendDebug('[openStatisticDialog] Waiting for queue to be released.')
wait(50)
end
-- target 'My Statistics' dialog and enter command
targetDialog = 235
sendDebug('[openStatisticDialog] Dispatching command.')
sampSendChat('/stats')
sendDebug('[openStatisticDialog] Command dispatched.')
end
)
end
function proccessTargetDialog(id, content)
sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Processing request received.')
-- if current dialog is 'Quests'..
if id == 25862 then
-- target 'Active quests' dialog and select corresponding menu item
targetDialog = 7650
sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Sending menu click.')
sampSendDialogResponse(25862, 1, 0, nil)
sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Menu click sent.')
return
end
-- if current dialog is 'Active quests'..
if id == 7650 then
sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Prepating dialog content.')
local preparedContent = split(content, '\n')
sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Dialog content prepared.')
-- clearing old data
quests = {}
questsKeys = {}
-- run through all lines of dialog
for _, value in ipairs(preparedContent) do
-- sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Proccessing value: ' .. tostring(value) .. '.')
-- remove colors from text
value = stripColors(value)
-- sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Stripped value: ' .. tostring(value) .. '.')
-- sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Matching questTitle.')
local questTitle = value:match('(.+) |')
-- sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') questTitle: ' .. tostring(questTitle) .. '.')
-- if we find title of quest - delete category from there
if questTitle then
-- sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Proccessing title: ' .. tostring(questTitle) .. '.')
questTitle = questTitle:gsub('%[.+%]', '')
-- sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Proccessed title: ' .. tostring(questTitle) .. '.')
end
-- sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Matching questMatch.')
local questMatch = value:match('Прогресс: (.+)')
-- sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') questMatch: ' .. tostring(questMatch) .. '.')
-- if we find progress - save quest
if questMatch then
-- sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Proccessing progress: ' .. tostring(questMatch) .. '.')
questMatch = questMatch:gsub('[%[%]]', '')
-- sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Proccessed progress: ' .. tostring(questMatch) .. '.')
sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Found "' .. tostring(questTitle) .. '" quest with progress: ' .. tostring(questMatch) .. '.')
quests[questTitle] = questMatch
-- save key, which will be useful for switching active quest
table.insert(questsKeys, questTitle)
sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Saved "' .. tostring(questTitle) .. '" quest with progress: ' .. tostring(questMatch) .. '.')
end
end
-- if active quest isn't selected or deleted - select first found one
if not quests[selectedQuest] then
sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Updating active quest.')
selectedQuest = questsKeys[1]
selectedQuestIndex = 1
sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') New active quest selected: ' .. tostring(selectedQuest) .. '.')
end
-- release pending dialog
targetDialog = -1
sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Dialogue capture released.')
return
end
-- if current dialog is 'My Statistics'..
if id == 235 then
sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Prepating dialog content.')
local preparedContent = split(content, '\n')
sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Dialog content prepared.')
-- run through all lines of dialog
for _, value in ipairs(preparedContent) do
-- sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Proccessing value: ' .. tostring(value) .. '.')
-- remove colors from text
value = stripColors(value)
-- sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Stripped value: ' .. tostring(value) .. '.')
-- sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Matching levelMatch.')
local levelMatch = value:match('Уровень: %[(%d+)]')
-- sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') levelMatch: ' .. tostring(levelMatch) .. '.')
-- sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Matching respectMatch.')
local respectMatch = value:match('Уважение: %[(%d+/%d+)]')
-- sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') respectMatch: ' .. tostring(respectMatch) .. '.')
-- sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Matching bankMoneyMatch.')
local bankMoneyMatch = value:match('Деньги в банке: %[%$(%d+)]')
-- sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') bankMoneyMatch: ' .. tostring(bankMoneyMatch) .. '.')
-- sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Matching depositeMoneyMatch.')
local depositeMoneyMatch = value:match('Деньги на депозите: %[%$(%d+)]')
-- sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') depositeMoneyMatch: ' .. tostring(depositeMoneyMatch) .. '.')
-- if we find a level - save it
if levelMatch then
local level = tonumber(levelMatch)
playerData.level = level
sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Found level: ' .. tostring(level) .. '.')
end
-- if we find a respect - save it
if respectMatch then
local exp = tonumber(respectMatch:match('%d+'))
local nextExp = tonumber(respectMatch:match('/(%d+)'))
playerData.exp = exp
playerData.nextExp = nextExp
sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Found respect: ' .. tostring(exp) .. '/' .. tostring(nextExp) .. '.')
end
-- if we find a bank money - save it
if bankMoneyMatch then
local money = tonumber(bankMoneyMatch)
playerData.bankMoney = money
sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Found bank money: ' .. tostring(money) .. '.')
end
-- if we find a deposite money - save it
if depositeMoneyMatch then
local money = tonumber(depositeMoneyMatch)
playerData.depositeMoney = money
sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Found deposite money: ' .. tostring(money) .. '.')
end
end
-- release pending dialog
targetDialog = -1
sendDebug('[proccessTargetDialog] (' .. tostring(id) .. ') Dialogue capture released.')
return
end
end
--- render interface with player data
function renderInterface()
local windowWidth = getScreenResolution()
local overlay = gui.GetBackgroundDrawList()
-- quests background
overlay:AddRectFilled(
gui.ImVec2(windowWidth - 600, 97),
gui.ImVec2(windowWidth - 395, 167),
0xE6090909,
16, 1 + 4
)
-- level
overlay:AddText(
gui.ImVec2(windowWidth - 600, 21.5),
0xFFA5A6A5,
'LVL:'
)
overlay:AddText(
gui.ImVec2(windowWidth - 566, 21.5),
0xFFFFFFFF,
tostring(playerData.level)
)
-- exp
overlay:AddText(
gui.ImVec2(windowWidth - 600, 49.5),
0xFFA5A6A5,
'EXP:'
)
overlay:AddText(
gui.ImVec2(windowWidth - 566, 49.5),
0xFFFFFFFF,
tostring(playerData.exp) .. '/' .. tostring(playerData.nextExp)
)
-- quests
if selectedQuest then
overlay:AddText(
gui.ImVec2(windowWidth - 580, 112),
0xFFFFFFFF,
u8(overlap(tostring(selectedQuest)))
)
overlay:AddText(
gui.ImVec2(windowWidth - 580, 132),
0xFFFFFFFF,
u8(overlap(tostring(quests[selectedQuest])))
)
else
overlay:AddText(
gui.ImVec2(windowWidth - 580, 122),
0xFFFFFFFF,
u8'Загрузка квестов..'
)
end
gui.PushFont(font['icon'])
-- bank money
overlay:AddText(
gui.ImVec2(windowWidth - 240, 225),
0xFF68FF65,
icons['BUILDING_COLUMNS']
)
-- deposite money
overlay:AddText(
gui.ImVec2(windowWidth - 242, 265),
0xFF68FF65,
icons['PIGGY_BANK']
)
gui.PopFont()
gui.PushFont(font[30])
-- bank money
overlay:AddText(
gui.ImVec2(windowWidth - 210, 225),
0xFFFFFFFF,
tostring(formatMoney(tostring(playerData.bankMoney)))
)
-- deposite money
overlay:AddText(
gui.ImVec2(windowWidth - 210, 265),
0xFFFFFFFF,
tostring(formatMoney(tostring(playerData.depositeMoney)))
)
gui.PopFont()
end
--- sends script-style message to player chat
function sendMessage(message)
sampAddChatMessage('{FFA500}' .. tostring(message), 0xFFA500)
end
--- sends debug message to console if debug is enabled
function sendDebug(message)
if not debug then return end
print(tostring(message))
end
--- separates string by delimiter
function split(str, delimiter)
local result = {}
for match in (str .. delimiter):gmatch('(.-)' .. delimiter) do
table.insert(result, match)
end
return result
end
--- shortens string if it is longer than 20 characters
function overlap(text)
if #text <= 20 then
return text
end
-- get first 20 characters and add ..
return text:sub(1, 20) .. '..'
end
--- сonverts number into readable monetary format
function formatMoney(amount)
return tostring(amount)
:reverse()
:gsub("%d%d%d", "%1 ")
:reverse()
:gsub("^%s+", "")
end
--- removes SAMP colors from text
function stripColors(text)
return text:gsub('{%x+}', '')
end
Логи moonloader с дебагом ( нас интересует в основном самое начало и конец ): https://pastebin.com/Cu8X9LKX
Отчёт краша: https://crash.sr.team/?uid=52e1786e-46482b32-56e669f9-79dcefad-dae3f8a8-7f24385d-ef42c033-999e6ed5
Скриншот краша:
Последнее редактирование: