top of page

Data driven integration

AGT.png

My job on Dofus was mainly about integration, not much design. However, that means I've been given to use the Ankama Games Toolkit a lot, and the principles behind it truly were the most interesting things I learned there.

I can't really show you all of it, that'd be overwhelming anyway, but I will give you my own interpretation of this tool. I hope you will find it as amazing as I do ! 

What's the Ankama Games Toolkit (AGT)

From a strict point of view, the AGT is simply a more friendly data editor than an SQL interface. It is the main tool of the game designers, and only game designers. Actually, on Dofus, technical responsabilities are very split among teams. Basically, only programmers touch code, only level designers touch game scenes, etc. and only game designers touch data.

It may seem quite limiting at first, but the system is actually very powerful and enables game designers to create a wide variety of objects, such as NPCs, quests, achievements, or spells. The handy part about this is that it requires no programmer to make things happen in the game. The system is made so that the data "translates" into code automatically. Thus the designers can design and integrate all by themselves, without a single conflict ever !

 

I will go into further details about spells because it's the deepest and most interesting exemple of how powerful this can be.

Fundamental actions

A spell data is basically lines of effects. I don't have actual screenshots of the AGT, but this is fine. I'll show you a simplified version using a spreadsheet mockup instead.

The main parameter of a line of effect is the action. There are plenty of base actions aviable that match a function in the code. Some examples of actions are "Deal fire damage", "Summon something", "Push". There are also parameters that mean something depending of the action. For example "How much fire damage ?", "Summon what ?" or, " Push how many cells ?"

There are also global parameters that are set for all effects. I won't display all of them because that may be overwhelming. However, I'll display some, such as the trigger ( When does the effect trigger ? Immediately ? At the beggining of each turn ? Upon taking push damage ?) and the area of effet (Single cell ? Circle ? Everywhere ?). I will also mention range, although it's not actually an effect parameter but I think it makes it easier to grasp for basic spells.

image.png

A generic fireball spell that deal 1D3 + 19 fire damage to a single target 1 to 8 cells away

image.png

The spell "Release", that pushes anything in melee range 4 cells away. Note that Push only uses P1

Faking logic : Target Masks

Data is not programming. Data can't "do things", nor "check things", and certaintly not "do things according to a check". Said differently, data doesn't have if/else. However, some spells in the game really look like they somehow do different things in different contexts. For example a spell both damages ennemies and heal allies, depending on who is in the area. From a programmer point of view, one would really like to write it like "If target is friend : heal, else hurt". But how can we achieve this with stone cold data ?

Introducing : Target mask. It's a parameter set in each line of effect of a spell. It describes what entities are affected by the effect. There are a bunch of them that may describe quite precise group of entities. Examples are allies, summons, or a specific monster ( typically useful for boss spells).

Now the real genius move. A spell that does different things depending of what it hits actually does it all, with appropriate target masks. Said differently, the spell is always the same, the spell doesn't adapt to anything, it's just that some effects miss some targets. Thus it can be written in stone! 

image.png

This spell damages ennemies and heals allies in the area, all at once

Faking functions : Target casts a spell

The basic action that really pushes this system into another dimension is "Target casts a spell" with the parameter "What spell ?". It makes the target cast a given spell. Basically, with this, you can think of spells as functions, that may execute other functions.​

It enables more complex effects, that's would not be possible directly. For example, imagine a spell that seeds an opponent, and at this end of its turn, the seeded opponent explodes dealing damage arround. The tricky thing here is that it's a single target spell, with area of effect. If you'd cast directly something like "Fire damage, triggers end of turn, circle 2", each entity in the circle would have its own damage trigger during its own turn, and that'd be more like a mass poison than an explosion.


However, with two spells, this becomes possible : 

  • Seed : Target casts Blow at the end of turn

  • Blow : Deals damage arround

image.png

The player only casts seed, which makes the spell single target, single trigger, and blow does the actual thing

There are actually a billion reasons to use "The targets casts a spell". Re set the reference of a spell, fix timing issues, break down a huge spells, encapsulate a recurring effect, or even just for semantics.

 

For example, the Forgelance's gameplay revolves arround a spear that many of their spells summon/unsummon. There are spells named "Summon the spear" and "Unsummon the spear". Summon is two lines, unsummon is one. It technically does'nt require to be encapsulated like this, it would work fine if it weren't. But similarly to programming functions, if something were to change, it would require only one change in one place. And even if unsummon is one line, basically "Kill target, area : all, mask : Named Immortal Lance", a data line in the AGT is like a kilometer long, you can't even display it all at once. When encapsulated in its own spell, it is easier to read. Moreover, in this case, the spell is read by the player. Imagine playing a forgelance for the first time and seeing "Kill target, area all" in half of your spells !​

Faking booleans : States

