Reading file in order to react

Ask gaming related questions
kallera
Posts: 17
Joined: 24 Sep 2017, 20:35

Reading file in order to react

Post by kallera » 23 Apr 2018, 21:51

Hello folks,


I have stumbled upon the following issue:

The game sends some of it's info to a log file [filename is like 'mychar.log' ] like f.ex. the info that my char's fight is over. That's why I have decided to check this file every time a fight occcurs (info involving my char ofc) to end my automagical fight. Atm. the fight continues for ever if not stopped by the user. Now the question arises how do I stop the fight automagically, how do I check the contents of a file looking backwards from he end of the file (I'm using Windows 10). I have seen the info myself in the log file using a word processor like Notepad and it's command to reread a file? Is the process mayhaps too slow, i don't know that either.

The game adds to the end of the file (atm. the log file is quite big like ~20M) each time a message like 'You have won your fight with [mob name]' or '[mob name] has won the fight. You die'. The same message is btw. displayed also on the screen with floating letters. It does further appear in my chat box tab: system info.

I have not worked with files either on Win10 or with ahk files-commands, so the amount of questions is quite large: how to read from the end of a file with ahk and Win 10, how uch to read, how to compare the file info with some text of my choice, do I have to reserve some memory in the process, would it be wiser and possible to solve the problem in some other way (I don't know where in memory that info is) and so forth and so forth.

Any help (and examples) would be greatly appreciated. Thank you all guys.


--=kallera=--
User avatar
evilC
Posts: 4341
Joined: 27 Feb 2014, 12:30

Re: Reading file in order to react

Post by evilC » 27 Apr 2018, 10:43

You need to "Tail" the log.

Luckily for you, I have a Log Tailer class that you can use:

Code: Select all

class CLogTailer {
	CurrentLine := 0
	LastSize := 0
	
	__New(logfile, callback){
		this.callback := callback
		this.logfile := logfile
		this.TimerFn := this.WatchLog.Bind(this)
		this.InitialParse()
		fn := this.TimerFn
		SetTimer, % fn, 250
	}

	; At the start, scan from the END of the log back towards the start...
	; ... and find the callback for each.
	; If the callback returns TRUE, that line matched a regex, so stop...
	; And set this.CurrentLine to that line number, so WatchLog can pick up from that point
	InitialParse(){
		if (FileExist(this.LogFile)){
			numlines := this.CountLines()
			Loop % numlines {
				ln := numlines + 1 - A_Index
				FileReadLine, line, % this.LogFile, % ln
				ret := this.Callback.call(Trim(line))
				if (ret){
					;OutputDebug % "AHK| Found last entry at line " ln " max=" numlines
					FileGetSize, sz , % this.LogFile
					this.LastSize := sz
					this.CurrentLine := numlines
					return
				}
			}
		}
	}
	
	; After the Initial Parse, all subsequent changes in log file size trigger this method.
	; Read all lines that were added since the last tick, and fire the callback for each
	WatchLog(){
		if (FileExist(this.LogFile)){
			FileGetSize, sz , % this.LogFile
			if (sz == this.LastSize){
				return
			}
			if (sz < this.LastSize){
				; Log got smaller - roll over? Reset CurrentLine to 0 and re-parse whole log
				;OutputDebug % "AHK| Log Rollover detected"
				this.CurrentLine := 0
			}
			this.LastSize := sz
			Loop, read, % this.LogFile
			{
				if (A_Index <= this.CurrentLine)
					continue
				;OutputDebug % "AHK| Processing line " A_Index
				this.CurrentLine := A_Index
				if (A_LoopReadLine == "")
					continue
				;OutputDebug % "AHK| Firing callback for line " A_Index
				this.Callback.call(Trim(A_LoopReadLine))
			}
		}
	}
	
	CountLines(){
		c := 0
		Loop, read, % this.LogFile 
		{
			c++
		}
		return c
	}
}
Usage:

Code: Select all

LogTailer := new CLogTailer("C:\myfile.txt", Func("LogChanged"))
return

LogChanged(line){
	; this function is called each time a new line is added to the log.
}
kallera
Posts: 17
Joined: 24 Sep 2017, 20:35

Re: Reading file in order to react

Post by kallera » 27 Apr 2018, 23:25

You are a truly Evil man, EvilC. Thanks for your helpful reply.

Do I only have to add all that code that you have written somewhere in my main file? What if (one of) the text(s) I'm expecting, let's say the text 'Belzebub', is found? Or the text 'You die''? I would like to react on those messages by say like stopping my attack loop (means releasing my NumLock key)? Or do I have to add (all) the text(s) I'm looking for (like 'Belzebub', 'You die' etc to the LogChanged(line) function?

Please clarify a bit. I'm only a newbie at coding anyway, so what may be selfevident to you is not at all easily understood to me.

Thank you very much for sharing your awesome work anyway.

--=kallera=--
User avatar
evilC
Posts: 4341
Joined: 27 Feb 2014, 12:30

Re: Reading file in order to react

Post by evilC » 28 Apr 2018, 13:10

Best to save the first snippet (The CLogTailer class) as CLogTailer.ahk and then use #include CLogTailer.ahk at the start of your script.
Looking at that class, it's not the one I thought it was. It should work, but it's not very efficient. I will see if I can find the better code that I should have somewhere.
Syntax using it would be the same tho, so you can just start writing code using CLogTailer and we can drop in a better version later if I can find the code.
kallera
Posts: 17
Joined: 24 Sep 2017, 20:35

Re: Reading file in order to react

Post by kallera » 30 Apr 2018, 09:56

Awesome, thank you, will do that as soon as possible, perhaps already tomorrow. At the same time I will (atm only planning to) backup the log file and take a backup of it. The new logfile will of course be considerably shorter.

--=kallera=--
User avatar
evilC
Posts: 4341
Joined: 27 Feb 2014, 12:30

Re: Reading file in order to react

Post by evilC » 30 Apr 2018, 11:46

I had a look around, but I could not find the more efficient log tailer class that I thought I wrote.
So I sat down and wrote it :P

Code: Select all

#SingleInstance force
#Persistent

lt := new CLogTailer("myfile.txt", Func("NewLine"))
return

NewLine(text){
	ToolTip % "New line added @ " A_TickCount ": " text
}

class CLogTailer {
	__New(logfile, callback){
		this.file := FileOpen(logfile, "r-d")
		this.callback := callback
		; Move seek to end of file
		this.file.Seek(0, 2)
		fn := this.WatchLog.Bind(this)
		SetTimer, % fn, 100
	}
	
	WatchLog(){
		Loop {
			p := this.file.Tell()
			l := this.file.Length
			line := this.file.ReadLine(), "`r`n"
			len := StrLen(line)
			if (len){
				RegExMatch(line, "[\r\n]+", matches)
				if (line == matches)
					continue
				this.callback.Call(Trim(line, "`r`n"))
			}
		} until (p == l)
	}
}
kallera
Posts: 17
Joined: 24 Sep 2017, 20:35

Re: Reading file in order to react

Post by kallera » 02 May 2018, 04:36

Thank you once again for your kind efforts, EvilC.

It seems like you have in some way that remains a mystery to me, have added a class (I believe) with c / c++ code? Still the code uses AHK-code ' := ' and, afaik c-code '==' ? How come both?

I must however ask once more for help with the usage. Where do i put in the text I am seaching for (like the text 'belzebub' or the text 'You die. Your death is very painful.' or anything)?

Where do I put in the commands I wish that my code should do in case a match is found (like Send {Lcontrol down} SendInput,{Numpad7} Send {Lcontrol up}) etc etc?

Where do I put in the filename to my log file: '(full path?) mychar_name.log'?

I'm sorry to be so ignorant, but that is because I am, I can't help it either since I was born this way and slept thru school:)


Rgds
--=kallera=--
User avatar
evilC
Posts: 4341
Joined: 27 Feb 2014, 12:30

Re: Reading file in order to react

Post by evilC » 02 May 2018, 07:47

Nope, it's pure AHK. In AHK, == is a valid comparison operator - = in AHK is sometimes comparison, sometimes assignment, so I avoid using it like the plague.
The only valid use of = for comparison in AHK is to do a case-insensitive string comparison. In all other cases, == is preferable as it is unambiguous.

As each new line is added to the log, the function NewLine() is called, and the new line of text will be in the text variable.
Inspect the contents of the text variable and take action as appropriate. For example:

Code: Select all

NewLine(text){
	if (text == "You die. Your death is very painful."){
		msgbox YOU DIED
	}
}
kallera
Posts: 17
Joined: 24 Sep 2017, 20:35

Re: Reading file in order to react

Post by kallera » 04 Aug 2018, 12:44

@EvilC:

To our discussion about reaction to new input to my logfile (btw. the logfile is ”D:\Main\RyzomII\Ryzom\ryzom_live\save\saija_log.txt”). I have tried to do (in different variations) do as you said however without success.

I have added to my main file this sequence as a separate file (called clogtailer.ahk) as you said:

Code: Select all

;from Evilc
#SingleInstance force
#Persistent

lt := new CLogTailer("myfile.txt", Func("NewLine"))
return

NewLine(text){
	ToolTip % "New line added @ " A_TickCount ": " text
}

class CLogTailer {
	__New(logfile, callback){
		this.file := FileOpen(logfile, "r-d")
		this.callback := callback
		; Move seek to end of file
		this.file.Seek(0, 2)
		fn := this.WatchLog.Bind(this)
		SetTimer, % fn, 100
	}
	
	WatchLog(){
		Loop {
			p := this.file.Tell()
			l := this.file.Length
			line := this.file.ReadLine(), "`r`n"
			len := StrLen(line)
			if (len){
				RegExMatch(line, "[\r\n]+", matches)
				if (line == matches)
					continue
				this.callback.Call(Trim(line, "`r`n"))
			}
		} until (p == l)
	}
}
I have also added these lines (separately, not reachable by the rest of the code) to my main file:

Code: Select all

; from EvilC
LogTailer := new CLogTailer("C:\myfile.txt", Func("LogChanged"))
return
LogChanged(line){
	; this function is called each time a new line is added to the log.
}

}
return
This is correct, right? Or shall this (last) code be separately called?

Now I still need to know where I can put a line containing the path to the logfile (”D:\Main\RyzomII\Ryzom\ryzom_live\save\saija_log.txt”) in the code. Further, where can I put the compare code, f.ex

(IF (text == belzebub) ...various code),
(IF (text == you die) ...other commands ) etc.

I’m sorry insisting but I really need to know. I would not return to the matter after several months if I wouldn’t. Please help me.

Rgds.
--=kallera=--
Post Reply

Return to “Gaming”