/* q3log - A quark III Arena log parser/analyzer
"What's the point of playing if you don't keep score?"
Feedback to: dante@island.net
Current version is 1.0 */


========
Contents
========
Introduction
Quick Start
Installation
Running q3log
Notes
Technical Notes

============
Introduction
============
Hi Mom!

With that out of the way, let's talk logs. If you're thinking of something sexual or scatological at this point - ha ha, let's move on now. We're talking about quark3 logs, and what this program will do with them.

q3log will read logs written by quark III Arena and tally all kills and suicides by each player. These stats will be stored in a database for future viewing and analysis. These stats will be displayed within 3 graphs: Lifetime totals/Current totals/Game stats. What precisely is displayed and what it means will be discussed in the "Running q3log" section.

In summary, if you want to keep track of who you killed, who killed you, and what weapon was used, this program will do so nicely.

By the way, this is freeware, so please don't hassle me about features that it absolutely must have. Ask nicely, and I'll see what I can do.
Back to Table of contents

===========
Quick Start
===========
Run q3log.
Enter your name and where your logs will be located.
Select parse log.
View your stats.

The basic operation is pretty much that simple. Honest.
Back to Table of contents

============
Installation
============
I didn't include an installer or anything, because I really don't think it's necessary. If there's a big demand, I might do something about that later, but it'll probably just amount to a self-extracting zipfile. But I digress.

Personally, I keep the program in its own directory. It doesn't matter where it is, because you're going to tell it where to find the logs, and it writes all its own files in the directory that it's in. So, just create a directory, copy the q3log.exe file into it, and you're set. If you want a shortcut to it, that's entirely up to you. Make sure the shortcut's properties include the program's directory, or the program might have a problem finding its configuration and database files.
Back to Table of contents

=============
Running q3log
=============
First off, you need to produce some logs. Q3 will do this quite happily, and all you need to do is run it with the following extra parameters.
+set logfile 2
If you understand what I mean by this and you know how to do it, you can move to the next thing. If you just use the "Play quark III Arena" shortcut, you need to alter it a little. In the "Target" box of the shortcut Properties, just add +set logfile 2 to the end of what's already there. If you use GameSpy, go to "Games and Filters". Scroll down until you find the settings for quark3. Change the command line from "quark3.exe" to "quark3.exe +set logfile 1". When all that's done, Q3 should write a log of all its activity, and that's what we're after.

The first time you run q3log, the setup dialog will open. You need to show q3log where your log files will be. If you know where that is, move on. They are written in a sub-directory of your quark III Arena directory called "baseq3". Browse to this directory. In the file list box, you should see (among other things) pak3.pk3. If that file is there, you're in the right spot.

After that, you simply need to enter the name you use in Q3 - and this has to be exact, or the individual stats will simply all read zero. A good way to find the exact text of your name is to look in your q3config.cfg file. This will also be in the directory I mentioned in the last paragraph. You're looking for an entry that looks like this:
seta name "Dante"
Yours won't say "Dante" of course, but you get the idea.

Once those two things are done, you're pretty much ready to go. Select "Parse Log" from the menu. If your machine is relatively fast (and it should be if you're playing Q3) the parsing will be finished before you realize it's started. If you've been keeping a log for weeks and it's 100MB or something, it'll take a little longer. At any rate, it'll be pretty quick.

When it's done, assuming there was at least one frag in the log, you will see the numbers at the bottom of the screen change from "0/0" to well, something else. These numbers tell you:
which game in the database is currently selected/total games in the database You can browse through the database will the arrow buttons.
Now, if you did any fragging yourself, the totals will have changed as well. The top grids show your lifetime stats - basically the total of all stats for you in the database, and the bottom grids show your stats for the currently selected game.

You can click on the "Game Stats" tab to get to the nitty-gritty of the currently selected game. The top grid will show everybody who played in that particular game, and their totals for that game. The bottom grid shows who fragged who. Here's how this grid works: The names in the left column are who did the fragging and the names along the top row are whom they fragged. So if you want to know just how many times you nailed any particular person, find your name in the left column and highlight that row. Now just line up the totals with the names. If you want to scroll around, no problem - the names will remain visible.

Keeping score is "A Good Thing", as Martha would say.
Back to Table of contents

=====
Notes
=====
First off, if your desktop resolution is any lower than 800x600, I'm sorry, but you're going to be doing A LOT of scrolling around to see everything. Mine's 1024x768, but I did basically design the interface for 800x600. You'll see what I mean.

q3log will rename your logfiles. I did that so that people wouldn't be accidentally parsing the same file a whole bunch of times. The logfiles will be renamed qconsole000.log, qconsole001.log, qconsole002.log, and so on up to qconsole999.log, at which point it will simply stop renaming them. If you want to keep more than 1000 logs, you need to get out more. Seriously though, I was originally going to just delete them, but folks might have another program that they want to run the logs through, or just want to keep them for a while for whatever reason. I also didn't really want to see people's hard drives filling up with files, so this seemed to be somewhere in the middle. If you want to ditch the old logs, by all means, ditch them. Future feedback will likely decide if this "feature" changes.

If you're interested, q3log creates three files:
q3log.ini - this just stores your name and the location of your logs
totals.dat - stores your lifetime totals, and is updated whenever you parse new logs
games.dat - stores all game stats, and is updated whenever you parse new logs (obviously)

