Skip to content

Better AI for a spellcasting enemy

Str8TStr8T Member Posts: 18
I am trying to make a challenging wizard boss but I feel like the default AI doesn't fight very well. The most noticeable issue is that the wizard seems to be able to cast only one offensive spell per round, despite being under effect of haste. Sometimes the wizard begin to stand idle for several rounds or stop attacking, even when it's in close proximity of a player. Is there are a way to fix this behavior and make the enemy more aggressive and reactive? Maybe there is some better script set for a spellcasting enemy I could use?

Comments

  • sarevok57sarevok57 Member Posts: 5,975
    what level is this wizard?

    do you want some spells to be cast on this wizard before the player fights it?

    what spells do you want this wizard to cast?
  • Str8TStr8T Member Posts: 18
    edited June 2021
    I've just done some testing with 40 lvl wizard created with Levelup Wizard. I gave him boots of speed and only a fireball spell. I measured intervals between spell casts and when I stand near him, he casts fireball in 8 seconds intervals, but when go in combat with him he casts every 3 seconds, so as frequently as he should. What I'm trying to achieve is have this wizard cast his offensive spells as he would be in combat, even if just standing and doing nothing to him. I tried SetAILevel(OBJECT_SELF, AI_LEVEL_VERY_HIGH) in his OnSpawn script but doesn't seem to help.

    I assume that OnDamaged event triggers AI, so it casts 2 spells per round and when I'm just standing it's only once per heartbeat. The solution which comes to mind is to make the AI schedule more actions per heartbeat when it's in combat, but how should I do it correctly? Or maybe I can just somehow make the AI "tick" faster?
  • sarevok57sarevok57 Member Posts: 5,975
    edited June 2021
    what you probably need to do is set a variable instead, for my spell casters thats how i do it and as soon as they see someone hostile they go to work and the variable i use is;

    X2_SPECIAL_COMBAT_AI_SCRIPT

    but with this variable you need to have an attached script with it for it to work , actually here is the step by step way to do it;

    for a creature go to the "advanced" tab and then at the bottom middle there is going to be a variable button press that

    another screen is going to open up, where is says "Name" you are going to copy paste the above X2... script i have above, for "Type" you are going to set it to "string" and then for "Value" you are going to put in the script name that is going to run this AI, so for example, if it is going to be a wizard, your wizard script could be called "wizscript001"

    once that is all done, press the "Add" button then OK

    now, we need to make that wizard script that we set for that variable ( "wizscript001" )

    so time to go to "Tools" and script editor and make our wizard script

    now here are some things that you are going to copy paste for that script;

    #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))
    {
    // ** Store HOw Difficult the combat is for this round
    int nDiff = GetCombatDifficulty();
    SetLocalInt(OBJECT_SELF, "NW_L_COMBATDIFF", nDiff);
    __TurnCombatRoundOn(TRUE);

    if(TalentPersistentAbilities()) // * Will put up things like Auras quickly
    {
    __TurnCombatRoundOn(FALSE);
    return;
    }
    else if(TalentHealingSelf())
    {
    __TurnCombatRoundOn(FALSE);
    return;
    }
    else if(TalentHeal())
    {
    __TurnCombatRoundOn(FALSE);
    return;
    }



    // then next you are going to need to add the spells you want the caster to cast, now make sure the caster actually has these spells or else they will just skip them and not cast them, and there are 2 types of spells that can be cast; 1 being spells already cast upon the caster when you meet up with it and 2 spells the caster will actually cast

    // here is the code for spells to be cast on the actual caster when you see the caster

    ActionCastSpellAtObject(SPELL_XXX, OBJECT_SELF, METAMAGIC_ANY, FALSE, 0, PROJECTILE_PATH_TYPE_DEFAULT, TRUE);

    // make sure to replace the XXX for the actual spell you want cast, and remember the caster actually has to have it memorized for it to work

    // to make a caster cast a spell use the following code

    ActionCastSpellAtObject(SPELL_XXX, oEnemy);

    // again, replacing the XXX with the desired offensive

    // also, if you want to make a spell caster cast spells on themselves you would use this line

    ActionCastSpellAtObject(SPELL_XXX, OBJECT_SELF);

    // again replacing XXX with the desired spell
    // and once all your spells are set you will end this script with;

    // Attack the PC.
    ActionAttack(oEnemy);
    }
    }


    so, with that all being said, lets set an example script for a level 7 wizard with 16 INT, which gives 1 level 4 spell, 3 level 3 spells, 4 level 2 spells, 5 level 1 spells and 4 level 0 spells, we will give him 1 spell that auto casts when he sees an enemy ( mage armor ) and then have him cast haste and ghostly visage on himself and then unload the rest of his spells, so his script would look like this;




    #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))
    {
    // ** Store HOw Difficult the combat is for this round
    int nDiff = GetCombatDifficulty();
    SetLocalInt(OBJECT_SELF, "NW_L_COMBATDIFF", nDiff);
    __TurnCombatRoundOn(TRUE);

    if(TalentPersistentAbilities()) // * Will put up things like Auras quickly
    {
    __TurnCombatRoundOn(FALSE);
    return;
    }
    else if(TalentHealingSelf())
    {
    __TurnCombatRoundOn(FALSE);
    return;
    }
    else if(TalentHeal())
    {
    __TurnCombatRoundOn(FALSE);
    return;
    }
    ActionCastSpellAtObject(SPELL_MAGE_ARMOR, OBJECT_SELF, METAMAGIC_ANY, FALSE, 0, PROJECTILE_PATH_TYPE_DEFAULT, TRUE);

    ActionCastSpellAtObject(SPELL_HASTE, OBJECT_SELF);
    ActionCastSpellAtObject(SPELL_GHOSTLY_VISAGE, OBJECT_SELF);

    ActionCastSpellAtObject(SPELL_WALL_OF_FIRE, oEnemy);
    ActionCastSpellAtObject(SPELL_SLOW, oEnemy);
    ActionCastSpellAtObject(SPELL_LIGHTNING_BOLT, oEnemy);
    ActionCastSpellAtObject(SPELL_BLINDNESS_AND_DEAFNESS, oEnemy);
    ActionCastSpellAtObject(SPELL_MELFS_ACID_ARROW, oEnemy);
    ActionCastSpellAtObject(SPELL_COMBUST, oEnemy);
    ActionCastSpellAtObject(SPELL_RAY_OF_ENFEEBLEMENT, oEnemy);
    ActionCastSpellAtObject(SPELL_NEGATIVE_ENERGY_RAY, oEnemy);
    ActionCastSpellAtObject(SPELL_BURNING_HANDS, oEnemy);
    ActionCastSpellAtObject(SPELL_MAGIC_MISSILE, oEnemy);
    ActionCastSpellAtObject(SPELL_RAY_OF_FROST, oEnemy);
    ActionCastSpellAtObject(SPELL_RAY_OF_FROST, oEnemy);
    ActionCastSpellAtObject(SPELL_RAY_OF_FROST, oEnemy);
    ActionCastSpellAtObject(SPELL_RAY_OF_FROST, oEnemy);

    // Attack the PC.
    ActionAttack(oEnemy);
    }
    }




    so with that above script, as long as the caster had those spells, mage armor would auto cast on him, and then he will cast haste and ghostly visage on himself and then unload everything else on any enemy he sees. Now for some weird reason I have found that if you make a caster cast a spells on himself and then go offensive and back to defensive it will mess the script up, so its best to make a caster cast any spell on themself at the beginning of their turns and once they are prepped up enough unload

    also i don't know how to make it so they won't cast certain spells at certain times ( aka; if undead sighted do not cast negative energy ray, or if player goes invisible then cast see invisibility ect... ) with this above scripting it may not be 100% SCS style, but it should get you started in the right direction, and even with some good spell choices you can make any spell caster a force to reckon with, with a decent spell selection and some feats ( spell focusing ect )

    oh, and another note, i haven't found a way to make metamagic feats work either, so unfortunately that is out of the question as well

    hope this at least gets you in the right direction, here is my default template i use for spell casters ( so you can just copy paste in the future, i save this in the normal "notepad" for this and directly copy paste it from there )

    //*X2_SPECIAL_COMBAT_AI_SCRIPT

    //*then set the variable to string and enter the name of the script there*

    //*actual script starts with # below*

    #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))
    {
    // ** Store HOw Difficult the combat is for this round
    int nDiff = GetCombatDifficulty();
    SetLocalInt(OBJECT_SELF, "NW_L_COMBATDIFF", nDiff);
    __TurnCombatRoundOn(TRUE);

    if(TalentPersistentAbilities()) // * Will put up things like Auras quickly
    {
    __TurnCombatRoundOn(FALSE);
    return;
    }
    else if(TalentHealingSelf())
    {
    __TurnCombatRoundOn(FALSE);
    return;
    }
    else if(TalentHeal())
    {
    __TurnCombatRoundOn(FALSE);
    return;
    }

    ActionCastSpellAtObject(SPELL_XXX, OBJECT_SELF, METAMAGIC_ANY, FALSE, 0, PROJECTILE_PATH_TYPE_DEFAULT, TRUE);
    // must actually have spell memorized and this is used for spells that are auto casted on casters

    ActionCastSpellAtObject(SPELL_XXX, OBJECT_SELF);

    ActionCastSpellAtObject(SPELL_XXX, oEnemy);

    // Attack the PC.
    ActionAttack(oEnemy);
    }
    }


  • Str8TStr8T Member Posts: 18
    Thanks for the reply but that's not what I'm looking for. I wanted to avoid custom scripting AI and hardcoding spellcasts.

    I've done some more testing and my conclusion is that the default AI is thrash :) It gets bugged too easily. After research I decided that better AI scripts is what I should look for and luckily there are some. I tried Jasperres AI and it's looking great so far. I think it's all I need for now.
    sarevok57
  • sarevok57sarevok57 Member Posts: 5,975
    yeah i hear ya, scripting especially in NWN's case can be a big pain in the butt
Sign In or Register to comment.