The damage calculations for RoN are, in short, complicated.
Units have a base attack value, which then goes through potentially up to at least eight unique damage modifiers12 depending on the units (and/or buildings) and circumstances involved. After all the modifiers, the defending unit’s armor is applied as a flat reduction and you end up with the final damage value.3
All of the static modifiers are calculated on game load, allowing for the values that are hardcoded into the game to be modified further by its game files, such as through official patches and user-made mods. The resulting calculation is a 493×493 table made by the game featuring every unit and building in RoN, even some that can never enter combat (Bison, Whales), or that are never used in the game (Siege/Catapult Ships).
This 493² matrix is part of the enormous header located in save game files, meaning we’re able to extract and view it. Here’s how to go about that.
Note: if you just want the end result (not the process), scroll to the bottom.
First you’ll need a save. To save (haha) time you can load up a single player game, save, and then quit – the full matrix will still be there in the save file even if the game session is only a couple seconds long. Just remember that if there are any modifications to game balance active at the time (such as via mods), those will be present in the matrix you end up extracting.
Rise of Nations saved games — at least those using T&P or EE — are .SVX files,4 but use gzip (?)5 compression. If you have a program such as 7-Zip handy, you can simply right-click -> extract the file.
From there you’ll need to open up the decompressed file with a hex editor. If you’re using a short saved game then the matrix will be towards the bottom (since there’s very little actual replay data), but if you’re using a “normal” save then expect it towards the top. So long as you’re not using any particularly unusual mods, you can find the start of the matrix by searching for a hex phrase of alternating 64s and 00s, e.g. 64 00 64 00 64 00 64 00.
These are the calculated static damage modifiers for a Citizen hitting a number of other targets. Each modifier is two bytes, typically a one-byte value flanked by 0x00 bytes. I can no longer remember whether this is little or big endian,6 but in any case we don’t actually care – we just want to copy the entire thing out for now. I copy out starting from 64 and include the 00 at the end point rather than the reverse (with 00 at the start and 64 at the end), but you can choose whichever method you think is least likely to induce the sudden heat death of the universe. (For subsequent steps I’m assuming my method though; there are minor changes if you do the reverse).
The terminating point of the matrix is marked with an 8F byte, excluding that byte itself. Immediately below the matrix you’ll see many variations of alphabetical and other character listings, I guess so that the game can guarantee it has all the characters it needs to print out strings and such. These make determining the end point relatively easy.
You can copy out the entire table into a text editor such as Notepad++. Ideally use something with regex because it’ll save some time (Np++ supports it), but at the very least you’ll probably want monospaced text and a replace all function. I’m going to assume you’ve found a regex-capable editor; I know first-hand how much easier the next steps are going to be with regex than regular old find/replace.
Start from the very start of the matrix and regex replace
$0\r\n to separate it out into 493 rows (thank you internet). In lieu of regex you can do this manually by counting or finding exactly the right number of bytes – it’s actually not too bad once you get into a rhythm,7 you just need to repeat a sequence of keystrokes that goes to the end of the where the line is, makes a new line, then returns to the old position (but where the next line of text is). I would however argue that the regex method is far better for your mental health than the manual method.
Next, group each double-byte together by removing alternating spaces. With regex replace this can be done by going to the start of the matrix (the very start) and replacing
(\S\S)([ ])(\S\S)([ ]) with
\1\3\4. You can use a regex tool to help walk you through what this is doing if you’re curious. If you replace all then you may find your editor will freeze/hang for a few moments while it does the hard work for you – just give it a bit and it’ll probably be fine unless you’re using a bad editor or particularly slow/old PC. Note that the last double-byte won’t be picked up so you’ll have to do that one manually.
Now you can copy this somewhat formatted monstrosity into a spreadsheet editor. I’ll note here that Libreoffice Calc did not enjoy the process of dealing with the extracted matrix, trying many a time to terminate its existence during the course of my attempted editing. I had better luck with a modern copy of Excel, so if you have trouble with your first editor of choice you can try an alternative.
If your spreadsheet editor is sufficiently sophisticated, you should be able to find a function that will split each entry into a new column based on a splitting character such as a space. After that you’ll (hopefully) have a 493×493 cell grid in hex. Make sure you have the cells formatted in such a way that your editor doesn’t condense a number like 0031 to simply 31; you want literally what you put into each cell to remain as-is, otherwise you’re going to end up with some very wonky values.
If, like me, you copied the matrix starting at 64 and ending at 00 inclusive (rather than starting at 00 and ending at 64) then you’ll now need to use a function to flip the order of the bytes.8 Using a separate sheet that references the raw hex and combining LEFT and RIGHT with CONCAT will do the trick, but there are certainly many different ways to go about this – some of which would surely be less computationally expensive.
You’ll also need to convert the hex to decimal so that it’s human-readable (unless you can read hex, in which case more power to you). I’ve personally combined both this and the byte swap into a single, larger formula like so:
=HEX2DEC(CONCAT(RIGHT('name of sheet with hex'!cell name,2),LEFT('name of sheet with hex'!cell name,2)))
The 2 in the RIGHT/LEFT is to select two characters (i.e. each byte). You’ll need to tweak the exact formatting of the formula depending on what program you’re using.9 Congratulations, those are the damage modifiers for every unit and building in the game.
As for labeling each cell: you can extract the labels, in order, from the same save file, but it’s somewhat inconvenient so I’ve never personally bothered. If you search for UTF-16 little endian strings for unit names (especially obscure ones like “Fur Trapper”), you’ll find what I expect is a big list of every unit and some associated stats and stuff (not sure exactly what since I didn’t parse it). So long as you’re not using any extreme mods that completely changing any units, what I’d recommend instead is that you just copy someone else’s labels. The layout for each unit and building is identical in T&P and EE, so it should just be a careful copy-paste job.
If you extract multiple damage tables from different versions of the game/mods you can then use
=(('sheet 1'!A1)/('sheet 2'!A1))-1 or the equivalent in your editor. Replace the sheet names and cells as appropriate, although I recommend you map out the cells exactly 1:1 (with e.g. B3 in the first sheet always being B3 in the second sheet) to make it easier to track things. Conditional formatting will help make noticing the differences easier.
Now that you’ve got the whole thing in front of you, you’re free to browse through and find juicy weird modifiers. MG42s have a damage modifier of over 2000% against some units. Shipyards (which have no attack value) would theoretically deal a bonus 150% damage to most structures. Oil Platforms (but not Oil Wells) deal 1/3 damage to Airbases, like most units do.
You can also see what’s changed between versions, such as noticing that the Extended Edition has broken a subset of the damage modifiers, causing basically every unit to deal or receive the wrong amount of damage in at least one unit matchup. Observe also that the damage modifiers for Barks and Triremes are completely borked in EE.
This is one of the tools I’ve personally used to investigate damage-related bugs — especially for CBP — and it’s been really handy for that. Hopefully a few other people can find it similarly useful, or at least interesting. As a final note, the game generally rounds down on damage modifiers, even mid-calculation. So if you make a change based on an exact result, you might notice your damage modifiers are off by a very small margin like this:
For those who just want a copy of my spreadsheet, I’ve uploaded the file here on Keybase (for a spreadsheet it’s
pretty large and bulky heckin’ chonkers). UPDATE: the linked file now has the QA’d final changes for CBP Alpha 7 including the singleplayer-only Ironclad changes.
2023 update: that host is shutting down, so here’s a mirror.
I did also try moving the data to a Google Sheet so that it would be easier to share and such. I got about halfway in and it started to freeze up my browser badly enough that everything else on my PC started to freeze up too, so I guess you’ll have to live with the Excel file.
If you notice any errors in the spreadsheet, whether formatting on my part or actual damage number issues, please do let me know though. I’ve checked over it multiple times but there are literally 243,049 values for each game/mod version. I’ve done my best, but it’s very challenging to keep things entirely error-free when that many numbers are involved.
- Some are simply binary conditions based on in-game circumstances (such as being in a river), while others are part of the game’s very complex balance methods. The latter category are what we’re interested in here.
- Because I’m predicting at least one person will ask for the full list that I know of, here are the eight potential modifiers: river, height, rocks, age difference, balance.xml direct modifiers, balance.xml mask modifiers, hardcoded mask modifiers, flank damage. I’m not ruling out that there may in fact be more than eight. You could potentially add both ammo count (shots per attack) and sub-units (e.g. infantry squads) into this as well for a minimum total of ten.
- Except not quite: I’ve simplified slightly, but it’s roughly correct.
- I’m assuming from this point forward that you’re using either T&P or EE for this, not vanilla.
- I haven’t verified that this is true by e.g. looking at the file metadata header, but it seems to be either true or approximately true enough that we don’t care.
- It’s one of those things I deal with once or twice a year, figure out, then forget.
- Speaking from personal pre-regex experience..
- Now that I’m typing this out, it makes it seem potentially very dumb to not just use the reversed order during the extraction phase to avoid this additional step here, but I also don’t know the save file’s structure in great detail and don’t like the idea that the preceding byte might not always be 00.
- Not every editor uses the same formatting for this stuff unfortunately.