Тема: 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
Каждая новая строка - ответ на новый вопрос. В качестве разделителя ключевых слов и ответов служит двоеточие ":".
Логирование вопросов без лишней информации.
Тут сложновато разобрать, в виду того, что, видимо я на другой сервер подключался со включенным скриптом, и сообщения с другого сервера подтянулись в эти логи.
Сейчас у меня ситуация следующая: хочу перенести небольшую часть фукнционала скрипта в другую игру, в майнкрафт.
А именно, я хочу перекинуть из первого кода тот функционал, который меняет регистр сообщения, а затем обращается к файлу и достает от туда ответ. Только в этом мне нужна ваша помощь. А с логированием всех вопросов игроков в отдельный документ я сделаю сам, думаю там все крайне легко будет.
Так-же хочу оставить работу скрипта только по кнопке, автоматизация тут не имеет большого смысла.
Сейчас я имею следующий код:
#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 строка, разумеется, временно находится там, и меня это не устраивает в виду неудобства добавления большого количества ключевых слов и ответов.