Skip to content

how do you properly script spell casters?

sarevok57sarevok57 Member Posts: 6,002
*sigh*

im having a hell of a time with this, sometimes scripts work, sometimes they dont, sometimes they just do whatever they damn well please

i can NEVER consistently make it so spell casters 100% cast spells correctly,

usually they will cast spells ( when they actually feel like doing so ) but sometimes they will just start their script over again ( so for example if they cast invisibility, mage armor, color spray, burning hands, ray of frost, ray of frost in this order ) and once they get to the burning hands part, my character will hit them in combat and sometimes they will just start over again and go back to invisibility and go down the list once more

and sometimes they just get stuck in an infinite loop where they will cast invisibility then haste then mage armor, and do nothing and if i attack then they just continue to cast those 3 spells over and over again forever with no end

does someone out there, know how to PROPERLY script casters so they will ONLY cast their spells when they are supposed to, AND do it when the PC is around and not have to wait to be attacked before they do something

i can't believe the frustration i am having over this nonsense, the toolset or this engine just in general is super unintuitive when it comes to scripting spell casters

Comments

  • ShadooowShadooow Member Posts: 402
    perhaps you should tell us how are you doing this

    coding a specific order of spellcasting is something I never heard of - some randomization is usually welcome because especially in PW environment, same order is easily exploitable

    so most builders don't care and they just put the spells to npc they want and leave it be

    I recommend to read: https://neverwintervault.org/article/tutorial/tutorial-vanilla-community-patch-ai-depth

    It is not covering what you want to do, but it is still very informative tutorial for anyone dealing with vanilla NPC AI.

    If you are experienced with scripting I can send you npc-specific AI using vanilla AI routines that can be easily modified to do what you want to do. But if you aren't scripter it will be useless for you. This however gave me idea to create a dynamic npc-specific AI that would allow to do this just with variables...
  • sarevok57sarevok57 Member Posts: 6,002
    so i noticed this is happening to lizard folk casters, the "cleric" one is working as intended ( as far as i can tell ) but the wizard ones are not working what so ever

    so here is the exact script of one of the lizard folk;

    void main()
    {
    object oPC = GetLastPerceived();

    if(!(GetPlotFlag(OBJECT_SELF)))
    {
    if(GetIsPC(oPC))
    {


    ActionCastSpellAtObject(SPELL_INVISIBILITY, OBJECT_SELF, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_HASTE, OBJECT_SELF, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_SLOW, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_FLAME_ARROW, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_FLAME_ARROW, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_MELFS_ACID_ARROW, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_MELFS_ACID_ARROW, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_GEDLEES_ELECTRIC_LOOP, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_GEDLEES_ELECTRIC_LOOP, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_COLOR_SPRAY, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_NEGATIVE_ENERGY_RAY, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_BURNING_HANDS, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_BURNING_HANDS, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_BURNING_HANDS, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_DAZE, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_ACID_SPLASH, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_ACID_SPLASH, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_ACID_SPLASH, oPC, METAMAGIC_ANY, TRUE);

    ActionAttack(oPC);
    }
    }
    }

    now, i used to also have ( after casting haste ) ghostly visage and mage armor, but when i had that, then all this guy did was cast; invisibility, haste, ghostly visage - and sometimes mage armor - and then just repeat that over and over and over again

    so now when i got rid of ghostly visage and mage armor, this guy will cast invisibility, haste - and sometimes cast slow on the PC - and then just cast invisibility and haste over and over and over again

    now this is incredibly dumb because here is my kobold arcane caster that has no problems;

    void main()
    {
    object oPC = GetLastPerceived();

    if(!(GetPlotFlag(OBJECT_SELF)))
    {
    if(GetIsPC(oPC))
    {


    ActionCastSpellAtObject(SPELL_INVISIBILITY, OBJECT_SELF, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_MAGE_ARMOR, OBJECT_SELF, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_BLINDNESS_AND_DEAFNESS, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_MELFS_ACID_ARROW, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_MELFS_ACID_ARROW, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_RAY_OF_ENFEEBLEMENT, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_COLOR_SPRAY, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_MAGIC_MISSILE, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_MAGIC_MISSILE, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_RAY_OF_FROST, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_RAY_OF_FROST, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_RAY_OF_FROST, oPC, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_RAY_OF_FROST, oPC, METAMAGIC_ANY, TRUE);

    ActionAttack(oPC);
    }
    }
    }

    and yet with this, i never have problems with the kobold caster ever

    the ONLY difference between the lizard folk and the kobold in these 2 scripts is that the lizard folk has a different spell list THAT IS IT, and yet the lizard folk REFUSES to act right while the kobold is doing as it should

    so what gives?
  • ShadooowShadooow Member Posts: 402
    this will never work because of the way AI works

    you need to make a npc-specific AI script which should be explained in the tutorial I linked, and that script looks like this:

    (npc specific AI created for cleric npc on 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))
        {
            // ** 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;
            }
    
            if(GetHasSpell(SPELL_IMPLOSION) && d4() == 1)
            {
                ActionCastSpellAtObject(SPELL_IMPLOSION,oEnemy);
            }
            else if(GetHasSpell(SPELL_DESTRUCTION) && !GetIsImmune(oEnemy,IMMUNITY_TYPE_DEATH,OBJECT_SELF))
            {
                ActionCastSpellAtObject(SPELL_DESTRUCTION,oEnemy);
            }
            else if(GetHasSpell(SPELL_HARM) && !GetHasSpellEffect(SPELL_SHADOW_SHIELD,oEnemy) && !GetHasSpellEffect(SPELL_NEGATIVE_ENERGY_PROTECTION,oEnemy) && GetCurrentHitPoints(oEnemy) > GetMaxHitPoints(oEnemy)/2 && GetRacialType(oEnemy) != RACIAL_TYPE_UNDEAD)
            {
                ActionCastSpellAtObject(SPELL_HARM,oEnemy);
            }
            else if(GetHasSpell(SPELL_ENERGY_DRAIN) && !GetIsImmune(oEnemy,IMMUNITY_TYPE_NEGATIVE_LEVEL,OBJECT_SELF))
            {
                ActionCastSpellAtObject(SPELL_ENERGY_DRAIN,oEnemy);
            }
            else if(GetHasSpell(SPELL_WORD_OF_FAITH) && d4()==1)
            {
                ActionCastSpellAtObject(SPELL_WORD_OF_FAITH,oEnemy);
            }
            else if(GetHasSpell(SPELL_GREATER_DISPELLING) && (GetHasSpellEffect(SPELL_SPELL_MANTLE,oEnemy) || GetHasSpellEffect(SPELL_LESSER_SPELL_MANTLE,oEnemy) || GetHasSpellEffect(SPELL_GREATER_SPELL_MANTLE,oEnemy) || GetHasSpellEffect(SPELL_SPELL_RESISTANCE,oEnemy)))
            {
                ActionCastSpellAtObject(SPELL_GREATER_DISPELLING,oEnemy);
            }
            else if(GetHasSpell(SPELL_BLADE_BARRIER) && d4()==1)
            {
                ActionCastSpellAtObject(SPELL_BLADE_BARRIER,oEnemy);
            }
            else if(GetHasSpell(SPELL_FIRE_STORM) || GetHasSpell(SPELL_FLAME_STRIKE))
            {
                switch(d2())
                {
                case 1:
                 if(GetHasSpell(SPELL_FIRE_STORM))
                 {
                 ActionCastSpellAtLocation(SPELL_FIRE_STORM,GetLocation(OBJECT_SELF));
                 break;
                 }
                case 2:
                 if(GetHasSpell(SPELL_FLAME_STRIKE))
                 {
                 ActionCastSpellAtObject(SPELL_FLAME_STRIKE,oEnemy);
                 break;
                 }
                }
    
            }
            else//no offensive spells
            {
                if(GetHasSpell(SPELL_DIVINE_POWER))
                {
                    ActionCastSpellAtObject(SPELL_DIVINE_POWER,OBJECT_SELF);
                }
                else if(GetHasSpell(SPELL_DIVINE_FAVOR))
                {
                    ActionCastSpellAtObject(SPELL_DIVINE_FAVOR,OBJECT_SELF);
                }
                else
                {
                    ActionAttack(oEnemy);
                }
            }
        __TurnCombatRoundOn(FALSE);
        }
        else
        {
            object oPC = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR,PLAYER_CHAR_IS_PC,OBJECT_SELF,1,CREATURE_TYPE_IS_ALIVE,TRUE,CREATURE_TYPE_PERCEPTION,PERCEPTION_SEEN);
            if(GetIsObjectValid(oPC))
            {
            __TurnCombatRoundOn(TRUE);
            if(TalentHealingSelf())
            {
                __TurnCombatRoundOn(FALSE);
                return;
            }
            else if(TalentHeal())
            {
                __TurnCombatRoundOn(FALSE);
                return;
            }
             if(GetHasSpell(SPELL_IMPLOSION))
             {
                 ActionCastSpellAtLocation(SPELL_IMPLOSION,GetLocation(oPC));
                 __TurnCombatRoundOn(FALSE);
                 return;
             }
             else if(GetHasSpell(SPELL_BLADE_BARRIER))
             {
                 ActionCastSpellAtLocation(SPELL_BLADE_BARRIER,GetLocation(oPC));
                 __TurnCombatRoundOn(FALSE);
                 return;
             }
             else if(GetHasSpell(SPELL_FIRE_STORM))
             {
                 ActionCastSpellAtLocation(SPELL_FIRE_STORM,GetLocation(OBJECT_SELF));
                 __TurnCombatRoundOn(FALSE);
                 return;
             }
             else if(GetHasSpell(SPELL_FLAME_STRIKE))
             {
                 ActionCastSpellAtLocation(SPELL_FLAME_STRIKE,GetLocation(oPC));
                 __TurnCombatRoundOn(FALSE);
                 return;
             }
             else if(GetHasSpell(SPELL_GREATER_DISPELLING))
             {
                 ActionCastSpellAtLocation(SPELL_GREATER_DISPELLING,GetLocation(oPC));
                 __TurnCombatRoundOn(FALSE);
                 return;
             }
             else if(GetHasSpell(SPELL_WORD_OF_FAITH))
             {
                 ActionCastSpellAtLocation(SPELL_WORD_OF_FAITH,GetLocation(oPC));
                 __TurnCombatRoundOn(FALSE);
                 return;
             }
            __TurnCombatRoundOn(FALSE);
            }
        }
    }
    

  • sarevok57sarevok57 Member Posts: 6,002
    i read over the tutorial and still dont understand what needs to be done

    where do i need to edit the scripts? which ones do i edit? which ones do i need to change? how do i need to change them? and it doesnt tell me at all how to make spell caster scripts ( unless there is a link somewhere? )

    i have basically zero knowledge on how C++ works, and what little knowledge i have, couldn't even get me outside a paper bag if i were trapped in one
  • TerrorbleTerrorble Member Posts: 179
    That's a great article ShadoOow (I wish I'd seen it a long time ago)



    Maybe this explanation of what the article is saying will get you a step closer.


    To set a variable on a creature (or anything else) you go to its Advanced tab and click the variables button to bring up the variables dialog box.

    For example, putting the following variable in the creature's variable list:

    X2_SPECIAL_COMBAT_AI_SCRIPT | string | ai_liz_wiz

    will cause the default NWN AI to execute the code in ai_liz_wiz as part of the AI routines.

    So, if you make a script saved as ai_liz_wiz and add what you want it to do, then it should do that. You don't have to put ai_liz_wiz anywhere other than just create it. The by setting X2_SPECIAL_COMBAT_AI_SCRIPT | string | ai_liz_wiz on the creature, it will find ai_liz_wiz at the appropriate time and use it.



    Giving a creature this variable
    X2_L_BEH_MAGIC | INT | 100
    will make it use its magic over other abilities. (the article explains this really well)
  • sarevok57sarevok57 Member Posts: 6,002
    okay, now we are getting close, thanks Terrorble for making it a bit more clear, so i just gave my NPC the default x2 scripts for everything and went into variables and added in the new script i made ( trying my best to copy paste to my desired effect from Shadooow

    but now, i have a new problem;

    when my NPC runs out of spells, he will go to attack for half a second and then just go back to spell casting the entire list again

    first, i on purposely made sure the NPC had no spells memorized thinking that was the problem, but it is not

    so far the script i have, works just as intended now all i need is that when the NPC is done casting the spells in my script i want the NPC to go and attack and use whatever default AI ( because it does have cure minor wounds memorized so if it wants to cast that afterwards i dont care )

    so here is my script i have now;

    #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_SANCTUARY, OBJECT_SELF, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_SHIELD_OF_FAITH, OBJECT_SELF, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_BLESS, OBJECT_SELF, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_SUMMON_CREATURE_II, OBJECT_SELF, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_SOUND_BURST, oEnemy, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_HOLD_PERSON, oEnemy, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_HOLD_PERSON, oEnemy, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_DOOM, oEnemy, METAMAGIC_ANY, TRUE);

    ActionCastSpellAtObject(SPELL_BANE, oEnemy, METAMAGIC_ANY, TRUE);

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


    as i said it works perfect except that when it comes time to attack he will just cast those spells in this order again

    if one of you chaps can help add to this so when he is done with those spells and then attacks afterwards without casting those spells again that would be great :)
  • TerrorbleTerrorble Member Posts: 179
    I'm learning this too, but after you assign your creature to cast the last spell: ActionCastSpellAtObject(SPELL_BANE, oEnemy, METAMAGIC_ANY, TRUE);

    add this line:
    DeleteLocalInt(OBJECT_SELF,"X2_SPECIAL_COMBAT_AI_SCRIPT ");
    


    My thinking is that since this variable that you set on the creature:

    X2_SPECIAL_COMBAT_AI_SCRIPT | string | ai_liz_wiz (or whatever you named the script)

    is what is telling the AI to run this special AI script, by deleting the variable once the script has run once, it won't run it again.

    That's probably a comically poor way to do it to someone who understands it better, but it's simple and might work. Good luck


    P.S. Post code between [c ode] & [/c ode] for it to look nice on the forum. (Of course, remove the space I put between 'c' and 'o')
  • ShadooowShadooow Member Posts: 402
    edited July 2020
    Terrorble wrote: »
    I'm learning this too, but after you assign your creature to cast the last spell: ActionCastSpellAtObject(SPELL_BANE, oEnemy, METAMAGIC_ANY, TRUE);

    add this line:
    DeleteLocalInt(OBJECT_SELF,"X2_SPECIAL_COMBAT_AI_SCRIPT ");
    


    My thinking is that since this variable that you set on the creature:

    X2_SPECIAL_COMBAT_AI_SCRIPT | string | ai_liz_wiz (or whatever you named the script)

    is what is telling the AI to run this special AI script, by deleting the variable once the script has run once, it won't run it again.

    That's probably a comically poor way to do it to someone who understands it better, but it's simple and might work. Good luck

    no this is wrong

    You both need to realize that the creature AI is not a script where you define 10 spells in succession to cast and creature will cast it. That will never work.

    Creature AI is called when npc finishes previous cast, when she fails to cast the spell (interrupted), and dozen of other cases.

    The AI script I sent shows this. There is not a queve of spells but each time it is called script will perform only one cast.

    The problem of OP with his script is that he took mine script, deleted what he should have deleted but then pasted his old code which is not going to work as it is the same as before, just in other "event".

    The script needs to be written in a way so it always casts just one spell and when no spell is available, performs ActionAttack.

    Specifically, the problem of OP script is that he doesn't check whether the spell is available and casts it "cheated". He needs to assign the creature as many uses of the spels he needs, and then use same logic as me in original AI script I posted - check if spell is available (+addiionally check some conditions if he wants to) and then cast the single spell (not using bCheat=TRUE) and exit the script the same way. Only when there will be no spell uses, ActionAttack is on the menu.
  • sarevok57sarevok57 Member Posts: 6,002
    hookay, so all i had to do was delete;
    ,METAMAGIC_ANY, TRUE at the end of all the spell lines

    ( although now i actually have to give the spells they are supposed to cast, but that is okay )

    and now everything works as it should

    baddies cast their spells, and then they attack once their spells are depleted, excellent

    thanks for the help guys :)
  • ShadooowShadooow Member Posts: 402
    no that is not all, but wel if it for reason works...
  • NeonGothicBloodNeonGothicBlood Member Posts: 13
    Shadooow, Man you really need to gather up all of your explanations and create a master class, to go along with what you posted about nwcript A.I. and put it all in one place.
Sign In or Register to comment.