RE: Modifying the X2 AI (X2_def_**** scripts)

Over the past several days I’ve been looking for ways to modify the way in which custom AI is implemented. Currently the standard convention is to create a custom OnSpawn script with a custom OnUserDefined script or to directly modify the other AI scripts (OnPerception, OnDamaged, etc.) to tweak the BioWare AI, leading to MANY scripts.

While this approach is effective, it ignores the fact that the X2 AI, with the introduction of Pre and Post-Spawn events, allows a Builder to place ALL this code into ONE OnUserDefined event - by moving all the AI switches from nw_c2_default9 to the EVENT_POST_SPAWN conditional in your custom OnUserDefined event.

/*
    Pstemarie: 12-23-2019

    Generic Creature OnUserDefined Event Script

    To use:

        1. Set the creature to use the X2 Monster AI (scripts with the prefix
           x2_def_****).

        2. Set this script as the creature's OnUserDefined event handler.

        3. Set an INT variable named "X2_USERDEFINED_ONSPAWN_EVENTS" with a
           value of "2" on the creature.

        4. Uncomment any desired special behavior under the PRE and POST-SPAWN
           conditionals

    Leeched from nw_c2_default9, nw_c2_herbivore, and x2_def_userdef - all (c) 2002-2004 Bioware
*/

const int EVENT_USER_DEFINED_PRESPAWN = 1510;
const int EVENT_USER_DEFINED_POSTSPAWN = 1511;

#include "x0_i0_anims"

