Simulating Fire Emblem’s RNG System in Python

Have you ever played a Fire Emblem game? If so, you will know that the combat system is RNG-based, you have a 0 to 100 chance hitting an enemy, and if you do so, you have a 0 to 100 chance of dealing a critical hit (a more powerful attack than a normal hit, but also a lot less common).

If you’ve ever wondered how this system really works, you’ve come to the right post! First I will explain in how the RNG mechanics work and then I will convert it to Python code. So, let’s begin!

In the picture above, you can see the battle screen for Fire Emblem: The Blazing Sword. To the right, you can see the battle data from the player controlled character, Florina (from now on we will call these characters an “unit”), you can see how much Health Points our unit has (14 HP), which weapons our unit is holding (an Slim lance), the hit chance of dealing a blow (99), the damage our hit would deal to the enemy (2) and the chance of doing a crit (11). To the left, you can see the same info but on the enemy’s Bandit unit. As you can see, our unit has an almost guaranteed chance of hitting the enemy, while the enemy’s chance are only 51%. Not a fair battle!

Now we know what all this means, but how does it works? Well, it depends on which Fire Emblem game are you playing (for the Accuracy /Hit Rate / Crit Rate / Avoid formulas) and if you’re playing on the English or the Japanese version of the game (only affects Accuracy). In this post we will simulate in Python both the Japanese and English version of Fire Emblem: The Blazing Sword (FE7 from now on), but first, we will explain each formula in text form.

Hit Rate

This might sound a little confusing, but the Hit Rate and the Hit numbers that shows on the pic above are not the same. In FE7, the Hit numbers refers to the Accuracy (we will explain that Accuracy is later). Actually, the Hit Rate alludes to the a character has of hitting a stationary object (an unit with an Avoid equal to 0, we will expand on this on the next section). The formula is this one:

Hit Rate = Weapon Accuracy + Skill  x 2 + Luck / 2 + Support Bonus + S-Rank Bonus + Tactician Bonus

Now, let’s break this formula down:

• Weapon Accuracy: Every weapon in Fire Emblem has a number called Accuracy associated with them, the higher the number, the higher the chance of hitting the enemy with this specific weapon.
• Skill: Every character in the game has a set of stats that will grow as they level up, Skill is just one of these stats. It is directly proportional to both Hit Rate and Crit Hit Rate.
• Luck: Another stat just like Skill, this one isn’t as relevant to the Hit Rate and Crit Hit Rate as Skill. For every point in Luck, both the Hit Rate and the Avoid variable go up by 1.
• Support Bonus: In FE7 there is a Support system, where if 2 units end their turns adjacent to each other their relationship will improve. There are 3 relationship ranks, C, D, and A, the higher the rank, the higher the Support Bonus.
• S-Rank Bonus: There are different types of weapons in FE7 (swords, spears, axes…), the more you use a certain weapon with an unit, the better they will get at using that specific weapon. This improvement is showed again by ranks (E, D, C, B, A and S). When you get to the S-Rank, you get +5 to the hit rate and crit rate when using that weapon.
• Tactician Bonus: Every unit in the game has an Affinity type, used primarily for the Support system. If the Affinity type of the unit is the same as the Tactician, a special type of unit in the game that doesn’t actually fights, the unit will get a hit rate boost.

If you’re not familiar with the game it might be a little difficult to understand, but don’t worry, we will simplify it when writing the Python code. Now let’s move on to the second formula.

Avoid

This is the second part of the Accuracy formula. it determines how likely is an unit of avoiding being hit by an enemy’s attack. This new formula adds 2 news parameters that I will explain later:

Avoid = Attack Speed x 2 + Luck + Terrain Bonus + Support Bonus

We already know what Luck and Support Bonus is, so let’s talk about the newcomers.

• Attack Speed: Another stat, this one determines whether the unit will perform a double attack or not, and also contributes to your evasion. To calculate the attack speed, we use another formula: Speed – (Weight – Constitution).
• Terrain Bonus: Every battle between two units takes place in a terrain. Some terrains are neutral in terms of boosts, but others give advantages or disadvantatges to the units, for example if your unit is in a Fort while being attacked, they will get 20 points more of Avoid.

With these two formulas broken down, now we can pass to the important one and the one we will simulate in Python, Accuracy!

Accuracy

Accuracy = Hit Rate – Avoid

Boom. Easy. Well, not that easy, considering that the actual, expanded formula looks like this:

Accuracy = (Weapon Accuracy  + Skill x 2 + Luck / 2 + Support Bonus + S-Rank Bonus + Tactician Bonus) – (Attack Speed x 2 + Luck + Terrain Bonus + Support Bonus + Tactician Bonus)

Critical Chance

Both the Critical Rate and Critical Evade are very similar to the Hit and Evade rate from earlier, so I will condense them into the final formula:

Critical Chance = (Weapon Critical + Skill / 2  + Support Bonus + Class Critical + S-Rank Bonus) – (Luck + Support Bonus + Tactician Bonus)

