Hide
Free your code from a slow death on your hard drive Join Siafoo Now or Learn More

LOTRO Log Analyzer Adventures Subscribe

This is a blog that I will be maintaining to record my adventure writing a combat log analyzer (mainly to calculate damage-per-second and related figures) for The Lord of the Rings Online. The analyzer is being developed in Python 2.6.2 for Windows and makes use of the Tkinter library.

Sessions and Subsessions

about 1 year ago

With this post I'm going to figure out how the session object works - what its methods are and how to create and manipulate it properly.

What is the session object? As per a previous entry, it is an object that has an array of subsession objects which have arrays of the data from the lines in the log.

The Session itself is the root object. There are not multiple sessions, but there are multiple subsessions within the containing array.

Session.subsessions[0] contains a subsession Session.subsessions[1] contains a subsession and so on...

Session.subsession[n].line[n] to access lines.

Should a line be an object? I'm not sure what the overhead of objects is. But, there are objects for simple ints and strings, so I guess it can be an object.

Quick outline of how the classes are...

# 's
 1class Session:
2 def __init__(self):
3 self.cursub = IntVar() # currently active subsession
4 self.subsessions = Array() # array of subsession objects
5
6class Subsession:
7 def __init__(self):
8 self.curline = IntVar() # current line
9 self.startpos = IntVar() # where this subsession starts
10 self.endpos = IntVar() # where this subsession ends
11 self.lines = Array() # the array of line objects
12
13class LogLine:
14 def __init__(self):
15 self.linenum = IntVar() # the index of this line - may not be needed
16 self.stamp = DoubleVar() # epoch timestamp of this line
17 self.actor = StringVar() # the person doing the action, whether an
18 # attack or heal
19 self.target = StringVar() # the target of that action
20 self.value = IntVar() # how much damage or healing was done
21 self.crit = BooleanVar() # whether it was a crit or not
22 self.dot = BooleanVar() # whether it was a damage over time or not
23 self.damtype = StringVar() # the damage type if applicable
24 self.skill = StringVar() # the skill used if applicable

When initializing the GUI, the Session is created, and then as fights start and end, subsessions are automatically created and added to the Session.subsessions array. Each time a line is read in, its data is entered into the Subsession.subsessions.lines[n] object. That line can then be accessed at any time to use its values in doing calculations on the fly.

The main thing to do right now is make the regexps that will pull the needed values out of the raw log line, and then figure out the best way to first process and THEN display the data. Originally the strings that came out of readlines() were pushed directly into the logbox text area. I guess I'll need to have three different .after methods - one to get the lines from the file and plug the data into logline, one to change the variables in the analysis panel based on info in the lines, and one to actually display modified log lines in the output box.

I am wondering whether this is better solved with three .afters or separate threads altogether. These .afters are too 'isolated' to really be part of the GUI's instructions - they can run independently of it as long as they know the filehandle and the analysis panel variables.

Progress

about 1 year ago

I am proud to present the appearance of my application as it is now... the numbers in the analysis panel don't work because the code isn't yet there to tie them together with what's displayed in the log, but I want to share the appearance of the program because this is my first foray into GUI work and I'm rather proud of this. :) Scaled down to fit nicely within the blog window, click for a clearer, bigger version.

http://www.siafoo.net/image/167?w=600

Haven't worked on the parser for the past few days - been puttering around on a cool freeMO called Runes of Magic. It's very WoW-y. I played WoW for a few months, but there were some major show-stoppers for me - however, I missed some of the 'look and feel' of the game once I stopped playing. RoM is a very cool replacement.

Anyway, on to the actual coding thoughts:

Filetype

My original goal with the parser in regards to its file format was to make no further changes to a raw logfile than to simply timestamp it. After a few comments from Stou on a previous entry, I've decided to change that goal. There's really no reason to force myself to use a nearly completely raw file - the only reason I once had was to make it so that logs that were stamped before the actual calculation part of the project was finished could be read in properly. There's an easy way to fix that - just add some extra functionality to the stamper part that simply converts each line into a uniform format - a table of sorts, with each element separated by some kind of token. I'm thinking two spaces will do the trick, because I know the chat logger forces all instances of multiple spaces to be merged into a single space, so there should never be a natural occurrence of two spaces within the file.