void main()
{
    int nEvent = GetUserDefinedEventNumber();

    if (nEvent == EVENT_USER_DEFINED_PRESPAWN)
    {

    }
    else if (nEvent == EVENT_USER_DEFINED_POSTSPAWN)
    {
        //----------------------------------------------------------------------
        // OPTIONAL BEHAVIORS (Comment In or Out to Activate )

        //SetSpawnInCondition(NW_FLAG_SPECIAL_CONVERSATION);

        //SetSpawnInCondition(NW_FLAG_SPECIAL_COMBAT_CONVERSATION);
            //* This causes the creature to say a special greeting in their conversation file
            //* upon Perceiving the player. Attach the [NW_D2_GenCheck.nss] script to the desired
            //* greeting in order to designate it. As the creature is actually saying this to
            //* himself, don't attach any player responses to the greeting.

        //SetSpawnInCondition(NW_FLAG_SHOUT_ATTACK_MY_TARGET);
            //* This will set the listening pattern on the NPC to attack when allies call

        //SetSpawnInCondition(NW_FLAG_STEALTH);
            //* If the NPC has stealth and they are a rogue go into stealth mode

        //SetSpawnInCondition(NW_FLAG_SEARCH);
            //* If the NPC has Search go into Search Mode

        //SetSpawnInCondition(NW_FLAG_SET_WARNINGS);
            //* This will set the NPC to give a warning to non-enemies before attacking

        //SetSpawnInCondition(NW_FLAG_APPEAR_SPAWN_IN_ANIMATION);
            //* If this is set, the NPC will appear using the "EffectAppear" animation instead of fading in.

        //SetSpawnInCondition(NW_FLAG_AMBIENT_ANIMATIONS);
            //* This will play Ambient Animations until the NPC sees an enemy or is cleared.
            //* NOTE: These animations will play automatically for Encounter Creatures.

        //SetSpawnInCondition(NW_FLAG_IMMOBILE_AMBIENT_ANIMATIONS);
            //* This will play Ambient Animations until the NPC sees an enemy or is cleared.
            //* NOTE: NPCs using this form of ambient animations will not move to other NPCs.

        //----------------------------------------------------------------------
        // ANIMATION SETTINGS
        /*
            These are extra conditions you can put on creatures using ambient animations - either
            NW_FLAG_AMBIENT_ANIMATIONS or NW_FLAG_IMMOBILE_AMBIENT_ANIMATIONS.
        */

        //SetAnimationCondition(NW_ANIM_FLAG_IS_CIVILIZED);
            //* Civilized creatures interact with placeables in their area that have the tag "NW_INTERACTIVE"
            //* and "talk" to each other.
            //*
            //* Humanoid races are civilized by default, so only set this flag for monster races that you want to
            //* behave the same way.

        //SetAnimationCondition(NW_ANIM_FLAG_CONSTANT);
            //* If this flag is set, this creature will constantly be acting. Otherwise, creatures will only start
            //* performing their ambient animations when they first perceive a player, and they will stop when the
            //* player moves away.

        //SetAnimationCondition(NW_ANIM_FLAG_CHATTER);
            //* Civilized creatures with this flag set will randomly use a few voicechats. It's a good idea to avoid
            //* putting this on multiple creatures using the same voiceset.

        //SetAnimationCondition(NW_ANIM_FLAG_IS_MOBILE_CLOSE_RANGE);
            //* Creatures with _immobile_ ambient animations can have this flag set to make them mobile in close
            //* range. They will never leave their immediate area, but will move around in it, frequently returning
            //* to their starting point.
            //*
            //* NOTE: Creatures spawned inside interior areas that contain a waypoint with one of the tags "NW_HOME",
            //* "NW_TAVERN", and "NW_SHOP" will automatically have this condition set.

        //----------------------------------------------------------------------
        // SPECIAL BEHAVIOR SECTION
        /*
            The following section outlines the various special behaviors that can be placed on a creature.
            To activate one of the special behaviors:

                1.  Comment in SetBehaviorState(NW_FLAG_BEHAVIOR_SPECIAL);
                2.  Comment in ONE other special behavior setting (ONLY ONE).
        */

        //SetBehaviorState(NW_FLAG_BEHAVIOR_SPECIAL);

        //* OPTIONAL SPECIAL BEHAVIORS - ONLY ONE OF THESE SHOULD BE SET AT A TIME

        //SetSpawnInCondition(NW_FLAG_ESCAPE_RETURN);
            //* Flee to a waypoint and return a short time later.

        //SetSpawnInCondition(NW_FLAG_ESCAPE_LEAVE);
            //* Flee to a waypoint and do not return.

        //SetSpawnInCondition(NW_FLAG_TELEPORT_LEAVE);
            //* Teleport to a waypoint and do not return.

        //SetSpawnInCondition(NW_FLAG_TELEPORT_RETURN);
            //* Teleport to a waypoint and return a short time later.

        //SetBehaviorState(NW_FLAG_BEHAVIOR_OMNIVORE);
            //* Will only attack those that close within 5m and are not friends, Rangers or Druids.

        //SetBehaviorState(NW_FLAG_BEHAVIOR_HERBIVORE);
            //* Will flee those that close within 7m if they are not friends, Rangers or Druids.

        //SetCombatCondition(X0_COMBAT_FLAG_RANGED);
            //* Ranged Attacker: Will attempt to stay at ranged distance from their target.

        //SetCombatCondition(X0_COMBAT_FLAG_DEFENSIVE);
            //* Defensive Attacker: Will use defensive combat feats and parry

        //SetCombatCondition(X0_COMBAT_FLAG_AMBUSHER);
            //* Ambusher: Will go stealthy/invisible and attack, then run away and try to go stealthy
            //* again before attacking anew.

        //SetCombatCondition(X0_COMBAT_FLAG_COWARDLY);
            // * Cowardly: Will attempt to flee attackers.


        //----------------------------------------------------------------------
        // CUSTOM USER DEFINED EVENTS
        /*
            The following settings will allow the user to fire one of the blank user defined events in
            the NW_D2_DefaultD.  Like the On Spawn In script this script is meant to be customized by
            the end user to allow for unique behaviors. The user defined events are 1001 - 1007, and
            1510 and 1511.
        */
            //SetSpawnInCondition(NW_FLAG_HEARTBEAT_EVENT);        //OPTIONAL BEHAVIOR - Fire User Defined Event 1001
            //SetSpawnInCondition(NW_FLAG_PERCIEVE_EVENT);         //OPTIONAL BEHAVIOR - Fire User Defined Event 1002
            //SetSpawnInCondition(NW_FLAG_ATTACK_EVENT);           //OPTIONAL BEHAVIOR - Fire User Defined Event 1005
            //SetSpawnInCondition(NW_FLAG_DAMAGED_EVENT);          //OPTIONAL BEHAVIOR - Fire User Defined Event 1006
            //SetSpawnInCondition(NW_FLAG_DISTURBED_EVENT);        //OPTIONAL BEHAVIOR - Fire User Defined Event 1008
            //SetSpawnInCondition(NW_FLAG_END_COMBAT_ROUND_EVENT); //OPTIONAL BEHAVIOR - Fire User Defined Event 1003
            //SetSpawnInCondition(NW_FLAG_ON_DIALOGUE_EVENT);      //OPTIONAL BEHAVIOR - Fire User Defined Event 1004
            //SetSpawnInCondition(NW_FLAG_DEATH_EVENT);            //OPTIONAL BEHAVIOR - Fire User Defined Event 1007

    }
    else if(nEvent == EVENT_HEARTBEAT ) //HEARTBEAT
    {

    }
    else if(nEvent == EVENT_PERCEIVE) // PERCEIVE
    {

    }
    else if(nEvent == EVENT_END_COMBAT_ROUND) // END OF COMBAT
    {

    }
    else if(nEvent == EVENT_DIALOGUE) // ON DIALOGUE
    {

    }
    else if(nEvent == EVENT_ATTACKED) // ATTACKED
    {

    }
    else if(nEvent == EVENT_DAMAGED) // DAMAGED
    {

    }
    else if(nEvent == 1007) // DEATH  - do not use for critical code, does not fire reliably all the time
    {

    }
    else if(nEvent == EVENT_DISTURBED) // DISTURBED
    {

    }
}

