One of the core focuses of this game lies on the concepts of Aspects and Deities. These are two separate-but-related concepts that will affect nearly every other aspect of the game: level generation, monster aggression, loot generation, and probably more. This post covers at a high level how each of these might work, and then will dive into some implementation details on how monster aggression is calculated using a “Similarity Index.”
The Goal
Every living thing in the game worships or is somehow bound to a Deity. Every Deity has three Aspects – something the Deity is, has, or does; something it embodies or personifies; a quality, statement, or goal. In short, an Aspect can sort of be anything you might use to describe that Diety. The followers of these Deities will most often carry one or more shared Aspects, but it is not required for every follower to share every Aspect. In addition, an entity can potentially embody many Aspects, instead of the limited number that each Deity personifies.
When the player moves through the Chapel, each “floor” generated will be themed around one (and sometimes more) Deities. Map generation will be tied to the Aspects embodied by this Deity, and typically there will be an altar of some sort where the player can perform certain actions: pray, offer, desecrate, etc. Performing these may dramatically alter that player’s standing with the given Deity.
The creatures that appear on any given floor will either be followers of that floor’s Deity, or share enough Aspects to be allowed nearby. How many Aspects are shared, and how close the shared Aspects are to each other in some given quality (Healing and Peace, for example, are friendly, but not Healing and War) come together to determine the aggressiveness of an entity toward another entity. Most of the time, this is just the monsters toward the player. Sometimes, the player will stumble upon a floor with two altars, and the monsters spawned may be hostile toward each other.
A stretch goal of mine is to also include Aspects and Deities in item generation. More than affecting which items spawn, certain items may only be usable if you share an Aspect; some rare items may only be worn or used if you worship the Deity that spawned the item. This is low on the list, and I’m not sure exactly how fun it would even be if it did work this way. Something to consider and test later.
How it Works, Probably
Below is still mostly in flux, save for how the game data is stored and modified on disk – I’m pretty set on that.
Game Data
Game data is stored in tab-delimited flat files. A header row denotes the value below. This makes it easy to open the files in Excel or some other spreadshett, add a bunch of stuff, and get going. Since the game is text-based, this is really all I need. Game data is pretty simple here, anyway:
As you can probably guess, the Deity “table” three columns that act as foreign keys to the Aspect “table”. Factories in SCORLIB are written to grab entities by ID, among other properties, making it pretty simple to just grab anything without worrying about whatever its name or other properties might be. For the example above, I used an old program of mine I called “MANG” to generate some random names.
I decided to limit myself to 64 Aspects at most, the labels of which are for now subject to change. I haven’t decided how many Deities I’m going to add. Probably no more than 16, or maybe 32.
These numbers weren’t picked just because they’re fun programmer numbers. I have a reason for the things I do, sometimes!
In the Game
The end goal for all of this is to calculate some sort of “similarity index” between two given monsters, and set their levels of aggression accordingly. This similarity index is calculated based on the Aspects each monster has in common, with 0.00 being completely hostile, and 1.00 being completely friendly.
The reason the upper limit of the Aspect count is 64 is because I’ll be using an enum, generated at runtime from the game data, whose values are calculated with bitwise operators – 1, bit shifted to the left by the ID value of the Aspect. So, 1 << 1
, then 1 << 2
, etc.
Each monster will contain a bitmask (which is each Aspect enum value compared with a bitwise OR) representing its Aspect flags, and when it sees a new monster, will calculate the similar index with that monster and adjust aggression accordingly.
Why use bitmasks? Well, if a monster can have at least three, but possibly more Aspects, without bitmasks I would have to implement each manually, or manage something like a List<>
or array of Aspect flags. Instead I’m storing a single item with possibly multiple values inside it, so I could have a crazy circumstance like a monster with 50 Aspects but that list would still take no more space than a long
.
In the end the Similarity Index calculation will really be very simple. Monster A has four Aspects and Monster B has two. They share exactly one Aspect. So A->B is .25, and B->A is .5, making Monster B neutral and Monster A aggressive. When struck, Monster B will fight back, but it will not seek out a fight, unlike Monster A. Once struck by another entity, this similarity index immediately drops to 0 (possibly only for a short time).
The lower this similarity index, the more hostile an entity is toward another. If A->B is .25, and A->Player is .33, then Monster A will target Monster B before the Player. But if Player attacks Monster A while those two are fighting, then Monster A changes its course.
Also, a monster’s Wisdom will affect how it prioritizes in scenarios where multiple other entities are attacking it at the same time. A low Wisdom means the monster will continue to target its most recent attacker, no matter what. But a high Wisdom will make the monster target the weakest enemy in its list of targets and strategize to eliminate that one first.
Implementation
The Code
/// <summary> /// Calculates the Similarity Index to determine how aggressive Actor1 should be toward Actor2. /// </summary> /// <param name="actor1">The source IActor</param> /// <param name="actor2">The target IActor</param> /// <returns>A percentage value representing how similar actor2 is to actor1.</returns> public static double SimilarityIndex(IActor actor1, IActor actor2) { var firstAspect = (Aspect.aspect1 | Aspect.aspect2 | Aspect.aspect3); var secondAspect = (Aspect.aspect1 | Aspect.aspect4 | Aspect.aspect5 | Aspect.aspect6); var aspectList = GetSetFlags((long)firstAspect); var aspectCount = GetFlagCount((long)firstAspect); var matchCount = 0; foreach (var aspect in aspectList) { if (secondAspect.HasFlag((Aspect)aspect)) { matchCount++; } } return (double)matchCount / aspectCount; } /// <summary> /// Counts how many flags are set for the given bitmasked value passed in. /// </summary> /// <param name="flags"></param> /// <returns>A count of enabled flags in the bitmask</returns> private static int GetFlagCount(long flags) { int count = 0; while (flags != 0) { flags &= (flags - 1); count++; } return count; } /// <summary> /// Gets a list of bitmask flags that have been set for the value passed int. /// </summary> /// <param name="flags"></param> /// <returns>An IEnumberable<long> of the enabled flags</long></returns> private static IEnumerable<long> GetSetFlags(long flags) { while (flags != 0) { flags &= (flags - 1); yield return flags; } }
Here we have three methods: the “main” method, used to calculate the Similarity Index between two IActor implementations. For this test I ignored those and just made my own sets of Aspects to test against.
I get a list of enabled flags and the flag count, and I check the enabled flags against the flags for the other IActor, and divide the flags in common against the flag count. Very simple. I could have used only one method for getting flags, and done the adding in the calling method to get the flag count, but I like having two methods with distinct purposes, even though they’re very similar, in case later I need to do only one or the other from elsewhere.
Initially I used LINQ to get the set flags, then in my testing watched my memory usage skyrocket when the list grew large. Partially because the LINQ had to sort through the entire Aspects
enum to get the ones that matched, and partially because I think LINQ itself has some non-negligible overhead. The same thing happened before I added the GetFlagCount
method – the divisor was initially aspectList.Count()
, and that also caused a non-trivial bump in memory usage.
The above approach, in my testing, ended up having near-zero memory overhead, according to my completely unscientific and non-rigorous calculations. By that, I mean I just watched the memory usage bar in Visual Studio as I ran the method. The application went from using 7MB to… 7MB. No change. That’s as much detail as I feel like going into this for now.
This approach also lets me keep my foreign key relationship between Deity data and Aspect data — the underlying value for the Aspect is 1 << {AspectId}
, so I get my type safety and some amount of relational integrity.
Now, all this covers why I’m using a bitmask for Aspect flags (at least I hope that’s clear). If you’re curious on how exactly these bitwise operators work, here is a good page describing them in detail.
The Rest
I haven’t made it to the point where any of this affects monster aggression yet. I’ll most likely come back and update this post, or add a new one with the details at that time. For now, it’s enough that I have these details ironed out and I can move onto the actual hard work: creating game data to use in the game. These Aspects aren’t gonna describe themselves.