Author Topic: AI Behavior Priority  (Read 427 times)

Legacy_Greyjoy

  • Newbie
  • *
  • Posts: 18
  • Karma: +0/-0
AI Behavior Priority
« on: July 23, 2011, 06:05:59 pm »


               Hey NWN community!

I'm struggling with the AI for my enemy NPCs and was hoping one of you lot could shed some light into this for me.

Basically I have a wall with archers that fires at incoming enemies.  The enemies are to destroy the wall's gate, which works somewhat.  My problem here is the fact that once one of the enemies gets attacked from the archers they will interrupt their priority to attack the gate and instead focus on the archers, which by the way are on top of the wall so the enemies can't really reach them.

I've been trying to modify various scripts on the enemy NPCs such as OnPhysicalAttacked, OnDamaged etc so they will ignore their attacker and instead focus on the gate since that is a priority for them.  But still they insist on trying to attack the archers on the wall which is driving me nuts.

Any ideas here?

Thanks in advance!
               
               

               
            

Legacy_Failed.Bard

  • Hero Member
  • *****
  • Posts: 1409
  • Karma: +0/-0
AI Behavior Priority
« Reply #1 on: July 23, 2011, 07:39:01 pm »


               It's possible it's the calls from the OnConversation that are still doing it.  They might not be responding to themselves being attacked, but the silent shouts from the others that are, and are responding to that.
               
               

               
            

Legacy_Lightfoot8

  • Hero Member
  • *****
  • Posts: 4797
  • Karma: +0/-0
AI Behavior Priority
« Reply #2 on: July 23, 2011, 07:54:50 pm »


               I do not see a reason here to mess with any or the default events.   All you need to do is set up a special AI for your archers. This is easy to do. 

First you need to set a Local string on your archers, you can do this either in the toolset or  in there onspawn script. 

From the OnSpawn Script you would add something like this.  
SetLocalString(OBJECT_SELF,"X2_SPECIAL_COMBAT_AI_SCRIPT", "Name  of AI script" );

In the toolset you would just add the X2_SPECIAL_COMBAT_AI_SCRIPT string name and give it the value of the AI script name.

After that you just need to write the script for your special AI.  I see it looking something like this.

void main()
{
   object oWall =GetNearestObjectByTag("Tag Of Wall");
   if  (oWall == OBJECT_INVALID) return;
   ActionAttack(oWall);
   SetLocalInt(OBJECT_SELF,"X2_SPECIAL_COMBAT_AI_SCRIPT_OK",TRUE);
}


Clean and simple.   
The only line in it the need a little more explanation is the:

SetLocalInt(OBJECT_SELF,"X2_SPECIAL_COMBAT_AI_SCRIPT_OK",TRUE);

When this local is set to true in the script it prevents the rest of the AI from running.   so there will be no fear of your archers changing targets to something else. 

Once your wall is destroyed, the script will no longer set that local to true and the regular AI will be able to take back over.
               
               

               
            

Legacy_FunkySwerve

  • Hero Member
  • *****
  • Posts: 2325
  • Karma: +0/-0
AI Behavior Priority
« Reply #3 on: July 23, 2011, 08:00:07 pm »


               We altered targeting behavior by modifying the target selection functions called by DetermineCombatRound. We included this script in x0_inc_generic to do so:


object bkAcquireTarget() {
    object oLastTarget = GetAttackTarget();
    if (GetIsObjectValid(oLastTarget) &&
        !GetIsDead(oLastTarget) &&
        !GetHasSpellEffect(SPELL_ETHEREALNESS, oLastTarget)) {

        object oPriority = GetLocalObject(OBJECT_SELF, "TargetPriorityOverride");
        if (GetIsObjectValid(oPriority)) {
            return oPriority;

        } else if (GetResRef(oLastTarget) == "x2_s_bblade") {//skip bbod if boss or smart enough, unless caster with Mord
            if (GetHasSpell(SPELL_MORDENKAINENS_DISJUNCTION, OBJECT_SELF))
                return oLastTarget;

            if (GetLocalInt(OBJECT_SELF, "IgnoreBBoD") || (GetAbilityScore(OBJECT_SELF, ABILITY_INTELLIGENCE) > Random(100)))
                oLastTarget = ChooseNewTarget(oLastTarget);

        } else if (GetHasSpellEffect(HGEFFECT_TAUNT)) {

            object oEffectSource = GetLocalObject(OBJECT_SELF, "TauntSource");
            if (!GetIsObjectValid(oEffectSource))//safety check
                return oLastTarget;

            int nType = GetLocalInt(OBJECT_SELF, "TauntSkill");
            if (nType == SKILL_BLUFF) {
            /*
                Bluffed creatures switch away from bluffer 75% of the time, and never choose them as
                their new target unless there are none others available.
            */
                if ((oEffectSource == oLastTarget) && Random(4))
                    oLastTarget = ChooseNewTarget(oEffectSource);
                else
                    return oLastTarget;

            } else if (nType == SKILL_TAUNT) {
            /*
                Taunted creatures switch to attacking the taunter 75% of the time
            */
                if ((oEffectSource != oLastTarget) && Random(4))
                    return oEffectSource;
                else
                    return oLastTarget;
            } else
                return oLastTarget;

        } else
            return oLastTarget;

    } else
        oLastTarget = ChooseNewTarget();