As for what to call the extension, I'm not sure. Some basic ideas: .lla (LOTRO Log Analyzer), .llr (LOTRO Log Replay), .lpf (Log Parser Format), .plf (Prepared Log File) - the list could go on, but it's not of urgent importance just yet. I think I may go with .lla for simplicity's sake.

GUI Grid

I was intimidated at how difficult it may be to replicate the previous entry's grid into the program, but with the .grid method in Tkinter widgets, it's pleasantly easy so far. I just have to work out some minor details like text alignment and the program's main GUI should be good to go! I must express thanks to Stou for nudging me to keep working on the GUI even when I was too intimidated to try to continue at the time. :)

Things it Needs to Track

about 1 year ago

Quick list of everything the parser needs to track in the analysis panel...

LOG TOTAL

  • Outgoing Damage
  • Incoming Damage
  • Outgoing Healing
  • Incoming Healing

PER SUBSESSION

  • Outgoing Damage
  • Incoming Damage
  • Outgoing Healing
  • Incoming Healing
  • Average outgoing DPS
  • Average incoming DPS
  • Average outgoing HPS
  • Average incoming HPS
  • Current DPS
  • Current HPS
  • Outgoing DPS in past X seconds (X is user-defined)
  • Incoming DPS in past X seconds
  • Outgoing HPS in past X seconds
  • Incoming HPS in past X seconds

Most compact way to display all this information would be in a grid of sorts. Quick outline of what it would look like:

SUBSESSION OUTGOING
TYPE CURRENT XS AVG SS TOTAL
DPS        
HPS        
SUBSESSION INCOMING
TYPE CURRENT XS AVG SS TOTAL
DPS        
HPS        
GRAND TOTALS
TYPE OUTGOING INCOMING
DMG    
HEALS    

Ramblings

about 1 year ago

I'm a little bit stuck and having trouble getting through what I'm trying to wrap my mind around, so it's time to do what my friends like to call 'Codespew'... the following passages may not make any sense whatsoever, but the simple act of making the attempt to somehow define them helps me to figure out a problem.

It's actually hard to define where precisely this new problem is. I have the core of the GUI set up - there's still the analysis panel to create, but that'll just be a bunch of boxes with variables in them. Shouldn't be too much trouble to make. I haven't made it yet because I don't have the variables to put into it yet - and to have the variables, I need calculations to actually be done. Therein lies my problem - I'm not sure where to start with actually doing the calculations, especially considering my 'replay' feature.

The replay feature will be essential to the program, so I should probably figure out how precisely that is going to work before I do anything else. Okay, here's the premise.

The program first needs a way to distinguish between opening a file for stamping and opening a file for replaying. Stamping is the act of placing timestamps on the lines fed in from an original log file as it is being written by the LOTRO program. Replaying is the act of loading a previously stamped file and 'playing' the file's contents based on the timestamps, so it looks like the fight is happening in real time.

When stamping, the original logfile is constantly being read from and when the program catches a line that isn't empty, it processes it.

When replaying, the entire file is loaded into memory, processed into arrays/objects, and the program decides when to display each line based on the difference in timestamps between the 'next' line and the current line.

So how to differentiate? Do we do it based only on the presence of timestamps? The idea is flimsy at best. I think that the best way to do this would be to have two different 'load' menu options - 'stamp logfile' and 'replay logfile.' Each option should be able to call an event loop in the application, unless I'm terribly mistaken, so that we don't always have two loops running, and there isn't even one loop running if a file simply isn't loaded.

So, even when we've let the user specify what type of file we're messing with, how does the program know what to do with the contents of the file? This will require liberal use of regexps and arrays. I will need to define a number of different regexps (or perhaps One Regexp To Rule Them All, if I can manage it) that will be able to pull apart each line and stuff the important parts of it into an array for each line.

But what to do with those arrays? Where to put them? Each section of the log that spans a single stream of uninterrupted combat will be considered a subsession. A subsession is an object with a multidimensional array of data pulled from lines from the log file. Where do we put the subsession? It can go into an array inside a 'root' Session object.

So a Session object has an array which lists the subsessions which list the data from the lines.

Once I create this structure and am able to fill it with data, I will be able to proceed with the other things I mentioned earlier.