While I haven’t playtested every single switch, I have determined that the following switches DO work when set in the EVENT_POST_SPAWN:

All Special Behaviors
Ambient Animations (and Animation Settings)
All Custom User Defined Events (Death, Heartbeat, etc.)

Just thought I’d share a little Christmas gift for the Community.

2 Likes

Interesting.

My own approach is to have one spawn script, with all the custom code conditional on creature variables.

However, I devised this on the assumption that Bioware wouldn’t change anything after 1.69, which isn’t necessarily true in the Beamdog era.

@Proleric Yes, I used to use the same approach, but experienced some issues with variables getting dumped.

What’s nice about this is that it’s Bioware’s original coding. Asfaik, Beamdog hasn’t touched this behavior yet and probably won’t. Georg Z coded all kinds of functionality into XP2 that is horribly documented, if at all. Decoding this functionality - and its expanded (and possibly unintended) usage, although tedious at times, is quite rewarding in the long run.

Did they change any other Diamond scripts so far? I’m kinda of out the loop.

I have yet another approach to this…


EDIT: @Proleric, since you were involved with the horse system (the docs says so - correct me if I’m wrong), do you know how tightly it is coupled with the current AI? I’m wondering how much tweaking is permitted before everything crashes hard it ceases to work correctly.

Yeah, those events are very useful. I’ve been using them for years :slight_smile:

Same, here. I just never tried using the Post-Spawn to toggle all those switches on that people usually toggle either with variables or a custom OnSpawn/OnUserDefined combo. Makes coding a lot easier when you can just drop ALL the custom stuff for a creature into a single script.

I don’t think Beamdog has changed ANY of the scripts except those that they HAD to change due to their own modifications to the game.

Well, I was only involved with beta testing & the Lexicon, but I got to know the scripts.

The horse system was essentially a bolt-on. The Bioware scripters were working under some constraints, such as not touching the engine or the core combat scripts.

So, arguably, it’s loosely coupled to the AI.

