1 (изменено: zerguddd, 2023-08-12 14:48:53)

Тема: AHK: Скрипт для хелперов. Автоматические ответы на вопросы игроков.

Доброго времени. Имею AHK скрипт для GTA SAMP, со следующим функционалом:
Постоянно проверяет чат, если в чат пришел вопрос, он это видит, с помощью регулярных выражений отрезает лишнее, имея заданный игроком вопрос, обращается в отдельный файл, и ищет по ключевым словам ответ на этот вопрос. Помимо этого, есть возможность отключить автоматический ответ. Все вопросы игроков логируются в отдельный текстовый документ (это необходимо для пополнения базы данных ключевых слов вопросов и ответов), логируются так-же все действия скрипта с указанием нынешних параметров его работы. Скрипт так же проверяет, свернута-ли игра, а так-же изменяет регистр вопросов, чтобы он реагировал на вопрос "как", "КАК", "КаК".
Прикладываю код, а так-же скриншоты, как выглядят файлы логирования и база данных ключевых слов и ответов. (обратится к автору нет возможности, его оставленный контакт уже невалидный)

+ Код скрипта
#SingleInstance force
#Persistent
#IfWinActive GTA:SA:MP
#Include UDFR18.ahk

SetBatchLines -1
ListLines Off

initFile := "database"

; Частота проверки чата на новый вопрос
SetTimer, myTimer, 500

; Инициализация объекта, хранящего пары типа {"регулярное выражение" : "Ответ"}
regExDict := Object()
initRegExepDict()

; Флаг автоотправки ответа (жмет Enter при ответе, или нет)
IniRead, autoSendEnabled, % A_Temp "/aafa.ini", main, autoSendEnabled, % false


; оффсет памяти введенного в чат текста
chatInputoffsetsarr := [0x12D8F8, 0x12D8F8, 0x17FBB8]

~Enter::
while(isInChat())
	Sleep 1
writelog("Нажат Enter")
chatInput := readString(hGTA, dwSAMP + chatInputoffsetsarr[sampVersion], 256)
; парсим введенный в чат текст
if(RegExMatch(chatInput, "^[\./](?P<Command>[^\s]+)\s+(?P<Args>.*?)\s*$", cmd))
{
    if(cmdCommand == "autosend")
    {
        if(cmdArgs)
        {
            autoSendEnabled := true
            addChatMessage("[AHK-SFH] AUTOSEND = 1 (Автоотправка включена)")
            
            IniWrite, % autoSendEnabled, % A_Temp "/aafa.ini", main, autoSendEnabled
        }
        else
        {
            autoSendEnabled := false
            addChatMessage("[AHK-SFH] AUTOSEND = 0 (Автоотправка выключена)")
            
            IniWrite, % autoSendEnabled, % A_Temp "/aafa.ini", main, autoSendEnabled
        }
    }
}
return

writelog(ByRef str)
{
    FileAppend, % "`n[" A_Hour ":" A_Min ":" A_Sec "] " str, aafa_dbg.txt
}

myTimer()
{
    global regExDict
    global autoSendEnabled
    
    static lastAfkStatus
    
    if(lastAfkStatus != IsInAFK())
    {
        lastAfkStatus := IsInAFK()
        writelog("IsInAFK(): " lastAfkStatus)
    }
    
    for k, v in getNewChatLines()
        if(regexmatch(v, "^Вопрос от \S+?\[(\d+)\]: (.+)", parsedStr))
        {
            writelog("Пришел вопрос: " v)
            writelog("autoSendEnabled=" autoSendEnabled " | isInChat()=" isInChat())
            
            FileAppend, % parsedStr2 "`n", % "logfile.txt"
            if(!autoSendEnabled)
            {
				KeyWait, NumpadAdd, D
				SendMessage, 0x50,, 0x4190419,, A
				sendInput, % "{F6}/an " parsedStr1 A_Space
            }
            
            for key in regExDict
                if(RegExMatch(parsedStr2, key))
                {
                    if(!autoSendEnabled)
                    {
                        SendInput, % regExDict[key]
                    }
                    else
                    {
                        addChatMessage("/an " parsedStr1 " " regExDict[key])
                        ;~ sendChat("/an " parsedStr1 " " regExDict[key])
                    }
                        
                }
        }
}