    if (!GetIsObjectValid(oLastTarget) &&  //non-henchmen swap to ranged weapons
        !GetIsObjectValid(GetMaster(OBJECT_SELF)))
        ActionEquipMostDamagingRanged();

    return oLastTarget;  // valid or not, return it
}

int GetConcealment(object oTarget) {
    int nEff, nConceal = 0;
    effect eEff;

    for (eEff = GetFirstEffect(oTarget);
         GetIsEffectValid(eEff);
         eEff = GetNextEffect(oTarget)) {

        if (GetEffectType(eEff) == EFFECT_TYPE_CONCEALMENT) {
            if ((nEff = GetEffectInteger(eEff, 0)) > nConceal)
                nConceal = nEff;
        }
    }

    return nConceal;
}

int GetIsSquishyCaster (object oCreature) {
    if (GetLevelByclass(class_TYPE_CLERIC, oCreature) > 10)
        return TRUE;

    if (GetLevelByclass(class_TYPE_SORCERER, oCreature) > 10)
        return TRUE;

    if (GetLevelByclass(class_TYPE_WIZARD, oCreature) > 10)
        return TRUE;

    if ((GetLevelByclass(class_TYPE_DRUID, oCreature) > 10) &&
        (GetLevelByclass(class_TYPE_SHIFTER, oCreature) < 5))
        return TRUE;

    if (GetAbilityScore(oCreature, ABILITY_CHARISMA, TRUE) >= 30 &&
        (GetLevelByclass(class_TYPE_BARD, oCreature) > 10))
        return TRUE;

    return FALSE;
}

void DeleteAIVariables(object oCreature, int nTargetsConceal) {
    DeleteLocalObject(oCreature, "GEN_AI_TARGET_NEAREST");//clear fallback
    DeleteLocalObject(oCreature, "GEN_AI_TARGET_HATED");//clear hated
    if (nTargetsConceal)
        DeleteLocalObject(oCreature, "GEN_AI_TARGET_HIGH_CONCEAL");//clear highest conceal
}

object ChooseNewTarget(object oTargetToExclude = OBJECT_INVALID) {
    object oSource = OBJECT_SELF, oTargetToExclude2;
    object oEffectSource = GetLocalObject(oSource, "TauntSource");
    int nType = GetLocalInt(oSource, "TauntSkill");

    if (GetIsObjectValid(oEffectSource)) {
        if ((nType == SKILL_TAUNT) && !GetIsDead(oEffectSource) && !GetHasSpellEffect(SPELL_ETHEREALNESS, oEffectSource) && Random(4))
            return oEffectSource;

        else if ((nType == SKILL_BLUFF) && Random(4)) {
            if (oTargetToExclude == OBJECT_INVALID) //oTargetToExclude can be either a bluffer or a bbod
                oTargetToExclude = oEffectSource;
            else if (oTargetToExclude != oEffectSource)//if excluded is not bluffer, it's bbod
                oTargetToExclude2 = oEffectSource;//makes bluffer TargetToExclude2, after bbod
        }
    }

    int nNth = 1, nConceal, nHighConceal, nCrit;
    float fRange = GetLocalFloat(GetModule(), "AIRange");
    if (fRange < 1.0)
        fRange = 10.0;