However, the code is fragile. What it’s trying to do is complicated - maybe overly - with nasty timing issues and impacts on many events / spells etc. As a tester, I often needed the devs to explain expected behaviour, it was that convoluted.

Beta testing was unduly prolonged because fixing one thing always seemed to break others. In the end, a pragmatic line was drawn, leaving several loose ends.

So it’s a can of worms. For my own modules, I didn’t tweak the official scripts much, finding it easier to code my own scripts around that core.

1 Like

Horse system has absolutely no support inside vanilla AI. If you are making npcs you make them mounted already and then they behave exactly the same as any other monster. There are no exceptions for AI when the npc is mounted nor any additional features.

This method has at least two problems:

  1. you cannot modify vanilla events and their behavior, so when this code is executed those scripts already told npc to do something and you have to cancel it
  2. these events doesn’t really modify internal AI itself but a support scripts that execute it

So it depends on what you want to do exactly. The simplest AI changes can be done with this - like target switching at various conditions. But if you want to code certain order how should npc cast spells then this will not work.

Check https://neverwintervault.org/article/tutorial/tutorial-vanilla-community-patch-ai-depth section Making custom AI for more informations.

Not sure this is entirely true, as I just coded the spell casting reactions for a goblin shaman using the OnUserDefined events:

const int EVENT_USER_DEFINED_PRESPAWN = 1510;
const int EVENT_USER_DEFINED_POSTSPAWN = 1511;

#include "x0_i0_anims"