A target mask I deliberately did not mention is "Entity in a given state". States are like tags that can be applied on entities. Players know some, like the Sadida Infected state, or the four elemental states of the Huppermage. Yet, most states are actually invisible and used for various checks in spells. They can be set/unset with the basic actions Set State and Remove State.

State can be used as booleans. You can declare a bunch and have all the custom checks you want with them. For example, Grunob's lesson is a recursive spell that bounces on nearby targets. But it can't rebounce on an already affected entity. That's why the spell tags everything it touches with a state, and the bounce misses tagged target thanks to an appropriate target mask.

image.png

The spell does its effects (fire damage and healing reduction), tags the target, then bounces on neabry non tagged ennemies.

Note the use of the variant "Caster casts a spell" here. If we used "Target casts a spell", "Ennemies" on the second iteration would the first target's ennemies, that is, playable characters. 

If it doesn't look amazing to you yet, I think I have not enough pointed out that all of this involves 0 programmer. Grunob's lesson's whole recursive behaviour is only 4 lines of numbers in a database. But now let's get into the real deal, and analyse a boss mechanic !

A big exemple : Tal Kasha's Delock

As a final attempt to make you grasp how powerful this sytem is, let's talk about a typically complicated kind of spell in Dofus : A delock. Numerous bosses are completely immune to damage until you do a specific thing that makes them vulnerable. This whole thing is typically managed by a spell cast at the beginning of combat by the boss. 

I've chosen talk about Tal Kasha, because her delock is quite easy to comprehend player wise, but hides a lot of unexpected complexity data wise.

Basically, when fighting Tal Kasha, ennemies respawn when killed. When they die, they leave a single cell glyph under them. When Tal Kasha has started 3 turns in glyphs dropped by 3 different ennemies, she becomes vulnerable until end of combat.

Here's a video of the fight to help you see how it works :

Now let's talk about the spell's data : There are actutally 6 spells 

  • (Main) The main spell makes Tal Kasha immune and casts 3 secondary spells a the beginning of combat :

  • (S1) A spell that hits all Tal Kasha's allies, and will trigger when they die. As they do, it makes Tal Kasha drop a glpyh where they died.

  • (G1) A glyph actually encodes a spell in it. This one makes the lastest dead ennemy respawn.

  • (S2) However, how would that glyph trigger ? The ennemy is dead, so it can't be on the glyph to trigger it. That's why Tal Kasha's second secondary spell makes every playable character make Tal Kasha trigger (and clean) thoses glyphs at the end of their turn. Simply put, a player that kills an opponent is the one that triggers the resurrection.

  • (S3) Then, Tal Kasha's final secondary spell triggers. It's a spell cast on each of her allies that trigger when they enter the zombie state, automatically applied when resurrected. If they are not in Tal Kasha's Generic State 1 (we'll get to this one soon), it makes them drop a glyph. Yes, another one. You probably thought there were only one glyph, because the visual transition is seamless, but there are actually two !

  • (G2) The second glyph is a counter. It triggers at the beginning of each turn if Tal Kasha is on it. Data doesn't feature variable counters like programming would, so it actually works with 3 states : Seal 1/2/3. If the target doesn't have any seal, it applies seal 1. If It has seal 1, it removes seal 1 and applies seal 2. Finally, if it has seal 2, it removes seal 2 and applies seal 3. When it does, it removes invulnerable, Tal Kaska is delocked. Also, note that the glyphs tags its caster with Tal Kasha's Generic State 1. This means a monster that dropped a glyph triggered by Tal Kasha won't drop their second glyph anymore, because of the condition in (S3). This is how the player is forced to use glyphs from 3 different monsters. Note that this is possible thanks to the very handy target mask "Caster, even if out of range" that enables the glyph to find the monster who dropped it !

image.png
image.png
image.png

And there you have it ! A boss mechanic strictly out of numbers in boxes. Once the system is set up, zero programmer is required to add Tal Kasha's gameplay into the game ! 

Conclusion

This system is truly the most valuable thing I learned when working at Ankama. It's the solution to many issues I encountered before. Sure, the actual AGT itself is a big one, with many, many, things I voluntarily silenced to keep it as simple as I could, while still trying to make it impressive. A fair replica would be too much work, but I think the principles I've highlighted in this page are quite reachable, even for a small team. 

Even without considering "splitting powers" like one could in a huge team, this system has plenty of intrinsic properties. It enables highly modular design and integration, much more modular that I could've imagined. Tal Kasha's delock is merely one of many examples. Moreover, it makes integration independant from the scripts. This is really healthy for the project, because functionnal scripts can remain untouched, and what works will continue working on ! And if ever an additional feature is required (thinking about Harebourg's confusion), devs only have to conceive a new basic action. This basically fixes the issue by making the system stronger and more modular at the same time !

The next time I'm working on a more or less RPG game, be sure I'll advocate an AGT-like framework !

bottom of page