Mines Malingerer
Posts: 35
Joined: Wednesday, 5th February 2014, 19:07
Monster AI, esp. spells
Once I explain my findings on this behavior, I will make some statements about the gameplay impact and propose changes where appropriate.
Investigation
Here are my findings from some source-delving, starting with spell handling and working up to a monster's turn. I will also summarize below.
1. _find_spell_prospect operates on a 'viable' (see 3) subset of the monster's spell table (found in mon-spell.h). Each spell is basically assigned a range with a size matching its frequency, starting from 0. (So, for example, if a monster had two spells with frequencies 20 and 40, the first would have the range 0-19 and the second would occupy 20-59.) These ranges are checked against a random number, 0-199. If the random number falls into a range, the corresponding spell is chosen. If it lies inside no range, no spell is chosen instead.
2. This is called from _choose_spell_to_cast. This spell can try _find_spell_prospect twice if a beam spell is attempted but fails - but this also makes an assert fail, so it probably doesn't happen often. (Note that on the second try there is a 1/2 chance of trying a self-enchantment instead, which always succeeds if tried. Maybe this is to avoid repeated beam targeting failures?)
3. This is called from handle_mon_spell, which also generates the 'viable' subset of the spell table by calling _find_usable_spells. This is another rabbit-hole, but basically most spells will pass through and hexes may pass through based on a heuristic for success (spell power vs. a guess of the target's MR).
4. This is called from _do_mon_spell.
5. This is called from handle_monster_move, where it has a 1/2 chance of being called first before a special ability (otherwise the ability can attempt to happen first instead and the spell is attempted only if that fails). After this, plenty of other stuff is attempted, such as ranged attacks, wand zapping, and so on. Probabilities on these are outside of scope, but if all these also fail then the monster will try a move/attack (depending on distance to the target).
To summarize, let's take a specific example. Here's the Tormentor's spell table:
The numeric argument is frequency here. Based on a roll of 1d200-1, Pain will be chosen if the result is within [0,27] and Symbol of Torment will be chosen if the result is within [28,57]. Any result beyond that will cause no spell to be chosen (and the monster will do something else).
Basically, on any given turn, the Tormentor has a 28/200 chance of casting Pain, a 29/200 chance of casting Symbol of Torment, and a 143/200 chance of moving or attacking if near (assuming the Tormentor has nothing else to try, like zapping a wand). Note that Pain is considered a hex and so may be evaluated as not viable on any given turn, based on the target's MR. So in turns when Pain is considered unviable, its 28/200 chance would go away and move/attack/etc. would rise to 171/200.
Also important to note is that Tormentors are 'fast' (speed 13), so these 'turns' will be occurring more often than usual. Bad stuff like torment may 'feel' more frequent because of that (but really it's just that everything they do is more frequent).
To ensure that I wasn't entirely off with my interpretations, I also ran some trials on a Tormentor. Here are the results:
- Code:
t = Used Symbol of Torment
x = Moved/Attacked (note that Tormentors get two pain-branded attacks. Both are considered as just one mark, of course).
p = Used Pain
MR:0, Adjacent to enemy
pxxxxxttpxxpxxxtxxxxttxxpxxxxxx
n=31 | p=4,t=5,x=22 => p=12.9%,t=16.1%,x=71%
MR:++, Adjacent to enemy
xxppxpxxxtxxxxxppxxxxtxxtxxxpxxpxxpxxtxxxxxtxxttxxxppxtxx
n=57 | p=10,t=8,x=39 => p=17.5%,t=14%,x=68.4%
MR:++++, Adjacent to enemy
txxxxxxxtxxtxtxxxtttxxxxxxxxxxxtxxxxxxxxtt
n=42 | p=0,t=10,x=32 => p=0%,t=23.8%,x=76.2%
MR++++, Adjacent to enemy (again)
ptxxxxtxxxxxxxxxpxxxpxxxxxxxxxxxxxxxtxtxxxxxxxxxtxxxtpxtttx
n=59 | p=4,t=9,x=46 => p=6.7%,t=15.3%,x=77.9%
MR:++, Not adjacent to enemy (blinked away constantly)
xxpxxxxxpxxxxxptxxxxxxxxxxtxxxxxxtxxtxxxxxxxxxxxxxxxxxxxtptxttxpxxxtxxptxxpxxxpx
n=80 | p=8,t=10,x=62 => p=10%,t=12.5%,x=77.5%
Notes:
- In the high-MR trials, Pain was very rarely casted, meaning it was not chosen as viable very often. In one of the trials, it seemed like Symbol of Torment got more frequent to compensate for Pain missing, but the code doesn't seem to support this behavior and I couldn't replicate it in further trials - so that's probably just an outlier.
- I tested adjacent trials and a non-adjacent trial to see if monsters would use spells less when near in favor of melee attacking. It doesn't seem like this is so (i.e. move in has the same frequency as melee attack, so the frequency of spells isn't affected). I haven't investigated how ranged attacks behave yet though, so there might still be some truth to this (just not for spells).
Gameplay Impact
Basically, monsters will use their spells (and ostensibly abilities) completely at random, usually with just a fixed probability. This means that, in ten turns with a Tormentor, you might see it move or perform its attack ten times in a row, or torment you ten times in a row. In fact, there is nothing stopping every torment-capable monster in the game from tormenting you every single turn they get, and similarly there is nothing stopping them from never tormenting you once. (Note that I am just saying there's nothing stopping this - not that it's likely to happen.)
Compounding this, consider the usual way in which one fights a Tormentor, especially a melee specialist. If you don't have a decent ranged option, you will try to close the distance between the two of you to get rid of them. Assuming the Tormentor is on the edge of your LOS, there are usually 6 spaces between the two of you. If the Tormentor decides to only torment you, you can spend 60 aut moving in and get tormented 7-8 times for it (from which a 400+ HP robustx3 troll with rN+++ would be reduced to ~20 hp!). Alternatively, you can spend 2-3 turns moving in and the tormentor can move in 3-4 times, and you'll find yourself adjacent in 20-30 aut with no damage taken. What I'm getting at here is that an enemy choosing to move in when at range is doubly beneficial since they aren't doing anything damaging and it minimizes the amount of time spent not adjacent to the enemy. Characters who primarily attack at range just get the didn't-attack benefit of the monster moving in, but that's still beneficial.
This same sort of logic applies to spells like Malmutate and Damnation(?), probably Smiting, Lehudib's Crystal Spear, a maybe a Centaur's ranged attack, and basically any other dangerous spell, action, or ability in the game.
Suggestions For Improvement
A lot of things in Crawl are random, so it's not like changing how the AI works for spells/etc. would be the silver bullet that fixes everything. That said, I think its effects can be very prominent, especially with spells like Symbol of Torment or Lehudib's, every cast of which could entirely alter the player's favor to win the battle. When you consider that their frequency of occurrence is completely uncontrolled, you accept that the outcome of combat with these monsters is uncontrolled. The only smart thing to do in these situations is to use a panic button when the random chance gets to be too much, and reset the fight until things go your way. That the player has the ability to do that is another issue, but even if they didn't it this random behavior would still be a problem (since every nth fight with a tormentor could be unavoidably deadly). The problem here really arises due to an obvious power differential between 'move in' and 'you lose half your life' (or other bad stuff depending on the spell).
Anyway, I think a small change to the monster AI in this area would go a long way towards reducing frustration/tedium when fighting monsters wielding threatening spells. I can imagine several different solutions, but I'll stick with just one idea for now.
Proposal: Assign monster spells/abilities/powerful actions an 'energy' cost, like MP but for monster usage. Give monsters 'energy meters' (just internally, not asking to display another cluttery bar) that fill up rapidly over time (effectively whenever the monster moves or does something less dangerous). A spell/ability/powerful action can only be used when the monster has enough energy to use it. Near max energy, monsters can favor expending it more often (to avoid wasting it). Monster energy upon noticing you could start at 1/2 or some randomized number to avoid ability spam upon starting a fight.
Details: Monsters would mix moving with powerful things more often and at a controlled pace. Depending on how it's tweaked, a Tormentor's move list might look something like this:
tpxxxtxxtxpppxxxttxxtxxx
As opposed to this:
xxxxxxtppttxxtpxxxxxxtpxtt
To further illustrate how this energy system would work, here's a few turns for a Tormentor with some made-up energy costs:
Energy max: 100
Energy regen rate: +25/turn
Torment energy cost: 75
Pain energy cost: 40
1: Torment (Energy 100/100 => 25/100 => 50/100) (energy cost, then energy regen happens)
2: Pain (Energy 50/100 => 10/100 => 35/100)
3: Move (Energy 35/100 => 60/100) (Note that the Tormentor must move here...)
4: Move (Energy 60/100 => 85/100)
5: Torment (Energy 85/100 => 10/100 => 35/100) (Note that the Tormentor would favor using a power here to avoid energy overflow)
Well, these numbers seem to make pain/torment more common than it was before, but they can be tweaked to be closer to their original probabilities in frequency. The benefit here is that there is some close-to-guaranteed amount of energy that is expended for any given length of time (depending on how strongly overflow is avoided). Abilities still happen randomly, they just don't run off the rails to avoid the obvious power imbalance between always moving and always tormenting.
I hope that all made sense. Let's discuss.
- For this message the author Veras has received thanks: 2
- VeryAngryFelid, yesno