Demox AI #1

.Factions

So to start things off, let’s go through my primary goals for the AI in Demox.


.DPM

DPM, or “Decisions per Minute” is a simple AI behaviour system I put together for Loot Burn Kill Repeat, which I have continued to build upon for Demox aswell! The name comes from a property field I’ve assigned to the AI character code that determine how many times per minute the AI avatar will calculate a new decision, I guess a less confusing name for it would be “Intelligence”… but… err, yeah!

Worth to note is that despite the AI calculating a decision at regular intervals, the process won’t always end with the AI actually deciding or changing any current actions, since the decision is controlled by multiple aspects concerning the current situation of the AI. I’ve improved the AI’s situational awareness and the DPM system extensively for Demox, compared to what I had in LBKR. Among other things, the AI will check it’s own status,

A check will also be done for the AI’s party members, to determine how many are wounded, escaped combat, have been killed and how scattered the party members are on the battlefield. These are the primary things that are impacting the decision, furthermore, each decision behaviour have tailor-made awareness checks.

AI triggering an alarm, decided with the AI decision behaviour system.

To describe what these “decision behaviours” are, they are basicly instructions for the AI how to determine if the behaviour should be used, aswell as how it is to be executed by the AI. The instructions include simple things like escaping combat, changing target, changing position during combat aswell as more complicated tasks such as triggering traps and alarms, or aiding party members.


.DPBS

When improving the AI awareness there were, as with everything else, some issues to solve, first and foremost- how would I make the AI select the most appropriate action? This morphed my old DPM system into what I now call DPBS, or Decision Point Behaviour System. And yes, again, why not just call it “Intelligence“? … I’m a hopeless case…
When the AI is calculating a decision, all decision behaviours are iterated and compared with each others. They are individually scored depending on the current situation and the best scored behaviour will be selected at the end of the process.

Below is an example from one of the implemented decision behaviours, multiple aspects of the current situation are checked and affects the score of the decision before said score is sent back to the member calculating the AI’s decision.

///<summary>
/// Gets the current score of this decision
///</summary>
///<returns>The score.</returns>
///<param name="agent">Agent Reference.</param>
public override int GetScore( CombatAgent agent ){
    int score = 0;

    if( agent.main.CurHealth > ( agent.main.stats.health * .5f ) || Time.time - agent.alarmAgent.LastEscape < agent.alarmAgent.minEscapeDelay ){
        return ImpossibleScore;   //If the AI is comfortable with it's current health value, or we recently escaped, return a score that prohibits the AI from chosing this decision
    }

    if( !agent.main.canAttack ){
        score += 5;    //If AI is unable to engage in combat, increase score
    }

    if( agent.main.CurHealth < ( agent.main.stats.health * .5f )){
        score++;    //If AI health is less than half of max health value, increase score

        if( agent.main.party != null ){
            for( int i = 0; i < agent.main.party.Length; i++ ){
                if( agent.main.party[i].IsDead ){
                    //For each member of the AI's party who has died, either increase score, or decrease it. (Enemies should be able to be enraged by their friends dying)
                    score += Random.Range( -1, 1 );
                }
            }
        }
    }

    if( agent.main.targetDistance < agent.safeDistance.Sqr() ){
        if( agent.main.CurHealth < ( agent.main.stats.health * .35f )){
            score += Random.Range( 0, 1 );    //If at unsafe distance from target, increase score
        }

        if( agent.main.CurHealth < ( agent.main.stats.health * .2f )){
            score += Random.Range( 0, 2 );    //If health value is critically low, increase score
        }
    }

    if( Time.time - agent.main.LastDmgReceived <= agent.DpmThreshold && agent.main.CurHealth < .4f){
        score += Random.Range( 0, 1 );   //If at relatively low health and recently received damage, increase score
    }

    return score;    //Return calculated score.
}
Decision behaviours available can be customized for each AI character from the editor, since not all characters will have the same options nor priorities.

This improved handling of AI awareness also helped me improve the AI attack system, which has been created in a very similar fashion. To give an example, in LBKR enemies would always attack the player with the most powerful attack that was not in cooldown, they would take no regard to the distance to the player and only checked their maximum attack range to determine if they could not possibly hit the player.

This created a weird behaviour though where AI’s with ranged attacks would fire at the player from point-blank range alot. With DPBS I’ve implemented a ‘Reposition‘-behaviour that will determine if the AI is at the most appropriate distance from the player, taking in considering a whole lot of properties ranging from current situation as described above, aswell as presets of what ranges the AI is most and least comfortable with aswell as check if they have a clear line of sight to their target. Hence, the AI will adapt their position continuously.

This alone didn’t solve the point-blank ranged attack issue described though, since the AI only make use of the Decision Behaviours at certain intervals. But as I structured the attack system similar I, among other things, added attack range limits and preferences for each AI attack to prevent them from beeing used in certain situations. Also all AI with ranged attacks have a minimum of one melee-attack to allow the AI to attack in close quarters if repositioning is not possible.


.Party interaction

All AI in the game belong to a party, the sizes of each party differ from each other, some contain only two characters while another party can contain 10 – 20 party members. The AI party is used primarily for the AI to be able to interact with eachother, as described in the topic above, DBPS, AI party status is checked when making decisions, how dependant a decision behaviour is of the AI’s party is different for each behaviour, also how an action from a decision behaviour is performed can be modified by the status of the AI’s party.

Take the “Escape” behaviour for example, if the AI for some reason become scared and decides to break contact his action can be either to run away and try to hide, or if the AI know about nearby party members who are not in combat, the character will escape to those party members and rally them, then returns to the battle with his fetched friends.

The combat role assigned to the AI also determine how much it will interact with it’s party, the roles I’ve currently implemented are classed as “Assault“, “Ranger“, “Support” and “Berserk“. Assault characters will most commonly only use the party to find AI to help them when they’re in trouble, ranger’s will be more bold when Assaulters are nearby. Support are generally the most party-fixated characters, they will keep a close eye on it’s party members to know if they require their attention, eg. resurrection or healing of a party member, or summoning new minions. Berserkers pay least attention to it’s party, they are focused entirely on destroying their enemy.

Combat roles in action- Support classed Bone Wizard (Staff-carrying guy) focus on resurrecting and healing his party, while the Rangers and Assaulters engage the player
Example of a hybrid-class, the Corpse Warden is a mix of Berserker and support, agressively attacking it’s opponents but if needed it will prioritize resurrecting and aiding it’s party members