initRegExepDict()
{
	global regExDict
	global initFile
	
	Loop, read, % initFile
		if(RegExMatch(A_LoopReadLine, "^\s*(.+?)\s*:\s*(.+)\s*$", parsedStr))
        {
            parsedStr1 := RegExReplace(parsedStr1, "А|а", "а")
			parsedStr1 := RegExReplace(parsedStr1, "Б|б", "б")
			parsedStr1 := RegExReplace(parsedStr1, "В|в", "в")
			parsedStr1 := RegExReplace(parsedStr1, "Г|г", "г")
			parsedStr1 := RegExReplace(parsedStr1, "Д|д", "д")
			parsedStr1 := RegExReplace(parsedStr1, "Е|е", "е")
			parsedStr1 := RegExReplace(parsedStr1, "Ё|ё", "ё")
			parsedStr1 := RegExReplace(parsedStr1, "Ж|ж", "ж")
			parsedStr1 := RegExReplace(parsedStr1, "З|з", "з")
			parsedStr1 := RegExReplace(parsedStr1, "И|и", "и")
			parsedStr1 := RegExReplace(parsedStr1, "Й|й", "й")
			parsedStr1 := RegExReplace(parsedStr1, "К|к", "к")
			parsedStr1 := RegExReplace(parsedStr1, "Л|л", "л")
			parsedStr1 := RegExReplace(parsedStr1, "М|м", "м")
			parsedStr1 := RegExReplace(parsedStr1, "Н|н", "н")
			parsedStr1 := RegExReplace(parsedStr1, "О|о", "о")
			parsedStr1 := RegExReplace(parsedStr1, "П|п", "п")
			parsedStr1 := RegExReplace(parsedStr1, "Р|р", "р")
			parsedStr1 := RegExReplace(parsedStr1, "С|с", "с")
			parsedStr1 := RegExReplace(parsedStr1, "Т|т", "т")
			parsedStr1 := RegExReplace(parsedStr1, "У|у", "у")
			parsedStr1 := RegExReplace(parsedStr1, "Ф|ф", "ф")
			parsedStr1 := RegExReplace(parsedStr1, "Ц|ц", "ц")
			parsedStr1 := RegExReplace(parsedStr1, "Ч|ч", "ч")
			parsedStr1 := RegExReplace(parsedStr1, "Ш|ш", "ш")
			parsedStr1 := RegExReplace(parsedStr1, "Щ|щ", "щ")
			parsedStr1 := RegExReplace(parsedStr1, "Ъ|ъ", "ъ")
			parsedStr1 := RegExReplace(parsedStr1, "Ы|ы", "ы")
			parsedStr1 := RegExReplace(parsedStr1, "Ь|ь", "ь")
			parsedStr1 := RegExReplace(parsedStr1, "Э|э", "э")
			parsedStr1 := RegExReplace(parsedStr1, "Ю|ю", "ю")
			parsedStr1 := RegExReplace(parsedStr1, "Я|я", "я")
			regExDict[parsedStr1] := parsedStr2
        }
        
    if(!regExDict.Count())
        MsgBox, 48, Ошибка инициализации, Ошибка инициализации:`nФайл %initFile% не заполнен по форме/не найден
}

IsInAFK() {
    res := ProcessReadMemory(0xBA6748 + 0x5C, "gta_sa.exe")
    if (res==-1)
        return -1
    WinGet, win, MinMax, GTA:SA:MP
    if ((res=0) and (win=-1)) or res=1
        return 1
    return 0
}
ProcessReadMemory(address, processIDorName, type := "Int", numBytes := 4) {
    VarSetCapacity(buf, numBytes, 0)
    Process Exist, %processIDorName%
    if !processID := ErrorLevel
        return -1
        ;throw Exception("Invalid process name or process ID:`n`n""" . processIDorName . """")
    if !processHandle := DllCall("OpenProcess", "Int", 24, "UInt", 0, "UInt", processID, "Ptr")
        throw Exception("Failed to open process.`n`nError code:`t" . A_LastError)
    result := DllCall("ReadProcessMemory", "Ptr", processHandle, "Ptr", address, "Ptr", &buf, "Ptr", numBytes, "PtrP", numBytesRead, "UInt")
    if !DllCall("CloseHandle", "Ptr", processHandle, "UInt") && !result
        throw Exception("Failed to close process handle.`n`nError code:`t" . A_LastError)
    if !result
        throw Exception("Failed to read process memory.`n`nError code:`t" . A_LastError)
    if !numBytesRead
        throw Exception("Read 0 bytes from the`n`nprocess:`t" . processIDorName . "`naddress:`t" . address)
    return (type = "Str")
        ? StrGet(&buf, numBytes)
        : NumGet(buf, type)
}