void main()
{
    int nEvent = GetUserDefinedEventNumber();

    if (nEvent == EVENT_USER_DEFINED_PRESPAWN)
    {

    }
    else if (nEvent == EVENT_USER_DEFINED_POSTSPAWN)
    {
        //----------------------------------------------------------------------
        // OPTIONAL BEHAVIORS (Comment In or Out to Activate)
        /*
            Except as noted below, you may have any number of these flags set on a creature:

                Conversation - choose either NW_FLAG_SPECIAL_CONVERSATION or
                               NW_FLAG_SPECIAL_COMBAT_CONVERSATION

                Animations   - choose either NW_FLAG_AMBIENT_ANIMATIONS or
                               NW_FLAG_IMMOBILE_AMBIENT_ANIMATIONS
        */

        //SetSpawnInCondition(NW_FLAG_SPECIAL_CONVERSATION);
        //SetSpawnInCondition(NW_FLAG_SPECIAL_COMBAT_CONVERSATION);

        //SetSpawnInCondition(NW_FLAG_SHOUT_ATTACK_MY_TARGET);
        //SetSpawnInCondition(NW_FLAG_STEALTH);
        //SetSpawnInCondition(NW_FLAG_SEARCH);
        //SetSpawnInCondition(NW_FLAG_SET_WARNINGS);

        //SetSpawnInCondition(NW_FLAG_AMBIENT_ANIMATIONS); //* OR
        //SetSpawnInCondition(NW_FLAG_IMMOBILE_AMBIENT_ANIMATIONS);

        //SetSpawnInCondition(NW_FLAG_APPEAR_SPAWN_IN_ANIMATION); //* May need to move to EVENT_PRE_SPAWN

        //----------------------------------------------------------------------
        // ANIMATION SETTINGS
        /*
            These are extra conditions you can put on creatures using ambient animations - either
            NW_FLAG_AMBIENT_ANIMATIONS or NW_FLAG_IMMOBILE_AMBIENT_ANIMATIONS.

            NOTE: ONLY ONE OF THESE SHOULD BE SET AT A TIME.
        */

        //SetAnimationCondition(NW_ANIM_FLAG_IS_CIVILIZED);
        //SetAnimationCondition(NW_ANIM_FLAG_CONSTANT);
        //SetAnimationCondition(NW_ANIM_FLAG_CHATTER);
        //SetAnimationCondition(NW_ANIM_FLAG_IS_MOBILE_CLOSE_RANGE);

        //----------------------------------------------------------------------
        // SPECIAL BEHAVIOR SECTION
        /*
            The following section outlines the various special behaviors that can be placed on a creature.
            To activate one of the special behaviors:

                1.  Comment in SetBehaviorState(NW_FLAG_BEHAVIOR_SPECIAL);
                2.  Comment in ONE other special behavior setting (ONLY ONE).
        */

        //* THIS LINE MUST BE UNCOMMENTED IF USING ANY OF THE SPECIAL BEHAVIORS BELOW
        //SetBehaviorState(NW_FLAG_BEHAVIOR_SPECIAL);

        //* OPTIONAL SPECIAL BEHAVIORS - ONLY ONE OF THESE SHOULD BE SET AT A TIME

        //SetSpawnInCondition(NW_FLAG_ESCAPE_RETURN);
        //SetSpawnInCondition(NW_FLAG_ESCAPE_LEAVE);
        //SetSpawnInCondition(NW_FLAG_TELEPORT_LEAVE);
        //SetSpawnInCondition(NW_FLAG_TELEPORT_RETURN);

        //SetBehaviorState(NW_FLAG_BEHAVIOR_OMNIVORE);
        //SetBehaviorState(NW_FLAG_BEHAVIOR_HERBIVORE);

        //SetCombatCondition(X0_COMBAT_FLAG_RANGED);
        //SetCombatCondition(X0_COMBAT_FLAG_DEFENSIVE);
        //SetCombatCondition(X0_COMBAT_FLAG_AMBUSHER);
        //SetCombatCondition(X0_COMBAT_FLAG_COWARDLY);

        //----------------------------------------------------------------------
        // CUSTOM USER DEFINED EVENTS
        /*
            The following settings will allow the user to fire one of the blank user defined events in
            the NW_D2_DefaultD.  Like the On Spawn In script this script is meant to be customized by
            the end user to allow for unique behaviors. The user defined events are 1001 - 1007.

            NOTE: A creature may have ANY number of UserDefined events
        */
            //SetSpawnInCondition(NW_FLAG_HEARTBEAT_EVENT);
            SetSpawnInCondition(NW_FLAG_PERCIEVE_EVENT);
            SetSpawnInCondition(NW_FLAG_ATTACK_EVENT);
            SetSpawnInCondition(NW_FLAG_DAMAGED_EVENT);
            //SetSpawnInCondition(NW_FLAG_DISTURBED_EVENT);
            //SetSpawnInCondition(NW_FLAG_END_COMBAT_ROUND_EVENT);
            //SetSpawnInCondition(NW_FLAG_ON_DIALOGUE_EVENT);
            //SetSpawnInCondition(NW_FLAG_DEATH_EVENT);

    }
    else if(nEvent == EVENT_HEARTBEAT )
    {

    }
    else if(nEvent == EVENT_PERCEIVE)
    {
        if (GetIsEnemy(GetLastPerceived()) )
        {
            ClearAllActions();
            ActionCastSpellAtObject(SPELL_DOOM, GetLastPerceived());
        }
    }
    else if(nEvent == EVENT_END_COMBAT_ROUND)
    {

    }
    else if(nEvent == EVENT_DIALOGUE)
    {

    }
    else if(nEvent == EVENT_ATTACKED)
    {
        ClearAllActions();
        ActionCastSpellAtObject(SPELL_AID, OBJECT_SELF);
    }
    else if(nEvent == EVENT_DAMAGED)
    {
        ClearAllActions();
        if (GetCurrentHitPoints() < (GetMaxHitPoints() /2) )
        {
            ActionCastSpellAtObject(SPELL_SANCTUARY, OBJECT_SELF);
        }
        else ActionCastSpellAtObject(SPELL_CURE_MINOR_WOUNDS, OBJECT_SELF);
    }
    else if(nEvent == 1007) // DEATH  - do not use for critical code, does not fire reliably all the time
    {

    }
    else if(nEvent == EVENT_DISTURBED)
    {

    }
}

