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.
Awesome 😉 I aint what you’d call a “programming nerd”, but it’s still interesting.
Are you willing to supply the fort battle xp formula aswell, or is that too much info at once?
I don’t see much use of making the xp formula public (except that you can argue about it on the forums ;)). So no, as long as I don’t see a need for this, I don’t even consider it ;). You might, of course, try to enlighten me 😉
I’m working on getting it, not much succes so far tho 😉
Well… there are a few variables counted in. And one is not included in the logs (player level) – so you’ll have to consider that beside the data from the logs 😉
I’ve put up a list, and like 10 people filled it in, it contains level, shots hit/missed, ppl KO’d, amount of attack/def, what side you’re on, etc. I just bloody hope there aint a random factor involved 😛 I think I’ll find it eventually 😛
Helló
i have a problem i make fort batle player
and i found bugs in log
“posidx” is not correct 🙁
where i can find the starting position?
Hi zet,
is the tile texture available too?
@docschwede: You can use the textures as long as you clearly state that Innogames is the copyright holder.
@errata: I have currently no time to check if there is really a bug in the logs.
Thanks for your answer, but i didn’t find the tile texture yet 🙁 . If my interpretation of the maps part is correct, this would be the only part to get an accurate background 😉
DocSchwede:
you can download the pictures from here: west.errata87.info/Image.zip
(I hope no problem I copied from swf)
yep, unfortunately, the initial positions seem to be wrong..
posidx is not correct and also the CHARTARGET
@errata
yes, part data in posidx is trash (perhaps there coordinates death fall or another, but they are wrong as “start position”)
Zet, I take a look at the logs and noticed that the “posidx” is actually the last player position. Always.
Hope this can be fixed somehow 😉
And I found no problem with the CHARTARGET so far
@Shrike
Really annoying bug. I recently developed a fortplayer based on information in this post and sadly it doesnt look too good when the starting positions are all messed up. Please fix this !
@all those who noticed problems in fort battle logs: This should be fixed, but right now, this is of lower priority… The data that is broken can’t be fixed anyway and it’s currently of no use for the game… (sadly).
pretty offtopic: there is a way to get the duel stats for a player (a function or a variable accesible via js)?
i’m trying to make a greasemonkey script that will add some extra info in a player profile, like the number of duels won/lost. these will save some extra clicks (for looking out the player in the rankings window). the duel level isn’t nessesary a good way of judging a player.
thx 🙂