getNewChatLines()
{
    static prevChatLine
    static prevTimeStamp

    if(!checkHandles())
        return
    dwAddress :=    readDWORD(hGTA, dwSAMP + ADDR_SAMP_CHATMSG_PTR[sampVersion]) 
    if ErrorLevel
        return

    chat := []

    curChatLine :=    readString(hGTA, dwAddress + 0x62C6, 0xFC)
    curTimeStamp :=    readMem(hGTA, dwAddress + 0x62A6, 4, "int")
    if(curTimeStamp != prevTimeStamp || curChatLine != prevChatLine)
    {
        if(prevTimeStamp != "")
            Loop 100
            {
                chatLine :=        readString(hGTA, dwAddress + 0x152 + (100 - A_Index) * 0xFC, 0xFC)
                timestamp :=    readMem(hGTA, dwAddress + 0x132 + (100 - A_Index) * 0xFC, 4, "int")
                if(timestamp < prevTimeStamp || timestamp == prevTimeStamp && chatLine == prevChatLine)
                    break
                else
                {
                    chat.push(chatLine)
                    writelog("getNewChatLines(): timestamp=" timestamp " | line=" chatLine)
                }
            }
      
        prevChatLine :=        curChatLine
        prevTimeStamp :=    curTimeStamp
    }
    return chat
}

:?*:reload::
reload

comment = Автор скрипта - vk.com/arsh1234
+ Скриншоты логов, бд


Каждая новая строка - ответ на новый вопрос. В качестве разделителя ключевых слов и ответов служит двоеточие ":".
https://i.imgur.com/j8x3MBd.png

Логирование вопросов без лишней информации.
https://i.imgur.com/CiSK6EZ.png

Тут сложновато разобрать, в виду того, что, видимо я на другой сервер подключался со включенным скриптом, и сообщения с другого сервера подтянулись в эти логи.
https://i.imgur.com/mIih4fQ.png


Сейчас у меня ситуация следующая: хочу перенести небольшую часть фукнционала скрипта в другую игру, в майнкрафт.
А именно, я хочу перекинуть из первого кода тот функционал, который меняет регистр сообщения, а затем обращается к файлу и достает от туда ответ. Только в этом мне нужна ваша помощь. А с логированием всех вопросов игроков в отдельный документ я сделаю сам, думаю там все крайне легко будет.
Так-же хочу оставить работу скрипта только по кнопке, автоматизация тут не имеет большого смысла.
Сейчас я имею следующий код:

+ Нынешний код

#SingleInstance force ; Означает, что старый экземпляр скрипта будет замещён новым автоматически
#Persistent ; Делает скрипт постоянно выполняющимся
#Include tf.ahk ; Используется одна ф-я из библиотеки. Возможно вырежу ее от туда и вставлю внутрь своего скрипта
; Запись расположений файлов в переменные
chatlog := "D:\MultiMc\instances\1.20.1\.minecraft\logs\latest.log"
database := "C:\mine ahk\dbase.txt"

; ВРЕМЕННАЯ ФУНКЦИЯ. Ее нужно вырезать, должны подгружаться ключевые слова и ответы из доп. файла
RegExDict := {"привет.*": "привет", "как дела.*": "нормис"} 

f1:: 
fileread, read, % TF_Tail(chatlog, 2) ; Читает последнюю строку из чат-лога игры, записывает это в переменную lastline
lastline:= % TF_Tail(chatlog, 2) 
RegExMatch(lastline, ".*>>>(.*)", msg) ; С помощью регулярных выражений отсекается лишняя информация, остается только сообщение игрока
for key, value in RegExDict 
{
    if (RegExMatch(msg1, key))
    {
		MsgBox, %value% ; До реализации скрипта, временное решение. По факту готовности переделаю, чтобы вместо окна с результатом, результат сам писался в чат.
    }
}

return

NumpadAdd::
Reload

9 строка, разумеется, временно находится там, и меня это не устраивает в виду неудобства добавления большого количества ключевых слов и ответов.

2 (изменено: zerguddd, 2023-08-12 17:09:39)

Re: AHK: Скрипт для хелперов. Автоматические ответы на вопросы игроков.

Есть прогресс. Подключил бд, но теперь скрипт постоянно флудит окном. Подскажите, что я делаю не так?
Постоянно выполняется 22 строчка кода. Пока что решил проблему через перезапуск скрипта (строкой Reload после MsgBox), но это решение явно не очень.

if(RegExMatch(msg1, key))

https://i.imgur.com/9rNslgs.png/