This will have issues with canceling ongoing spells. OnDamaged event specifically runs extremely often which means that when the npc gets damaged frequently it will do nothing as it will try to cast spell but cancel it before it finishes. (also Attacked runs a milisecond before OnDamaged so this means that when the attack against npc is successfull npc tries to cast Aid then cancels it and then Sanctuary or Cure wounds.)

This specific issue could be fixed inside this script by checking the current npc action. Still, even if this will work you have very limited options and lot of gaps that can cause weird behavior. I am not entirely sure if vanilla OnDamaged runs when being damaged by a spell, but even if it does there are other ways how to break such npc and cause him to get stuck or fall into vanilla AI.

EDIT: Not 100% sure now, I am out of NWN scripting for a few months now, but IIRC canceling actions outside of the internal AI can completely mess out the internal AI. When the internal AI is casting a spell it stores local variable to TRUE and adds action spell cast and then setting that variable to FALSE. If you cancel actions when this is happening the variable will remain TRUE meaning the internal AI breaks and do nothing. Of course, with the code like this, the creature will still perform the spells in those events, so it might not be such a big deal but all and all, this is probably the wrong way how to do what you want (On the another way, internal AI doesn’t have events which means that coding to cast specific spell when npc gets damaged is basically impossible… I suppose this would need completely different approach…)

I just hobbled that together to see if spell sequence could be controlled from it, which they apparently can. The question now is, just how much control do you really have? Which of course leads to more scripting to see if I can indeed break the vanilla AI for the creature.

This should be fun…

EDIT - Tweak to code to catch if already casting a spell

    else if(nEvent == EVENT_ATTACKED)
    {
        if (GetCurrentAction() != ACTION_CASTSPELL )
        {
            ClearAllActions();
            ActionCastSpellAtObject(SPELL_AID, OBJECT_SELF);
        }
    }
    else if(nEvent == EVENT_DAMAGED)
    {
        if (GetCurrentAction() != ACTION_CASTSPELL )
        {
            ClearAllActions();
            if (GetCurrentHitPoints() < (GetMaxHitPoints() /2) )
            {
                ActionCastSpellAtObject(SPELL_SANCTUARY, OBJECT_SELF);
            }
            else
            {
                ActionCastSpellAtObject(SPELL_CURE_MINOR_WOUNDS, OBJECT_SELF);
            }
        }
    }

Exhibited behavior of goblin shaman:

  1. Shaman perceives PC and casts doom on PC
  2. PC summon familiar
  3. Shaman casts doom at PC
  4. PC fires crossbow at shaman and misses
  5. Shaman casts Aid
  6. Familiar attacks shaman in melee and hits
  7. Shaman casts Cure Minor Wounds on self
  8. PC fires again and hits, familiar attacks and misses
  9. Shaman casts Cure Minor Wounds on self
  10. PC fires and hits, familiar attacks and hits
  11. Shaman casts sanctuary.

Exhibited behavior of goblin shaman with vanilla AI, X2_L_BEH_MAGIC set to 100, and X2_L_BEH_OFFENSE set to 50:

  1. Goblin shaman stands there
  2. PC casts summon familiar
  3. GS casts doom at PC
  4. Familiar uses fear attack on GS
  5. GS casts Aid on itself, spell disrupted by opportunity attack
  6. Familiar melee hit
  7. GS melee attack misses
  8. Familiar melee hit
  9. GS casts Cure Minor Wounds on self
  10. Familiar melee attack hits
  11. GS melee attack misses
  12. Familiar melee attack misses
  13. GS melee attack misses
  14. Familiar melee attack misses
  15. GS melee attack misses
  16. Familiar melee attack hits
  17. GS casts Cure Minor Wounds on Self
  18. Familiar melee attack hits
  19. GS casts Cure Minor Wounds on Self
  20. Familiar melee attack hits
  21. GS melee attack misses
  22. Familiar kills GS