    if (GetLocalInt(oSource, "X2_L_BEH_MAGIC") || GetItemIsRangedWeapon(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oSource)))
        fRange += 20.0;

    int nTargetsCasters = GetLocalInt(oSource, "AttackCasters");

    int nTargetsConceal = (GetHasFeat(FEAT_SKILL_FOCUS_LISTEN, oSource) || GetHasFeat(FEAT_EPIC_SKILL_FOCUS_LISTEN, oSource));

    int nTargetsCritVuln = GetHasFeat(GetWeaponDevastatingCriticalFeat(GetBaseItemType(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oSource))), oSource);

    //retrieve hated class, set if not
    int nHatedclass = GetLocalInt(oSource, "NW_L_BEHAVIOUR1") - 1;
    if (nHatedclass == -1) {
        nHatedclass = Random(10);
        SetLocalInt(oSource, "NW_L_BEHAVIOUR1", nHatedclass+1);
    }

    object oTarget;
    string sRes;

    while (GetIsObjectValid(oTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY,
         oSource, nNth++,
         CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN,
         CREATURE_TYPE_DOES_NOT_HAVE_SPELL_EFFECT, SPELL_ETHEREALNESS))) {

        if (GetIsDead(oTarget)) {
            nHatedclass = Random(10);
            SetLocalInt(oSource, "NW_L_BEHAVIOUR1", nHatedclass+1);//pick a new hated class when any target is dying
            continue;
        }

        if (oTarget == oTargetToExclude || oTarget == oTargetToExclude2)//ignore these targets unless there are none others
            continue;

        sRes = GetResRef(oTarget);
        if (sRes == "x2_s_bblade") {//skip bbod if boss or smart enough, unless caster with Mord
            if (GetHasSpell(SPELL_MORDENKAINENS_DISJUNCTION, oSource)) {
                DeleteAIVariables(oSource, nTargetsConceal);
                return oTarget;
            }
            if (GetLocalInt(oSource, "IgnoreBBoD") || (GetAbilityScore(oSource, ABILITY_INTELLIGENCE) > Random(100)))
                continue;
        }

        if (!GetIsObjectValid(GetLocalObject(oSource, "GEN_AI_TARGET_NEAREST")))
            SetLocalObject(oSource, "GEN_AI_TARGET_NEAREST", oTarget);//store nearest enemy for fallback

        if ((GetLevelByclass(nHatedclass, oTarget) > 0) &&
            (!GetIsObjectValid(GetLocalObject(oSource, "GEN_AI_TARGET_HATED"))))
            SetLocalObject(oSource, "GEN_AI_TARGET_HATED", oTarget);//store nearest hated enemy


        if (nTargetsCritVuln &&
            !GetIsImmune(oTarget, IMMUNITY_TYPE_CRITICAL_HIT)) {
            if (GetDistanceBetween(oSource, oTarget) <= fRange) {
                DeleteAIVariables(oSource, nTargetsConceal);
                return oTarget;//ret nearest non-crit enemy if in range
            }
            break;//end loop - if nearest crit vuln out of range, just going to attack default target
        }

        if (nTargetsCasters && GetIsSquishyCaster(oTarget)) {
            DeleteAIVariables(oSource, nTargetsConceal);
            return oTarget;//return nearest caster if they AttackCasters (ignoring range)
        }

        if (nTargetsConceal) {//find the highest conceal target in range
            nConceal = GetConcealment(oTarget);
            if (nConceal > nHighConceal) {
                if (GetDistanceBetween(oSource, oTarget) <= fRange) {
                    nHighConceal = nConceal;
                    SetLocalObject(oSource, "GEN_AI_TARGET_HIGH_CONCEAL", oTarget);//store highest conceal enemy in range
                } else break;//stop if out of range
            }
        }
    }

    if (nTargetsConceal) {
        oTarget = GetLocalObject(oSource, "GEN_AI_TARGET_HIGH_CONCEAL");
        if (GetIsObjectValid(oTarget) && Random(4)) {//target highest conceal 75% of the time
            DeleteAIVariables(oSource, nTargetsConceal);
            return oTarget;
        }
    }

    oTarget = GetLocalObject(oSource, "GEN_AI_TARGET_HATED");
    if (GetIsObjectValid(oTarget) &&  (GetDistanceBetween(oSource, oTarget) <= fRange)) {
        DeleteAIVariables(oSource, nTargetsConceal);
        return oTarget;
    }

    oTarget = GetLocalObject(oSource, "GEN_AI_TARGET_NEAREST");
    DeleteAIVariables(oSource, nTargetsConceal);

    if (!GetIsObjectValid(oTarget))
        oTarget = oTargetToExclude2;//target of last resort

    if (!GetIsObjectValid(oTarget))
        oTarget = oTargetToExclude;//target of last resort- means bluffers get targeted before bbods

    return oTarget;//ok if invalid
}




As you can see, it allows us to fine-tune target selection. There may be an easier way that's equally effective, but I'm unaware of it if so.
[EDIT] Use Lightfoot's way, it's far simpler than making deep ai edits.


Funky
               
               

               


                     Modifié par FunkySwerve, 23 juillet 2011 - 07:01 .
                     
                  


            

Legacy_Greyjoy

  • Newbie
  • *
  • Posts: 18
  • Karma: +0/-0
AI Behavior Priority
« Reply #4 on: July 24, 2011, 01:50:58 am »


               Lightfoot8 that's a brilliant idea.  I can just force the enemies to attack the gate and disable the rest of the AI.  Once the gate is destroyed their regular/default AI kicks in and all is good.

Thanks a bunch!
               
               

               
            

Legacy_Calgacus

  • Full Member
  • ***
  • Posts: 195
  • Karma: +0/-0
AI Behavior Priority
« Reply #5 on: October 08, 2011, 03:06:16 am »


               I used LightFoot8's script as the starting point for a Wand of Targets script which lets you tell a henchman who to attack.   See the Utilities link in my sig for the item.
               
               

               
            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
AI Behavior Priority
« Reply #6 on: October 08, 2011, 03:53:01 am »


               

FunkySwerve wrote...

...

I really like the BBotD code, would you mind if I take that idea for my project?