+ открыть спойлер
#SingleInstance force ; Означает, что старый экземпляр скрипта будет замещён новым автоматически
#Persistent ; Делает скрипт постоянно выполняющимся
#Include tf.ahk ; Используется одна ф-я из библиотеки. Возможно вырежу ее от туда и вставлю внутрь своего скрипта.
; Запись расположений файлов в переменные
chatlog := "D:\MultiMc\instances\1.20.1\.minecraft\logs\latest.log"
database := "C:\mine ahk\dbase.txt"

; ВРЕМЕННАЯ ФУНКЦИЯ. Ее нужно вырезать, должны подгружаться ключевые слова и ответы из доп. файла.

f1::

fileread, read, % TF_Tail(chatlog, 2) ; Читает последнюю строку из чат-лога игры, записывает это в переменную lastline
lastline:= % TF_Tail(chatlog, 2) 
regExDict := Object()
initRegExepDict()
global regExDict
for key, value in regexdict 
{
    if RegExMatch(lastline, ".*>>>(.*)", msg) ; С помощью регулярных выражений отсекается лишняя информация, остается только сообщение игрока
    {
		for key in regExDict
                if(RegExMatch(msg1, key))
                {
                    Msgbox, % regExDict[key]
					break    
                }
    }
}
return

NumpadAdd::
Reload

initRegExepDict()
{
	global regExDict
	global database
	
	Loop, read, % database
		if(RegExMatch(A_LoopReadLine, "^\s*(.+?)\s*:\s*(.+)\s*$", msg))
        {
            msg1 := RegExReplace(msg1, "А|а", "[Аа]")
			msg1 := RegExReplace(msg1, "Б|б", "[Бб]")
			msg1 := RegExReplace(msg1, "В|в", "[Вв]")
			msg1 := RegExReplace(msg1, "Г|г", "[Гг]")
			msg1 := RegExReplace(msg1, "Д|д", "[Дд]")
			msg1 := RegExReplace(msg1, "Е|е", "[Ее]")
			msg1 := RegExReplace(msg1, "Ё|ё", "[Ёё]")
			msg1 := RegExReplace(msg1, "Ж|ж", "[Жж]")
			msg1 := RegExReplace(msg1, "З|з", "[Зз]")
			msg1 := RegExReplace(msg1, "И|и", "[Ии]")
			msg1 := RegExReplace(msg1, "Й|й", "[Йй]")
			msg1 := RegExReplace(msg1, "К|к", "[Кк]")
			msg1 := RegExReplace(msg1, "Л|л", "[Лл]")
			msg1 := RegExReplace(msg1, "М|м", "[Мм]")
			msg1 := RegExReplace(msg1, "Н|н", "[Нн]")
			msg1 := RegExReplace(msg1, "О|о", "[Оо]")
			msg1 := RegExReplace(msg1, "П|п", "[Пп]")
			msg1 := RegExReplace(msg1, "Р|р", "[Рр]")
			msg1 := RegExReplace(msg1, "С|с", "[Сс]")
			msg1 := RegExReplace(msg1, "Т|т", "[Тт]")
			msg1 := RegExReplace(msg1, "У|у", "[Уу]")
			msg1 := RegExReplace(msg1, "Ф|ф", "[Фф]")
			msg1 := RegExReplace(msg1, "Ц|ц", "[Цц]")
			msg1 := RegExReplace(msg1, "Ч|ч", "[Чч]")
			msg1 := RegExReplace(msg1, "Ш|ш", "[Шш]")
			msg1 := RegExReplace(msg1, "Щ|щ", "[Щщ]")
			msg1 := RegExReplace(msg1, "Ъ|ъ", "[Ъъ]")
			msg1 := RegExReplace(msg1, "Ы|ы", "[Ыы]")
			msg1 := RegExReplace(msg1, "Ь|ь", "[Ьь]")
			msg1 := RegExReplace(msg1, "Э|э", "[Ээ]")
			msg1 := RegExReplace(msg1, "Ю|ю", "[Юю]")
			msg1 := RegExReplace(msg1, "Я|я", "[Яя]")
			regExDict[msg1] := msg2
        }
        
    if(!regExDict.Count())
        MsgBox, 48, Ошибка инициализации, Ошибка инициализации:`nФайл %database% не заполнен по форме/не найден
}

3

Re: AHK: Скрипт для хелперов. Автоматические ответы на вопросы игроков.

Ну у вас флудит ибо вы вечно получаете одни и те же строки на последующих итерациях.
Подумайте, как бы вы могли это обойти.

Win10: LTSC (21H2); AHK: ANSI (v1.1.36.02)