During the development of Heroes of Asgard server side I came across a system of behaviours and AI for monsters and bosses (as would be obvious in any game).
The general system was already set up and I have to say that I’m satisfied, each monster has its AI_GROUP pointing to a list of Behaviours and each is running into an update loop.
Now comes the realization of individual behavior, the specific case of aggro* of an entity to another one.
*For who does not know what is the aggro, it is the act of attracting the attention of a monster.
So I analyzed the problem, slicing it in more parts.
The behavior begins with the monster in neutral state (or IDLE), it will run only its passive behaviors (such as randomly walking around its spawn point).
The aggro is activated when a player joins the list of attackers of the monster, it has two ways to join it:
- They attack the monster or enter the field of vision of the monster if it is aggressive.
- The monster then continue to execute its behavior in accordance with the current state of aggro and will decide when to attack, when to retire or chase.
So far so smooth. My dilemma arises if the list of attackers contains more than one player. Clearly, there must be an act for which the monster can choose who honor with its loving attention and who ignore.
The gameplay arising from this behavior changes dramatically, as I’ll explain shortly.
Mini glossary of the preface:
Tank: the one who gets the beating in most RPGs, characterized by a number of industrial life, from little damage done and slow.
Damage dealer: the one that blows in most RPGs, characterized by short life, a number of industrial damage done and a good speed.
ATTACK THE CLOSEST PLAYER
I thought of a simple “attacks the one who is closest, don’t give a f*ck of others“, but this can create unexpected situations.
A monster adopting this behavior will create a situation where the tank class (which is by definition short-range melee) will ALWAYS the one who receives damage, leaving damage dealers happy and spotless around. You will then have an extremism: the tank is the one who will mount any object liable to prolong his life and damage dealers will have anything to do more damage, not having to worry about receiving damage.
ATTACK WHO DEALS MORE DAMAGE
I then thought about the reverse case: “attacks who makes you more damage“. Again, it can rise unpleasant situations.
In a comparison of damage between a tank and a damage dealer, aggro will ALWAYS be on damage dealers, sending all to hell roles and concepts of tank and damage dealer.
A MIX BETWEEN THEM
I then thought again: why not mix the two? Attacking those around you until its damage is the average (X%) of the damage done by all the attackers, otherwise go to break the neck to the one which damages exceed the average of X%.
In this way it is already more balanced as mechanics, but even here you can appreciate the obvious limitations: once the damage threshold, the tank has no more chance to gain aggro if not chasing and attacking the monster, who is chasing the damage dealer, trying to do more damage than the damage dealer do.
It will be funny.
THE END OF THE JOURNEY
How to solve these problems and then give a consistent thickness to the gameplay of the fights? So I took the last case proposed and I based on that, since it was already a good foundation.
What to do, however, to eliminate the problems that plague the mechanics?
Aggro points. Any attacker will be aggred when he has the highest value of aggro points among all attackers.
How does it work then?
It added a new bonus: the probability of drawing aggro (AGGRO_PROB). It is a simple value from 0 to 1.
Depending on the races, the basic AGGRO_PROB can be more or less high (high in the tank, low in the damage dealer). Of course is possible to add this probability on equipment.
When someone attacks a monster, he gains aggro points:
aggro_points = damage_dealt * AGGRO_PROB
It is clear that a tank with bonus of 100% AGGRO_PROB gains maximum aggro points, while a damage dealer with little AGGRO_PROB gains much less. It is also clear that the bonus AGGRO_PROB will be highly sought by tank classes, but can act as a penalty on damage dealing classes (which adds a positive note to the gameplay).
Another addition is to make sure the fallen of aggro points in time, in inverse proportion to how they earned.
Then a tank over time will lose few point, a damage dealer will lose many.
aggro_points decay = (((current aggro_points * k) / 100) / AGGRO_PROB) * k / 100)
At this point, doing the math, we can give values to these expressions.
damage_dealt = 800
AGGRO_PROB = 1
damage_dealt = 2000
AGGRO_PROB = 0.1
The tank attacks the monster A.
tank_aggro_points = damage_dealt * AGGRO_PROB = 800
The damage dealer attacks the monster A.
dd_aggro_points = damage_dealt * AGGRO_PROB = 200
Damage dealer dealt a lot of damage, but the aggro remains on our tank.
Now we modify damage dealer’s damage: damage_dealt = 10000.
dd_aggro_points = damage_dealed * AGGRO_PROB = 1000
This time aggro is assigned to damage dealer, because he did a huge amount of damage more than our tank.
But now it will be easier to return aggro at tank. Let k = 20:
tank_ap_decay = (((800 * 20) / 100) / 1) * 20/100) = 32
dd_ap_decay = (((1000 * 20) / 100) / 0.1) * 20/100) = 400
After a tick of decay, aggro pass back to the tank. Obviously the numbers are a little bit random, but you can get it. We need to tune them in future.
In addition, the calculation can also be added the level of the player or the difference of levels, or Dexterity, etc.