Hey all! Sorry about the delay on this post. I'm gonna be pushing myself to write a bit more this month so hopefully you enjoy that. So last article I went over the general how and why of what makes good AI behavior, here I'd like to cover the system that's driving the combine's new decision making process.
By default, Half-Life 2's ai is essentially done in a finite state machine system using basic if else statements. Before you go running out the door from sheer confusion, you can basically think of if else in very simple terms. If certain conditions are true, do a given thing, else if other conditions are true, do something else. Basic example would be something like an imp in Doom. If we can throw a fireball, do it, otherwise move towards our enemy. Sounds simple, but here's something not mentioned there: They also need to see their enemy. So that's even more conditions to add in this if else tree. For basic actions this isn't so bad but when you've got something like a soldier who can reload, run to cover, shoot at his enemy, lay down suppressing fire, run from grenades etc. it gets so much harder to actually manage. Trying to make tweaks to HL2's AI with this system became a nightmare, so I worked to create my own system.
The first part of my solution to this was to essentially make NPC actions their own class outside of any given npc. Storing the conditions needed for them to activate on their end rather than having to describe them in a giant if else tree on the NPC immediately made adding and tweaking behaviors easier. So instead of needing to look through something like this

I could simply add a list of behaviors I wanted in the order I wanted them like
Run from danger
Reload an empty gun
Shoot Gun
Take Cover From Damage
So in this simple example I basically want my soldiers to prioritize running from danger and having a gun with ammo in it before trying shoot or take cover. I don't need to awkwardly go through all the conditions needed because I know they're already defined on the action's end. So this allowed me to start constructing my soldiers with a lot more nuanced understanding of how they worked, since I could at any time break their behavior down much more quickly without having to scroll through potentially hundreds of lines of code (That example shown above is literally close to 200 lines.) This approach by itself however had its own limitations. While understanding ai behavior and the priority of actions they took became much easier, it was starting to actually get harder to give distinct npcs their own reasons for using simple universal actions.
For example, consider reloading. Reloading a gun seems like a fairly simple action, but WHY an npc would be reloading their gun could cover a whole range of options. Should a soldier have full ammo or close to it before they move closer to their enemy? Should they reload when they have a visible enemy? Are they ever allowed to reload a gun that's just above half way empty? These nuances could be broken down into distinct actions, reload empty, reload idle, hide and reload etc. but there are still problems with this approach if you're working with multiple distinct npcs. For instance the Suppressor in Inhuman wants to reload his gun at different times than most soldier types do, and he doesn't really want to be hiding if he doesn't need to be. I needed a way to add some nuance on the npc's side, which is where utility scoring comes in.
Utility scoring is a way of essentially assigning values to different possible actions an npc can take, dynamically changing as their conditions change over time. To accomplish this in Inhuman, I have npcs assign "considerations", various conditions that can be added to an action with a score it tests and returns such as enemy distance, health, ammo count etc. These considerations can add or subtract to a final score on the action, which I keep simple with a range between -1.0 and 1.0. Any action with a score above 0 gets considered when an npc is trying to do something.
This allows me to keep npc actions relatively simple while allowing for all kinds of specific reasons a given npc might or might not use them. With reloading as an example, soldiers can add the enemy distance, last time enemy seen, last time damaged, and ammo count considerations. So if their ammo is below max and some of these other conditions are met, it's possible for them to reload without taking cover first. For instance if they don't see you, its been a while, and you're kinda far, they'll reload their gun even if it's 75% full, but if they can see you they'll opt to keep firing their gun or possibly even hide depending on the situation.
While this is still just as deterministic as standard if else behavior trees, the ease of organizing all of this allows for far more nuance and much more rapid iteration. If an npc is doing something I didn't quite expect it's far easier for me to simply find the given action and look at the considerations for it, and also see what actions weren't able to be used before their current selection.
Utility scoring has become a popular way to work with complex npc behavior and I'd recommend this article on the subject if you're curious about more nerdy specifics: http://www.gameaipro.com/GameAIPro/GameAIPro_Chapter09_An_Introduction_to_Utility_Theory.pdf
The next article I plan on making here will cover more of my general balancing thoughts. I think I'm gonna start trying to do more edited videos for my public youtube, with behind the scenes break downs here. Feel free to add any feedback or questions here!