The battle logs|Die Kampflogs
I stated sometimes that the there are logs of all battles that have been fought and that the players could analyse them themselves if they know how to use javascript.
So what this blogpost is about is: Explaining the battle logs so that you can (maybe) build your own statistics tool. If you are not a programming nerd, you probably can skip this post as boring and useless for your gameplay, but if you are one, you’ll most likely find this interesting unless you haven’t figured this stuff already out yourself. So here we go.
Ich habe irgendwann mal geäußert, dass es für alle ausgetragenen Kämpfe Logs gibt und dass die Spieler sie analysieren könnten, wenn sie sich mit Javascript auskennen.
Dieses Posting beschäftigt sich also damit, die Kampflogs zu erklären, so dass ihr (vielleicht) euer eigenes Statistik-Tool basteln könnt. Wenn du kein Programmier-Nerd bist, kannst du dieses Posting getrost als langweilig und uninteressant für das Gameplay überspringen, aber falls du einer sein solltest, wirst du es vermutlich interessant finden, falls ihr das nicht schon selbst herausgefunden habt. Allerdings werde ich den folgenden Kram nicht weiter übersetzen, da es eigentlich nur Leute interessieren dürfte, die sich mit Programmiersprachen beschäftigen und aus diesem Grund vermutlich eh Englisch beherrschen.
Zur Englischen Sprache wechseln.
The data format
If a battle is opened on the cemetary (you know, on the forts), all the required data is transferred. It contains really everything. For example, this is a battle of only 2 players being involved:
{
"attackerlist":[
{"westid":60,"name":"foo124663470921","flagholdcount":1,"hitcount":2,
"totalcauseddamage":131,"takendamage":260,"takenhits":4,"killedby":1,
"finishedhp":0,"starthp":260,"maxhp":260,"townid":3,"townname":"cccc",
"weaponid":100,"weaponname":"Stein","weaponmindmg":50,"weaponmaxdmg":110,
"posidx":356,"diedwhen":0,"targetidx":356}
],
"defenderlist":[
{"westid":1,"name":"testxx","flagholdcount":0,"hitcount":4,
"totalcauseddamage":260,"takendamage":131,"takenhits":2,"killedby":-1,
"finishedhp":1389,"starthp":1520,"maxhp":1520,"townid":5,
"townname":"attackers of doommmm","weaponid":100,"weaponname":"Stein",
"weaponmindmg":50,"weaponmaxdmg":110,"posidx":396,"diedwhen":8,"targetidx":396}
],
"log":[0,2, 1,1, 2,396, 3,1520, 4,1, 1,60, 2,356, 3,260, 4,0, 8,594, 0,3, 1,1, 2,396, 3,1520,
4,1, 5,60, 7,62, 1,60, 2,356, 3,198, 4,0, 5,1, 7,67, 8,560, 0,4, 1,1, 2,396, 3,1453, 4,1,
5,60, 7,62, 1,60, 2,356, 3,136, 4,0, 5,1, 8,492, 0,5, 1,1, 2,396, 3,1453, 4,1, 5,60, 1,60,
2,356, 3,136, 4,0, 5,1, 8,424, 0,6, 1,1, 2,396, 3,1453, 4,1, 5,60, 1,60, 2,356, 3,136, 4,0,
5,1, 7,64, 8,390, 0,7, 1,1, 2,396, 3,1389, 4,1, 5,60, 7,85, 1,60, 2,356, 3,51, 4,0, 5,1,
8,356, 0,8, 1,1, 2,396, 3,1389, 4,1, 5,60, 6,51],
"map":{
"tiles":[[3,5,2,1,11,9],[8,4,1,2,18,7],[6,6,3,1,11,15],[0,11,3,4,11,11],[3,11,3,4,21,6],
[6,11,3,4,21,11],[20,0,4,3,10,6],[128,0,2,2,16,9],[0,0,4,4,6,2],[8,0,5,4,24,2],[8,6,5,5,24,14],
[0,6,4,5,6,14],[1,4,2,2,7,6],[1,4,2,2,7,8],[1,4,2,2,7,10],[1,4,2,2,7,12],[1,4,2,2,26,6],
[1,4,2,2,26,8],[1,4,2,2,26,10],[1,4,2,2,26,12],[4,1,4,2,10,3],[4,1,4,2,14,3],[4,1,4,2,18,3],
[4,1,2,2,22,3],[4,1,4,2,10,16],[4,1,2,2,14,16],[4,1,2,2,18,16],[4,1,4,2,20,16],[5,8,2,2,16,16]],
"cells":[0,0,0,0,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,4,4,4,4,0,0,0,0,1,1,1,1,1,1,1,
1,1,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,4,4,4,4,0,0,0,0,1,1,5,5,5,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3,3,
6,6,6,3,3,4,4,4,4,0,0,0,0,1,1,5,5,5,7,7,7,7,7,8,8,8,8,8,8,9,9,9,9,9,6,6,6,3,3,4,4,4,4,0,0,0,0,1,
1,5,5,5,10,10,11,11,11,11,12,12,12,12,13,13,13,13,14,14,6,6,6,3,3,4,4,4,4,0,0,0,0,15,15,15,16,10,
10,10,11,11,11,11,12,12,12,12,13,13,13,13,14,14,14,17,18,18,18,4,4,4,4,0,0,0,0,15,15,15,16,10,10,
19,19,19,11,11,20,20,20,20,13,13,21,21,14,14,14,17,18,18,18,4,4,4,4,0,0,0,0,15,15,15,16,22,22,19,
19,19,23,23,20,20,20,20,24,24,21,21,25,25,25,17,18,18,18,4,4,4,4,26,26,26,26,15,15,15,16,22,22,22,
27,27,23,23,28,28,29,29,24,24,21,21,25,25,25,17,18,18,18,30,30,30,30,26,26,26,26,15,15,15,16,22,
22,22,27,27,23,23,28,31,31,29,24,24,32,32,25,25,25,17,18,18,18,30,30,30,30,26,26,26,26,33,33,33,
34,35,35,35,27,27,36,36,37,31,31,38,39,39,32,32,40,40,40,41,42,42,42,30,30,30,30,26,26,26,26,33,
33,33,34,35,35,35,43,43,36,36,37,37,38,38,39,39,44,44,40,40,40,41,42,42,42,30,30,30,30,26,26,26,
26,33,33,33,34,35,35,35,43,43,36,36,45,45,45,45,39,39,44,44,40,40,40,41,42,42,42,30,30,30,30,26,
26,26,26,33,33,33,34,46,46,46,43,43,47,47,45,45,45,45,48,48,44,44,49,49,49,41,42,42,42,30,30,30,
30,26,26,26,26,33,33,33,34,46,46,46,47,47,47,47,50,50,50,50,48,48,48,48,49,49,49,41,42,42,42,30,
30,30,30,26,26,26,26,51,51,52,52,52,46,46,47,47,47,47,50,50,50,50,48,48,48,48,49,49,53,53,53,54,
54,30,30,30,30,26,26,26,26,51,51,52,52,52,55,55,55,55,55,55,55,56,56,57,57,57,57,57,57,57,53,53,
53,54,54,30,30,30,30,58,58,58,58,51,51,52,52,52,51,51,59,59,59,59,59,59,60,60,60,60,60,60,54,54,
53,53,53,54,54,61,61,61,61,58,58,58,58,51,51,51,51,51,51,51,59,59,59,59,59,59,60,60,60,60,60,60,
54,54,54,54,54,54,54,61,61,61,61,58,58,58,58,51,51,51,51,51,51,51,59,59,59,59,59,59,60,60,60,60,
60,60,54,54,54,54,54,54,54,61,61,61,61,58,58,58,58,62,62,62,62,62,62,62,62,62,63,63,63,63,63,63,
63,63,64,64,64,64,64,64,64,64,64,61,61,61,61,58,58,58,58,62,62,62,62,62,62,62,62,62,63,63,63,63,
63,63,63,63,64,64,64,64,64,64,64,64,64,61,61,61,61,58,58,58,58,62,62,62,62,62,62,62,62,62,63,63,
63,63,63,63,63,63,64,64,64,64,64,64,64,64,64,61,61,61,61,58,58,58,58,62,62,62,62,62,62,62,62,62,
63,63,63,63,63,63,63,63,64,64,64,64,64,64,64,64,64,61,61,61,61],
"height":24,
"sectors":[{"height":0,"attackerSpawn":true},
{"height":0},{"height":0},{"height":0},{"height":0,"attackerSpawn":true},
{"height":6,"attackerBonus":6,"defenderSpawn":true,"defenderBonus":7},
{"height":6,"attackerBonus":6,"defenderSpawn":true,"defenderBonus":7},
{"height":4,"attackerBonus":3,"defenderSpawn":true,"defenderBonus":4},
{"height":4,"defenderSpawn":true},{"height":4,"attackerBonus":3,"defenderSpawn":true,"defenderBonus":4},
{"height":0,"defenderSpawn":true},{"height":0,"defenderSpawn":true},{"height":0,"defenderSpawn":true},
{"height":0,"defenderSpawn":true},{"height":0,"defenderSpawn":true},{"height":0},
{"height":4,"attackerBonus":3,"defenderSpawn":true,"defenderBonus":4},
{"height":4,"attackerBonus":3,"defenderSpawn":true,"defenderBonus":4},
{"height":0},{"height":3,"attackerBonus":1.25,"defenderSpawn":true,"defenderBonus":1.25},
{"height":0,"defenderSpawn":true},{"height":3,"attackerBonus":1.25,"defenderSpawn":true,"defenderBonus":1.25},
{"height":0,"defenderSpawn":true},{"height":0,"defenderSpawn":true},{"height":0,"defenderSpawn":true},
{"height":0,"defenderSpawn":true},{"height":0,"attackerSpawn":true},{"height":0,"defenderSpawn":true},
{"height":0,"attackerBonus":-5,"defenderSpawn":true,"defenderBonus":-5},
{"height":0,"attackerBonus":-5,"defenderSpawn":true,"defenderBonus":-5},
{"height":0,"attackerSpawn":true},{"flag":true,"height":0,"attackerBonus":-10,"defenderSpawn":true,"defenderBonus":-10},
{"height":0,"defenderSpawn":true},{"height":0},{"height":4,"defenderSpawn":true},{"height":0,"defenderSpawn":true},
{"height":0,"defenderSpawn":true},{"height":0,"attackerBonus":-5,"defenderSpawn":true,"defenderBonus":-5},
{"height":0,"attackerBonus":-5,"defenderSpawn":true,"defenderBonus":-5},{"height":0,"defenderSpawn":true},
{"height":0,"defenderSpawn":true},{"height":4,"defenderSpawn":true},{"height":0},
{"height":3,"attackerBonus":1.25,"defenderSpawn":true,"defenderBonus":1.25},
{"height":3,"attackerBonus":1.25,"defenderSpawn":true,"defenderBonus":1.25},
{"height":0,"defenderSpawn":true},{"height":0,"defenderSpawn":true},
{"height":0,"defenderSpawn":true},{"height":0,"defenderSpawn":true},
{"height":0,"defenderSpawn":true},{"height":0,"defenderSpawn":true},
{"height":0},{"height":6,"attackerBonus":6,"defenderSpawn":true,"defenderBonus":7},
{"height":6,"attackerBonus":6,"defenderSpawn":true,"defenderBonus":7},{"height":0},
{"height":4,"attackerBonus":3,"defenderSpawn":true,"defenderBonus":4},
{"height":0,"attackerBonus":3,"defenderSpawn":true,"defenderBonus":4},
{"height":4,"attackerBonus":3,"defenderSpawn":true,"defenderBonus":4},
{"height":0,"attackerSpawn":true},{"height":0},{"height":0},{"height":0,"attackerSpawn":true},
{"height":0,"attackerSpawn":true},{"height":0,"attackerSpawn":true},{"height":0,"attackerSpawn":true}],
"mapname":"Test",
"width":34},
"logtypes":["ROUNDSTART","CHARTURN","CHARTARGET","CHARHEALTH","CHARONLINE","SHOOTAT","KILLED","HIT","MOVED"],
"roundsplayed":8,"maxrounds":50,"battleid":81,"outcome":"ATTACKER_WIPED","fortname":"abcfort",
"declarerid":6,"declarername":"foo12464580102","attackertownid":3,"defendertownid":5,"defendertownname":"attackers of doommmm",
"attackertownname":"cccc"}
That’s quite a chunk, so let’s rip it apart and look at it one by one.
Attacker- and Defenderlist
Both arrays contain identical descriptions of all the involved players. Each player has following descriptions:
- westid: the id of the player in this world. Identical to the id used in ranking etc.
- name: the name of the player
- flagholdcount: number of rounds that the player was holding the flag
- hitcount: number of hits the player made
- totalcauseddamage: the total amount of damage caused by this player
- takendamage: how much damage the player got
- takenhits: how often the player was hit
- killedby: a player id (westid) of the player who knocked the player out
- finishedhp: how much hitpoints that have been left at the end of the battle
- starthp: hitpoints on start of the battle
- maxhp: the maximum amount of hitpoints the player may have
- townid: the id of the town the player belonged to
- townname: the name of that town at the moment when the battle started
- weaponid: the id of the used weapon
- weaponmaxdmg: the maximum damage the weapon can cause
- weaponmindmg: the minimum damage that the weapon is causing
- posidx: The starting position of the player
- diedwhen: The round the player was knocked out (bugged, fixed with 1.23)
- targetidx: The target of the player when the game started
A lot of data is already supplied in these arrays, but there’s far more.
The log
Each battleresult contains a complete log of the whole battle. This is most likely the most interesting thing for you, as this contains a lot of data (as a side note, this data might be bugged as it has never been used yet anywhere). A part of the log looks like this:
"log":[0,2, 1,1, 2,396,(...)
I inserted whitespaces at each second comma, because each pair of numbers describes what happened. The first number tells us what operation this was, the second number describes the operation. The mere numbers are useless unless we know the codes for the numbers. The codes are supplied with the log. The values are represented in an array called “logtypes”:
"logtypes":["ROUNDSTART","CHARTURN","CHARTARGET","CHARHEALTH","CHARONLINE","SHOOTAT","KILLED","HIT","MOVED"]
The array tells us that 0 means “ROUNDSTART”, 1 means “CHARTURN” and so on. The order can change anytime when we change some implementations on our sides, so in case of developing a tool in javascript, I would suggest to figure out the right numbers before analyzing the log.
This codebook tells us now following information:
“logtypes”:
"log":[ROUNDSTART,2, CHARTURN,1, CHARTARGET,396, CHARHEALTH,1520, CHARONLINE,1,
CHARTURN,60, CHARTARGET,356, CHARHEALTH,260, CHARONLINE,0, MOVED,594,
ROUNDSTART,3, ...
So this is more meaningful now and with a bit of description, this is the log, encoded in these number pairs:
- ROUNDSTART,2: The game begins with round 2 (1 is the first round where everyone is choosing their starting position and target position.
- CHARTURN,1: The character with west id 1 is now active
- CHARTARGET,396: The character 1 is trying to go to the cell 396 (which is his starting position, so we’re there already)
- CHARHEALTH,1520: The character 1 has a health of 1520 hitpoints
- CHARONLINE,1: The player for character 1 was online during this round
- CHARTURN,60: It is now the turn of player with id 60
- CHARTARGET,356: The character 60 wants to go to cell 356
- CHARHEALTH,260: The character 60 has 260 hitpoints
- CHARONLINE,0: The player of character 60 was not online
- MOVED,594: Character 60 moves to cell 594
- ROUNDSTART,3: Round 2 has ended, round 3 begins
This pattern repeats all over the place and we can reconstruct the whole battle for each player. We do even know who was online and who was not online! The keys “SHOOTAT”,”KILLED” and “HIT” describe further actions:
- SHOOTAT: the id of the player that this character is shooting at
- KILLED: if the shot was knocking the player out, this key is used. The value represents the damage that was caused the player to be knocked out. Can be larger than the player’s hitpoints
- HIT: if the shot was a hit, the damage is described with the value
If no hit was made, the SHOOTAT value exists but without a following HIT / KILLED actions.
The map description
The map description is also supplied here. It could be used for replaying the whole map visually. The first thing to know is how a x,y coordinate is represented in the map – since we know the width and height of the map, we can recalculate a pair of numbers to a single number and back with this small function:
// javascript code
function toxy(idx,mapwidth) {
return {x:idx%mapwidth,y:Math.floor(idx/mapwidth)};
}
function toidx(x,y,mapwidth) {
return x+y*mapwidth;
}
Both functions can be used to do the required calculations. The width and height of the map is supplied by the map description as well with the keys “width” and “height”.
Furthermore, we have various arrays now: “tiles”, “cells” and “sectors”. The “mapname” parameter is currently not really used, it still has the name “test” on the productive worlds, something that I didn’t know about yet – as it’s nowhere used right now.
The tiles
The tiles are simple arrays of arrays containing each 6 numbers, describing the visual layout with out tileobject:
- The x coordinate on the tile texture
- The y coordinate on the tile texture
- The x size of the tile
- The y size of the tile
- The x coordinate on the map where the tile is being drawn (left side of the tile)
- The y coordinate on the map where the tile is being drawn (top side of the tile)
- height: The height of this sector
- attackerbonus / defenderbonus: What bonus the sector provides
- defenderspawn / attackerspawn: A boolean value that tells us if players can spawn here
- flag: True if this is a flag sector
This information allows us to draw the hole map using the tile texture. It contains only eyecandy information – nothing else.
The sectors
That array describes the attributes of each sector that is present on the map. Following attributes are available:
There will be further values if there’s a bonus for a certain playerclass, but I think that the whole sector description is pretty self explanatory.
The cells
The most important description for the game is the layout of the sectors. The sectors can have any kind of form, so what we do is, we define for each cell on the map what sector it belongs – it refers here to an index in the sector array. The array called “cells” describes now for each cell, starting from top left the sector for each cell, left to right, top to bottom (just like reading letters in a book). For example:
"cells":[0,0,0,0,1,1,1,1,1,1,1,1,1,2 ...
What we see here is, that the cell located at x=0 and y=0 is described with the attributes given by the sector description at index 0 of the sector array. The same attributes are used for x=1,y=0 and x=2,y=0 and x=3,y=0. The next sector starts at x=4,y=0. Every cell that refers to the same sector id belongs to the same sector (of course…). From the game logic we know that if a sector is taken by a team, the other team can’t enter that sector. So in theory, there could be sectors that are disjunct – which means that a sector could be split. However, this should not be the case.
That’s it!
So that’s everything that is needed to know if you really want to analyze the logs of a battle yourself. How can you get now the data? Well, there are several ways – for example taking the text from the ajax call using firebug. Or by overwriting the FortBattle.makeStats function:
javascript:(function() {
var orig = FortBattle.makeStats;
FortBattle.makeStats = function (data,element,fortx,forty,bool) {
alert(Json.toString(data));
return orig(data,element,fortx,forty,bool);
}
})(); void(0);
If you execute this code in your browser’s address bar, you’ll get an alert message the next time when you look at a fort battle result. Copy & paste the result and you’ll have the data anywhere you want it to have.