Have I mentioned that the nemesis system in Shadow of Mordor was really cool? Because it was. Playing that game made me wander what else could be done with it. For example, I have always been fond of RPG oracles and general random generators for pen and paper RPG games. I am firm believer that every NPC and/or enemy, no matter how minor or unimportant should have a name and a few distinguishing features. A good game master can usually make up such details on the spot, but keeping track of dozens of throw away characters which may or may not die or conversely become important at some point can be difficult. So random generators are GM’s best friend – especially when trying to populate the game world with diverse collection of characters and not just standard “dwarf with brown beard, elf with blond hair” type stand-ins which is what you usually come with when you need to make up a character on the spot.
While there are dozens of random NPC generators, I figured I might as well write my own. It seemed like a fun and quick side project. How would one go about procedurally generating non player characters though?
First and foremost I figured it should be easy to modify and expand. Instead of hard coding values into the generator itself, I figured it should be stored as some sort of a structured list. I went with YAML because unlike many data serialization formats what claim to be “human readable” it actually is. Well, at least for me it is – your opinion may of course vary and it is not like YAML is without a lot of weird quirks. But I basically just needed a simple data format that could be easy to edit by hand, and that could be consumed by my code without doing a lot of parsing. Seeing how in Ruby can slurp a YAML file into an associative array in a single line of code, it was more or less perfect.
Moreover, I wanted my generator not to be “fully” random but rather use weighted probability scores for specific things. For example, it should be relatively rare to see a Rogue wearing a plate armor, but it would be common to see it on Warrior characters. How do you implement that? There is a few ways. For example you could find the discrete cumulative density function (CDF) of your list, generate a random number in the range between 0 and the sum of all weights, do a binary search to find this number… Actually, no. Scratch that. This is a solved problem and there is literally no reason to re-invent it other than as a classroom exercise maybe (or if you are worrying about performance). Instead of spending hours writing and debugging CDF code, we could just grab a gem such as this one and be done with it.
The basic idea was to let me write a weighted list like this in YAML (higher the number, the better likelihood the item gets picked):
race:
Human : 6
Elf : 6
Dwarf : 6
Orc : 6
Goblin : 5
Halfling : 4
Hobgoblin : 3
Kobold : 2
Ogre : 2
Troll : 1
class:
Fighter : 4
Soldier : 3
Cleric : 1
Bard : 1
Diplomat : 2
Ranger : 5
Rogue : 5
Sage : 1
Scout : 3
Warrior : 6
social:
Commoner : 5
Noble : 2
Then in Ruby I could pull stuff out of it like this:
require 'yaml'
require 'pickup'
data = YAML.load_file('stuff.yml')
race = Pickup.new(data['race']).pick(1)
class = Pickup.new(data['class']).pick(1)
This was basically as complex as the code would get. As it is usually the case with this kind of projects the bulk of the work went into actually generating the data files that would yield not only a good deal of variety but also return both mundane and ordinary foot soldiers as well as funky and exceptional fun characters from time to time. It is more of a creative endeavor rather than programming.
What kind of weapons are appropriate for a rogue? What kind of armor should be worn by scouts? What color can Orc eyes be, and would this be any different for goblins? What kind of scale colors are most popular amongst the Kobolds? These were the sort of questions I had to answer while making this tool.
If you follow me on Twitter (as you should) you have probably seen me posting screenshots of the minions I was generating on the console:
Here are some more randomly-generated minions, now with personality traits and descriptions. pic.twitter.com/QERJtDXWP0
— Luke Maciak (@LukeMaciak) October 21, 2014
This is back when I still had “barbarian” as a class which I later decided against including. Why? Well, to me it seems like every other class (warrior, rogue, bard, cleric, etc..) is something you choose to be. Barbarian, on the other hand is something you are. It is more often than not used to describe a social caste or grouping of people rather than a profession / calling. So I removed it and replaced it with Fighter and Soldier to have 3 solid close combat classes. In my mind warriors fight out of conviction (they have a duty, seek glory, want justice, etc..), fighters do it because they like it (they are the brawler, trouble-maker types that start fights in taverns for shits and giggles) and soldiers do it strictly for money.
Creating plausibly sounding names proved to be a whole separate problem. I knew that when it came to elves and Dwarfs, I could just shamelessly crib from Tolkien if I wanted to because there are billions of good names for both of these races in the Middle Earth lore. But I didn’t just want to have gigantic copy pasted lists. So I opted for something slightly more clever. I grabbed some interesting names, broke them into (usually) two parts, and then randomly recombined them. For example, here is a sample of Orc name table:
Orc:
given:
Male:
prefix:
grish: 5
gor: 5
muz: 5
maz: 5
lag: 5
lat: 5
lar: 5
lir: 5
rad: 5
shá: 5
rag: 5
rat: 5
urf: 5
goth: 5
núr: 5
nir: 5
fár: 5
postfix:
nákh: 5
bag: 5
gash: 5
gnash: 5
bash: 5
mash: 5
gol: 5
mol: 5
duf: 5
buf: 5
rúf: 5
muf: 5
dúr: 5
grat: 5
gnat: 5
thrak: 5
lúk: 5
múk: 5
mog: 5
rog: 5
This particular selection can yield names like Grishnákh, Gorbag and Muzgash (all of whom are named characters from Lord of the Rings) as well as dozens more or less plausibly sounding names.
Most races have gendered first names and last names dictated by social status. So for example a noble’s name may include the name of their estate, or name of their father, whereas the names of commoners are typically nicknames or trade profession related. Elves, Hobgoblins and Trolls ended up with gender neutral names just because of how alien they sounded and because I wanted to have at least one group which did not have a concept of gendered names.
Once I had basic data files created, I wrapped it up in a bit nicer interface and started generating minions by dozens. It was interesting just to read their short descriptions and try to imagine how they would look and what their personalities would be. At some point I even noticed emergent little micro-stories popping up every once in a while. For example, here are two randomly generated Orcs I got the other day:
Noble born warrior Ragma travels with her attendant Mizni seeking glory in combat 2 prove she is worthy of her title pic.twitter.com/FcUfYJuCi8
— Luke Maciak (@LukeMaciak) October 26, 2014
I found it interesting that they were both ambitious and feared losing face. It felt like they were connected somehow. Ragma was a noble born warrior while Mizni one was a commoner and a ranger. Possibly Ragma’s attendant and a guide? They were likely traveling companions: Ragma young, impetus, and irresponsible, but eager to make a name for herself. The older, wiser Mizni was likely appointed by her parents to keep the young warrior in check, and make sure she returns home safely from their adventures. They both driven by their ambition. Ragma wants to prove she can live up to the high standards of heroism set by her parents. Mizni wants to prove her value to the family by taking on a challenge of keeping the wild and irresponsible Ragma in check. You could literally write a short story about them, just based on this relationship.
This is the beauty of randomly generated content: sometimes a short little blurb can strike a chord with you and your imagination will immediately fill in the blanks creating interesting and meaningful relationships and scenarios. I figured it was worth sharing this little thing that I have done with others.
I set it up on Heroku cloud platform, and named it Minion Academy, mainly because I managed to snag that as a URL. So it is now up at minion.academy. When you visit the page you will get five randomly generated NPC’s and you can refresh the page for five new ones. It’s very basic, and still rather rough around the edges. There is still some work I want to do with it.
For example, I want to add more armor choices. Right now it’s basically just cloth, leather, chain or plate. I would like to expand it so that you could have a wide variety of different armor types for each of these categories. You might have also noticed there are no magic user types being generated right now. This is partly by design (I was initially trying to make a minion specific generator which kinda grew to cover all kind of NPC’s) but I’d like to add some wizards and sorcerers at some point.
If you notice a bug, I have the source code on Github so feel free to submit a bug report. As usual, let me know what you think in the comments.