The only new parameter here is Class Critical, basically every classe has a higher or lesser chance of dealing a crit.

So, with all the math out of the way, let’s show the code!

It should be noted that due that we’re only interested in the RNG part of the combat mechanic, we will skip all the Weapon Accuraccy, Skill, Luck… parameters and just add our own Accuracy and Critical Chance when starting the script. There will be 2 versions of the script, the one that simulates the English one, and the one with the Japanese. They don’t have many differences, but they do have one that is important mentioning.

English RNG

(Note: I imported randint from random and statistics at the start of the script)

def RNG_English(hit, crit):
hitnum1 = randint(0,100) # Generates a random number between 0 and 100
hitnum2 = randint(0,100) # Generates a random umber between 0 and 100
critnum = randint(0, 100) # Generates a random number between 0 and 100
hit_avg = statistics.mean([hitnum1, hitnum2]) # Returns the average between the 2 hit numbers
if hit_avg <= hit:
print("You've hitted the enemy!") # If the averaged number is below the hit chance, it will attack.
if critnum <= crit:
print("You've done a critical attack!") # If the critnum is below the crit chance, it will deal a crit.
else:
print("You missed the attack!")

Japanse RNG

def RNG_Japan(hit,crit):
hitnum1 = randint(0, 100)# Generates a random number between 0 and 100
critnum = randint(0, 100)# Generates a random number between 0 and 100
if hitnum1 <= hit:
print("You've hitted the enemy!") # If the random number is below the hit chance, it will attack.
if critnum >= crit:
print("You've done a critical attack!") # If the critnum is below the crit chance, it will deal a crit.
else:
print("You missed the attack!") # In case the random number is above the hit chance, it will miss

They’re pretty similar, right? The only difference is that the RNG in the English system will roll twice, and the number that will be compared with the Accuracy will be averaged between these two rolls. This makes that attacks with a 90% have a higher chance of being accurate, and attacks with a 5% will rarely hit.

That’s pretty much it, now let’s make a quick main function to select the HIT (Accuracy) chance and the CRIT chance, a selector to choose which version of the RNG use and a simple look to keep the script rolling until the user wants it to end.

def main():
while True:
hit_chance = int(input("Insert your HIT chance (1 - 100) " ))
crit_chance = int(input("Insert your CRIT chance (1 - 100) " ))
option = int(input("If you want to use the RNG English System, press 1, if you want to use the RNG Japan System, press 2 " ))
if option == 1:
RNG_English(hit_chance, crit_chance)
option2 = int(input("Press 1 to restart the script, 2 to exit "))
if option2 == 2:
break

elif option == 2:
RNG_Japan(hit_chance, crit_chance)
option2 = int(input("Press 1 to restart the script, 2 to exit "))
if option2 == 2:
break
else:
print("That's not a correct option! Please try again. ")
exit()

Ta-dah! Done. The full code should look like this:

from random import randint
import statistics

def RNG_English(hit, crit):
hitnum1 = randint(0,100) # Generates a random number between 0 and 100
hitnum2 = randint(0,100) # Generates a random umber between 0 and 100
critnum = randint(0, 100) # Generates a random number between 0 and 100
hit_avg = statistics.mean([hitnum1, hitnum2]) # Returns the average between the 2 hit numbers
if hit_avg <= hit:
print("You've hitted the enemy!") # If the averaged number is below the hit chance, it will attack.
if critnum <= crit:
print("You've done a critical attack!") # If the critnum is below the crit chance, it will deal a crit.
else:
print("You missed the attack!") # In case the averaged number is above the hit chance, it will miss.

def RNG_Japan(hit,crit):
hitnum1 = randint(0, 100)# Generates a random number between 0 and 100
critnum = randint(0, 100)# Generates a random number between 0 and 100
if hitnum1 <= hit:
print("You've hitted the enemy!") # If the random number is below the hit chance, it will attack.
if critnum >= crit:
print("You've done a critical attack!") # If the critnum is below the crit chance, it will deal a crit.
else:
print("You missed the attack!") # In case the random number is above the hit chance, it will miss

def main():
while True:
hit_chance = int(input("Insert your HIT chance (1 - 100) " ))
crit_chance = int(input("Insert your CRIT chance (1 - 100) " ))
option = int(input("If you want to use the RNG English System, press 1, if you want to use the RNG Japan System, press 2 " ))
if option == 1:
RNG_English(hit_chance, crit_chance)
option2 = int(input("Press 1 to restart the script, 2 to exit "))
if option2 == 2:
break

elif option == 2:
RNG_Japan(hit_chance, crit_chance)
option2 = int(input("Press 1 to restart the script, 2 to exit "))
if option2 == 2:
break
else:
print("That's not a correct option! Please try again. ")
exit()

if __name__ == "__main__":
main()

With this, our simulator is finished. I hope you know now a bit more of this fantastic game combat systems!