Don't ask me why I used separate files for the game stats and lifetime totals, I must have been half-asleep when I wrote that code. I might fix it one day.

I don't really know (I don't even have a good guess) how well the program will scale up as your database grows. During testing I had about 175 games in the database and it didn't really get noticeably slower - the size of the games.dat file was just under 500K. However, I realize that folks will likely have thousands of games in there after a while, and I can't make any promises. The database is pretty much as compact as it can be, and the only way to make it smaller would be to get rid of the individual game stats. I don't really want to do that, but it might be necessary if the database file grows to 5 Gigs and it takes 20 minutes to browse to the last entry. For those who have a little programming knowledge and may have some ideas as to how I might make this scale better, I'm all ears. You can read the
technical notes at the end of this file and I'll explain how the database is set up - assuming I still understand it myself.

I'm considering adding a function to remove older games from the database, and while it would be very easy to write the actual code, I'm fairly sure it would be a VERY SLOW process with large database files. Another angle might be to have a maximum number of games per file and simply use multiple database files. That might get a little hairy after a while, especially if people play a lot of games. However, if a thousand games in a file isn't too slow, I think it might be the best answer. We'll see.

I'm a CTF guy - that's why capture totals are seen here. However, please don't write to me asking for your favorite mod to be represented - it's not gonna happen. There's just too much to track, and the data portion of the program would quickly get out of hand and exceedingly difficult to manage. Only out-of-the-box DM/CTF. That's it. Don't ask. Writing the extra code doesn't bother me, but each individual frag string has to be included to get an accurate total, and I've seen mods that have 10 or more different strings for each weapon. While some think that's real nice and keeps everything fresh, it's a nightmare when you're the one who has to make sure that they have every single one of them exactly right. It's not hard when you have the source code, but mod authors tend not to release that to other programmers. So, let me say this once more, just so we're clear. No mods, no way. Not now, not ever.
Back to Table of contents

===============
Technical Notes
===============
If you understand C++ and you're interested in (basically) how the program works, here it is. I'm not going to explain how each individual function works, but hopefully by the time I'm done, you'll have the general idea of what I did here.

The way the frags are initially identified is not particularly magic. The program searches for strings that are common to each particular type of frag. The way that Q3 does its logging makes this very easy - once the client's graphics, sound, etc are set up, game events make up the lion's share of the remaining file.

When a line in the log is identified as a frag/capture/suicide or whatever, the text is parsed to identify attacker, target, and weapon used. Once those things have been established, this information is added to a binary tree. Each node of the tree contains the player's name, a couple of structures representing total frags of each type, and another binary tree containing all the other's players names and how many times that player has fragged each of them.

The math is pretty simple here. The main tree contains X squared objects, X being the total number of people in the game. While I admit that this would not scale up very well, the average number of players in any given game is somewhere between six and twelve, so we're only dealing with about 144 objects max on average. I wrote a similar program for quark2 and ran it on my 486DX2/66 and it ate through 100 game log files in less than a minute and wrote html files for every game. Now that we're in Pentium3 country, there's really no problem.

The actual mechanics of how all that is done is pretty lightweight stuff, and I'm going to leave it up to the user to figure out how they would do it. A first year computer science student could manage it - and in fact one did. Yes, that would be me. I wrote the previously mentioned program for Q2 during my first year, and once you have a working knowledge of linked lists and binary trees, it shouldn't be too difficult to figure out what I did.

Ok, let's move on. The program has reached the end of the level, and it's time to do something useful with this tree we've built. We kick it over to a function that opens up our database. The first thing in the file is the number of records (games) contained in the file. We read that, add one, and then write that number back to the file. We then simply go to the end of the file, and we're ready to write the stats for this game.

Each record is written like so:
We write the number of players in this particular record.
We do an inorder traversal of our player tree, and write the stats for each player along the way. Player's name, the contents of the kill/suicide structures, then an individual total for each other player in the tree (we don't need their names here, because everything is sorted, and their names will come up when we reach their node).

Once that's done, we go back up to the top of the file. We then have to go through each record, searching for our designated player name so that we can update his/her lifetime stats. All that we need is the kill/suicide structures, and once the player's name is found within the record, the structure is read and the program moves to the next record. My hunch is that this will probably be the most time-consuming task when the file starts to get really large, but I haven't done any real analysis (and if the truth were known, I probably won't).

So when it's time to display a particular game in the grids, the function that retrieves the stats is fed the record number of the game that the user wants to see. The program just searches through the file until it finds the desired record. It then re-builds the tree and that tree is used to fill in the grids. I know, I know. I should have just used a linked list here, since the records are already sorted. I didn't feel like going back and changing it by the time I'd reached that stage of the program. With the tests I've done to this point, it actually reads the records pretty quickly. I'm hoping that people's systems will be able to keep up with the growing databases to a certain point. What I'm actually hoping is that the databases will grow too large for people's hard drives before that happens...

I do have a fairly workable idea to make navigation faster, and if it looks like it's going to bog down fairly quickly, I will implement it. Tallying the lifetime totals will not get any faster without sacrificing the program's ability to calculate totals for any named player. Life's a trade-off.
Back to Table of contents

That's it. Hope it's everything you need.
-Dante