A Better Monster AI
Jump to navigation
Jump to search
A Better Monster AI - Joseph Swing [JSwing@wport.com] I've written this to document what I consider a more interesting monster AI. This article mainly concerns how a monster might evaluate its environment. Places where someone has pointed out a suggestion on how to expand or improve the basic model are marked with a (*). Special thanks for the rec.games.roguelike.misc group for their comments and suggestions. Some of RL games use a simple AI. It looks something like: if (ISeeHero) then (MoveToAttackHero) else (DoNothing) Surprisingly, this works pretty well. But let's expand on it to give the monsters some personality, some style. JSwing's guidelines (this is the pompous section - I'm not an expert) 1) Different monsters want different things. 2) Monsters recognize other monsters and objects in the vicinity and interact with them, even when the hero is not around. 3) Each monster acts independently. 4) Groups of monsters need to recognize themselves and be recognized as a group. 5) You can always make it more sophisticated, but we don't need perfect AI. We only need the illusion of some more interesting behavior. The average lifespan of a monster in a RL game is maybe 100 turns, and only 10% of that on the visible screen. Monster behavior is broken down into 5 possible generic types: Desire, Fear, Aggression, Wander, and Misc. Aggression - monster seeks to attack another monster (or hero). Fear - monster seeks to move away from a monster (or hero); this may be expanded to include other stuff. Desire - monster seeks an object or objects, incl gold, food, weapons, etc. Wander - A random function to prevent monsters from getting locked into a pattern. Misc - This will cover special behavior like staying near an altar or going on patrol. Data types used to make decisions: Each monster will have a value to weight the 5 types listed above. If this value is zero then the monster ignores any behavior of that type. The monster may also have one or more specific personality behaviors, such as Hate Elves, or Desire Gold. Each of these will also have a relative strength and should be considered an addition to value the basic behavior type. In order for a monster to have a Desire, it is necessary to have a specific personality behavior to indicate what kind(s) of thing(s) it desires. In order for the fear and aggression behaviors to work, the monster must have a relative combat strength for itself and any monster it sees. (*) Possible Improvement : Monsters will have two relative strengths, a smart view and a dumb view, where a human peasant and a human wizard might appear the same to a dumb monster, but a smart monster can tell the difference. (*) Possible Improvement : Reduce the perceived combat strength of a monster if it's injured. This can be simple. Ex: if (HP<(MaxHP/2)) CombatStr-- A monster will need a list of friendly monsters. Make this as broad as possible. Ex: all orc types are friendly with all other orc types, so an Orc Warrior will know that an Orc Priest is its friend. (*)Possible Improvement: Expand this simple binary (friend/not friend) to a range of values to indicate the strength of the relationship from utter hatred to complete awe. In order for Desire to work, each monster must have a perceived value of goods or loot. Each object needs this as well, but since objects usually have a worth, use that. During a monster turn it will: 1) Look around and evaluate it's surroundings. The monster should look in all 8 directions. For each object or monster it finds, it should perform a calculation to help determine where it wants to go. 2) If it finds a friendly monster, the monster adds (monstr str +1 - distance) to it's own perceived combat strength. Friends will try and work together. 3) If it finds a monster that is not a friend, assume it's an enemy. Here's where we get tricky. The desire to kill something is only based on how strong it thinks it is and how far away the target is. The target's combat strength doesn't matter here. Further more, it is only interested in attacking the nearest enemy in any given direction. I don't expect to be able to walk by one enemy to get to another one. We also need to do this to evaluate groups of monsters correctly. For any given direction, the aggression value is (my str + my buddies str [per step 2]) x (6 - nearest enemy distance) + 2x (basic aggressive value + any special hates). We don't know for certain how many friends we have until we finish looking around. This means that we need to track some intermediate values. Start by tracking the nearest monster distance in each direction. At the end we'll modify this to find the real values. 4) For *each* non-friendly monster, calculate a fear value. This is (target's perceived str) x (6-target's distance) + 2x (any special fears). Since we calculate this for each monster in a particular direction we can figure the value the first time through. This means that groups of monsters are considered more dangerous than individual ones. If there are any monsters (we return a non-zero value) then we add + 2x (my base fear) for the direction. This way your base fear is static for each direction, and does not depend on the number of monsters. The total value should be added to the direction opposite from where we are looking, so we represent fear as a desire to move the other way as opposed to a negative desire to move in this direction. This makes it easier to normalize at the end. 5) For each object or target monster, figure any Desires if applicable. If the monster who is looking around wants gold or equipment, use the perceived ($) value for any target monsters. If they desire food, use a function of the corpse nutrition value. Objects on the ground should be calculated the same way. The total desire for a particulare direction should be the sum of (obj value) + (6-obj distance) + (basic desire) + (particular desire). Note that these values are added, not multiplied. 6) If you see something that would trigger the Misc category, then calculate a value for it. Examples might include staying close to an altar, or coming to chat with a hero, etc. (*)Suggestion from Aidan Ryder : maybe an altar should subtract from the Fear value to indicate that the monster is trying to protect it's holy place 7) Calculate a wander value, which is a random direction with a value of 1 << (basic Wander value) and << 2 if the monster is confused. The basic wander value should be low, but should be incremented if the monster returns to a previous location (waffling) or stands still. This is to avoid getting stuck. If the monster does not move to a previous location or stand still, decrement the wander value to the monster's minimum (from the monster template). Do not alter the wander value if the monster is occupied with a task that takes more than one turn (including sleeping). (*)Suggestion from Aidan Ryder: [When a monster is occupied, for example ] "Eating" ... it takes an amount of time proportional to the weight of the food eating it...and while it is doing this it's "vision range" is reduced to one or two squares...allowing you to sneak up on monsters feeding on their kills. 8) Finally, revisit the aggression values. In each direction we should now have stored a value for the distance to the nearest monster if there is one, else 0. If there is a monster, we should now have our total perceived attack strength (from all our friends) and can convert it to an aggressive behavior value. 9) Add up the values. Normalize to a vector (in X and Y direction, or maybe even Z). This is the direction the monster desires to go. Also calculate which is the strongest motive of the 5, and assign this as to the monster action. This will enable a monster to use a ranged attack if it has one. (*) See notes on path finding below 10) Be sure to calculate all of the monster AI values in a turn before they move. This will keep groups together. Some nice behaviors come out of this: Ex #1:If Bobo the Ogre (relative str 4) has to choose between a close weak monster and a slightly stronger one a little further away, he'll move to mash the weaker one first. #......... # = wall . = space #..3..B.2. 3 = str 3 monster #......... B = Bobo 2 = str 2 monster Here Bobo chases after the 2. (*) Steven Salter argues that it is in the monster's best interest to attack the strongest opponent it thinks it can kill. This would let groups pick off the more dangerous opposition before any insignificant enemies. I disagree. Since most monsters on a given level have about the same combat strength, attacking a weaker opponent is better than getting yourself injured by a stronger one. A lot depends on how you set up your monsters, and how densely populated your levels are. You can decide what's best for you. Ex#2:If Bobo the Ogre is presented with the following: ........ .1...8.. ........ ...B.... ........ He will move directly left, try and spiral around the Str 1 monster, mash it, and run away (assuming the other two are stationary). Ex #3: You can define a cautious scavenger type. If Nimble the RatThief (relative str 1, aggressive 1, fear 4, greed 4) spots a Hero (relative str 4, $ 4), he will try and stay about 3 spaces away, moving further back as the hero encounters other monsters, moving in if the hero gets injured (and weaker). The rat thief 'shadows' the hero, picking up any goodies the hero leaves behind, moving in to kill only if the hero gets weaker. In this example, Nimble wants the player's gold more than he wants to kill the player, so Nimble would rather try and pickpocket over stabbing the player. Weaknesses of this model There's a big problem with 'out of sight, out of mind'. Our monsters don't remember where they want to go from one turn to the next. If a monster loses sight of its objective, then if forgets completely that the objective exists. #.......... # = wall . = space #.B........ B = Bobo $ = Gold #......#### #........$. Bobo travels east and loses sight of the gold. One solution: This problem will mostly occur with Desires. Other monsters will move around and may pop back into view of their own accord, or else move further away and out of interest. One solution is to remember, from turn to turn, the strongest Desire and direction. If the calculated desire for that direction is zero (the object disappeared), add the remebered desire back in. Obstacle avoidance. If a monster encounters an obstacle that it can see around, like a pit, it doesn't know how to walk around it. Obstacles only become a problem if they are exactly in one of the 8 directions, but the monster will often move towards this condition. By separating the direction the monster wants to travel from the actual getting there I've also made some more work if we want to incorporate any pathfinding. #..... # = wall . = space #.B.X$ B = Bobo X = pit #..... $ = Gold Bobo has a problem. Aidan Ryder points out that pits or other traps should only affect Aggression and Desire, not always Fear: #........... # = wall #.......X... X = trap #...D.B.X... B = Bobo The Unlucky #.......X... D = Greater HellWyrm #........... Bobo [should] now run for it as fast as possible, ignoring the traps because he is so scared of the wyrm ... if he tried to go around, the wyrm may well catch him. A solution: My first solution was a hack. If the obstacle was directly in oneof the 8 directions and you wanted to be on the other side of it, split the attractive vectors (Aggression and Desire) to point 3/4 at (desired direction + 45 degrees) and 1/4 at (desired direction -45 degrees). If the obstacle is crossable, but undesireable, leave the fears alone. This would let you walk around small obstacles and still account for other impulses that might direct you in one direction over another. It's not a good solution since monsters get stuck on large obstacles and still get lost fairly easily. But it's easy to implement with the existing model. A good solution: We should combine the behavior impulse with a pathfinding algorithm where it makes sense. We will need an additional data member for an (x,y) location. When we evaluate the monsters desires (steps 1-10 above), if the monster's strongest behavior is either Aggression or Desire, we store the target location as well as the direction. If the strongest behavior is Fear or Wander, we store a null value. For Misc behaviors, it will depend on the specific behavior. When we're done with evaluating all of the squares, if our (x,y) is not null, then we can employ a pathfinding algorithm to get to the target. You only need to evaluate paths in the desired direction, or maybe the desired direction and the two adjacent directions. Remember to include a factor for other behavior we have already evaluated (if you want to travel east and you've got a strong fear from the north, weight a southern route more desireable. You dont need to re-evaluate the thing up north that is generating the fear). If the (x,y) is null, then revert to the simpler method, since it better handles behaviors that are not target specific. Consider this text public domain. Comments are always welcome, JSwing@wport.com