Recently Rift released Nightmare Tide! We added in five new levels, an entire continent's worth of content, and an entirely new game system: Minions! With the Minion system players can collect various characters they've met in their travels and then assign them to a variety of different adventures, which change daily. Once a minion embarks upon an adventure it lasts for a certain duration in real time, and when the time is up the minion earns experience and brings back some loot. As the minions gain experience they level up and improve various stats that they have which in turn improves the loot they bring back later on. This system has proven to be a huge hit with players, and I wanted to pull back the curtain on it and show what's going on under the hood.
Unlike nearly all of the systems in Rift, the minion system is region wide rather than character- persistent. This is slightly different than 'account wide,' however: it's based on what database the player's server talks to. In practice, this is very similar to 'account wide,' provided the players are not going from say, a US server to a European one. What this means, practically speaking for our players is that they can use minions and claim rewards on any of their characters. Behind the scenes, however we needed to add support for this feature. Ultimately whenever a player logs on we go to the database to get all their character information. However while this is also true for the minion system we have to go to a different section in the database to obtain the needed info, loading it up upon log in. So why does this matter at all? Ultimately, it means that every change the minion system makes needs to hit the database to record it, which is not the case for many other character changes. This may not seem like much, but because the minion system exists for every player, and essentially in every location, it means that this load could become very very large. Which leads us to....
Being as lazy as we can get away with.
I don't mean this for me personally. I am of course referring to the system itself. It does the absolute minimum work it has to, at the absolute latest time it has to. Only when it actually does any work does it update the database. Which means, for most cases, that the minion system only hits the database, a few times each hour for every logged in player, and usually much less. Because of this, we can then afford to let every player access their minions whenever they like, instead of requiring them to go to specific areas to take care of them. Since we're running fairly light, this allows us the opportunity to spend some processing time doing other things, like ensuring players don't get the same adventure repeatedly through the use of a cooldown system.
Thankfully this is the sort of system where lazy evaluation comes naturally: most of the individual components are relatively independent of each other and the rate of change in the system is typically measured in minutes or hours rather than seconds. Internally this allows us to operate upon a set of discrete events, such as when the timer for an adventure completes or when a player clicks a button to assign a minion to an adventure. Because of this we can entirely forgo an 'update' loop, reducing processing time. Even the daily refresh, when the player receives a new set of adventures, is handled in a lazy fashion. Rather than watching for exactly the right time, the system only cares if the daily refresh happened prior to a log in. This has the additional benefit of being relatively transparent as no adventures suddenly change while they are looking at the interface.
Opening the hood.
Before we open the hood completely I want to first mention our tools. Here on the Rift team we have a very useful tool that allows for much of our development to be data driven (and thus designer driven) rather than engineer driven. This saves a significant amount of time in the long run and is vital to our ability to rapidly iterate on features. There are a number of things that I'll discuss that are strictly engineering topics but a significant amount of effort went into ensuring that designers could easily manipulate the data behind the system.
Despite the system appearing to be relatively straightforward, there are a number of working parts. Let's start out by dissecting the minions themselves. While the player refers to them as minions, I see them as "Minion Templates". Why a 'template'? Because each of these minions are the same for each and every person, assuming the level is the same. These templates consist of a few different variables which contain information on how a minion levels up, how their stamina regenerates, what stats (Such as Fire, Earth, Artifacts or Dimensions) they have and how they increase as they level. Each minion needs each of these properties filled in. Because of this, I ensured that most of the information can be shared among multiple minion templates. For instance, rather than each minion having a special definition for a stat and how it grows, we instead have a generic stat growth scheme. This stat growth scheme essentially creates a set of coordinates that we do a linear interpolation between. If a designer wishes to keep things simple all they need to do is create a start point and end point. If they want to do something fancier they can make stats which grow very quickly in the first ten levels and then grows more slowly for the next fifteen. We then map a stat to a one of these growth schemes.
Adventures however are a bit easier to develop. A name, a duration for the minion to be on the adventure, what type of adventure they are, the cost to hurry, and special requirements to embark on that adventure. Though most of those variables are straightforward, the requirements created an interesting issue: How can we efficiently and randomly select an adventure when we don't know how many adventures we qualify for? The solution for this particular issue is one of my favorite algorithms, called reservoir sampling (http://en.wikipedia.org/wiki/
Despite adventures being quick to set up for a designer there is one specific part that requires a larger investment of time on the part: the rewards. Each adventure refers to one of many different reward sets each with it's own tiers of success. Each of these reward sets contain the loot tables we roll on and a few other interesting bits. For instance, we can create a reward set such that if a minion qualifies for the second tier, they also receive the reward for the first tier, but if instead they earn the third tier, the first two are ignored. This allows us to better fine tune what someone gets from a adventure.
While being able to fine tune rewards is great, it's not my favourite feature of the rewards system. As an old school pen and paper rpg'er, I've played many different styles of games. The one thing that irks the heck out of me is how poorly many systems handle rolling for things. Especially when only rolling a single die. Why? Because a person has an equal chance of getting a 5 as a 13 or a 20. And that's well and good I suppose but I wanted something better! So one of the neat things under the hood on the reward system, is the ability for the designer to specify the number of sides and number of die to use to generate a random number. Ultimately this allows us to create a nice distribution of random numbers, which means a minion is far more likely to do 'average' on a run than to do 'poor' or 'great', making such results more meaningful.
One of the most amazing things about the Minion System is the fact the whole system only took about two to three months to put together in it's entirety. The effort was not just mine, however, as there's been significant contributions from three other Rift team members. It was thanks to them that we were able to put together a well playing, great looking system that so many players enjoy.