As you can see, much different results with the vanilla AI running things. I then did a second fight without changing any parameters in the script.

On the second battle with the vanilla AI, the PC (Aluvian Darkstar) used her crossbow to attack each round with the familiar. That fight lasted 5 rounds and the GS never tried to cast a spell after casting doom in the first round.

I will only respond to this. It seems you can manage to get satisfying behavior using this method so I see no point trying to deter you from it. Especially when the other methods doesn’t have as much “event control”. As long as you are happy with it…

But there are at least two reasons why the goblin shaman didn’t cast spells.

  1. you are using vanilla vanilla AI, ie. without my unofficial patch which fixes hundreds of AI bugs which most of them results to npc not casting anything
  2. npc doesn’t have combat casting and your familiar got into her face which then means that she won’t cast spells but attack in melee, X2_L_BEH_MAGIC 100 should simulate combat casting but I am not 100% sure about it now and it might not work everytime as well

given that the same npc casted with familiar in his face I would bet on 1)

btw, try this to test your custom AI:

  1. approach the goblin shaman in stealth mode with high skills, then attack him
  2. cast a disabling spell on him
  3. cast a damaging spell on him
  4. approach and fight him normally for a round or two, then go into stealth (HIPS needed to break his actions), wait a 20 seconds, then cancel stealth but don’t attack him

these are possible actions, that can break your AI, I can think of

Yeah, there’s a hundred + ways I’m sure it could be broken, most of which will no doubt be used by PCs that play through the module. Thus, its nothing you need to “deter me from” - I’m only using it for SIMPLE ai overrides, nothing as complex as what you’re suggesting.

I was more fascinated with the fact that all those built-in switches can be toggled from EVENT_POST_SPAWN, which means - for SIMPLE ai tweaks - you only need one script.

As for you observations on what occurred: correct on both accounts. I didn’t want to make the module dependent on CPP, leaving that installation up to players.

The custom AI solution is actually one script as well. You don’t even need to modify vanilla OnSpawn event script or assign custom one to your npcs.

example script from my PW Arkhalia:

#include "x2_inc_switches"
#include "nw_i0_generic"

void main()
{
    object oSelf = OBJECT_SELF;
    SetCreatureOverrideAIScriptFinished(OBJECT_SELF);

    if(__InCombatRound())
        return;

    ClearAllActions();
    object oEnemy = bkAcquireTarget();

    if (GetIsObjectValid(oEnemy))
    {
        __TurnCombatRoundOn(TRUE);

        if(TalentPersistentAbilities()) // * Will put up things like Auras quickly
        {

        }
        else if(TalentHealingSelf())
        {

        }
        else if(GetHasSpell(SPELL_EPIC_RUIN))
        {
            object oTarget = oEnemy;
            object oTargetPrevious = GetLocalObject(OBJECT_SELF,"TARGET_PREV");
            if(GetIsObjectValid(oTargetPrevious))
            {
                oTarget = ChooseNewTarget(oTargetPrevious);
                if(!GetIsObjectValid(oTarget)) oTarget = oEnemy;
            }
            SetLocalObject(OBJECT_SELF,"TARGET_PREV",oTarget);
            ActionCastSpellAtObject(SPELL_EPIC_RUIN,oTarget);
        }
        else
        {
            ActionAttack(oEnemy);
        }

        __TurnCombatRoundOn(FALSE);
    }
}

This simple AI script makes the npc to switch target of the spellcasting each time the spell is cast, it will otherwise spam the Greater Ruin till it runs out of uses. (Note: might require cpp 1.72 to compile as I think the function ChooseNewTarget is not in vanilla AI)

Regarding building with CPP, it should not be an obstacle for builders. I mean, unless you add patch172.hak into module it will never require CPP. Besides this, you just need to avoid using extra CPP features or if you do, copy scripts that provides them from CPP files into your module so it will work regardless of player having CPP installed or not. This is main feature CPP was designed in mind, unlike other hak-based packages, but builders still don’t understand it…

3 Likes