Skip to content

Newbie at Monster AI scripting, could use some basics.

ReachPWReachPW Member Posts: 27
edited January 2022 in Builders - Scripting
The basic problem I want to solve, is that for spell caster monsters they cast pretty junky spells. For example, one of my monsters has Isacc's Greater Missile Storm (and verified they have uses remaining), but keeps casting things like Color Spray or Ray of Frost (much lower level and lower damage spells). Only when it starts running out of spells does it then cast Missile Storm.

Ideally, the monster would cast highest level offensive spells first.

I've played around with these settings (on module load event), but they don't seem to affect it from what I can tell.

Settings:
    SetModuleSwitch(CREATURE_AI_MODIFIED_MAGIC_RATE, 90);
    SetModuleSwitch(CREATURE_AI_MODIFIED_OFFENSE_RATE, 90);
    SetModuleSwitch(CREATURE_VAR_RANDOMIZE_SPELLUSE, FALSE);
    SetLocalInt(GetModule(),"X2_SPELL_RANDOM", FALSE); // this is also set per monster on monster spawn


Any tips? What code file determines the logic on which spells the Monster AI casts? I've been looking though the Monster event scripts but it's not clear were this is kept.



--
edit:


I tracked it down to this function GetCreatureTalentBest. That is called from `x0_i0_talent` function `talent GetCreatureTalent(int nCategory, int nCRMax, int bRandom=FALSE, object oCreature = OBJECT_SELF)`

`GetCreatureTalentBest` is defined in nwscript.nss which I'm guessing means it's a hardcoded function. I could re-write this, but not sure were `talents` are defined. The code just passes around an int for "Talent ID". I don't see them in extracted 2da.




Post edited by ReachPW on

Comments

  • ForSeriousForSerious Member Posts: 446
    edited January 2022
    From all the digging I have done, it looks like spells are chosen by challenge rating. Example: Your monster is level 20 and a level 5 player attacks it, it will cast low spells first. The other way around, it will cast its best spells first if a level 20+ player attacks it.

    In nw_i0_generic line 964 and 5 it assess the enemies and sets the CR.
    In x0_i0_talent line 1033 it defines the offensive spell behavior function. (Like try an AoE spell, then ranged or special attacks, then damaging spells.) Anyway all through this function it calls GetCRMax() to help pick what spell to cast.
    GetCRMax() is in x0_inc_generic.
    TL;DR: Change line 38 in x0_inc_generic to TRUE to ignore the 'smart' spell picking.

    Never mind. More digging found that this would already be 20 meaning "Use what ever spell you've got."
    I'll keep looking.
  • ForSeriousForSerious Member Posts: 446
    I did not find anything helpful. It looks like normal enemies should already be casting their best spells first. Only Henchmen should be taking into account how hard the enemies are. Looks like that's controlled by OnPerception and the script in that slot should be nw_c2_default2.
    From the lexicon it says "Returns the best talent (i.e. closest to nCRMax without going over) of oCreature, within nCategory." for GetCreatureTalentBest—Which is what's ultimately used to pick what spell gets cast.

    The only things left that I can think of doing are: Add printouts to x0_i0_talent -> TalentSpellAttack(). Something like
    SendMessageToPC(GetFirstPC(), "Talent Spell CR: " + IntToString(GetCRMax()));
    
    under line 1034. That should tell you if it is not defaulting to 20.
    And, maybe just replacing all the calls to GetCRMax() in that function to 20 or something higher. (Though I think 20 is the highest rating spells are given.)
  • sarevok57sarevok57 Member Posts: 5,975
    as long as your creature has a full spell book with spells here is the template i use for my casters;

    //*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);
    }
    }

    - so first you need to make a script and copy paste everything you see here directly and save your script with a name that you will remember, lets say; mage_cast001
    - then you need to make a variable and name it; X2_SPECIAL_COMBAT_AI_SCRIPT
    - and then for the "string" portion of that variable you replace the "string" name with the name of the script you made for your spell caster ( ex would be; mage_cast001 )
    - next is the; ActionCastSpellAtObject(SPELL_XXX, OBJECT_SELF, METAMAGIC_ANY, FALSE, 0, PROJECTILE_PATH_TYPE_DEFAULT, TRUE);
    - with this line, replace the XXX with any defensive spell the creature has ( as long as the spell is memorized ) and this spell will automatically be cast upon the creature when your character meets up with them, ( kind of how in bg2 with mage fights, as soon as you start the battle, sometimes random protections just pop up on them, this is the same case scenario )
    - you can have multiple spells of this at the same time, just copy paste the line and replace the XXX
    - next is the; ActionCastSpellAtObject(SPELL_XXX, OBJECT_SELF);
    - this action makes it so when the caster sees the character they will start casting defensive spells on themself ( while the previous line made the spells already casted ) this will make it so the creature actually has to legitimately cast it instead, again you can use multiple lines, just replace the XXX
    - then next is the; ActionCastSpellAtObject(SPELL_XXX, oEnemy);
    - this is the line used to cast spells on any creature the caster views as an enemy ( so it could be your character, their familiars, summons, henchmen, ect )
    - and like the previous 2 lines, you replace the XXX with your offensive spells

    so for example, lets say you have a level 8 caster who has 2 level 4 spells, 3 level 3 spells, 4 level 2 spells, 5 level 1 spells and 4 level 0 spells and you want this to occur;

    when player sees caster, stoneskin and ghostly visage are auto casted

    then then caster casts improved invisibility then haste on itself when it sees the character

    then the caster casts

    lightning bolt
    flame arrow
    combust
    melfs acid arrow
    gedlees electric loop
    gedlees electric loop
    color spray
    ray of enfeeblement
    negative energy ray
    magic missile
    magic missile
    ray of frost
    ray of frost
    ray of frost
    ray of frost

    so the script would look like this:

    //*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_STONESKIN, OBJECT_SELF, METAMAGIC_ANY, FALSE, 0, PROJECTILE_PATH_TYPE_DEFAULT, TRUE);
    ActionCastSpellAtObject(SPELL_GHOSTLY_VISAGE, OBJECT_SELF, METAMAGIC_ANY, FALSE, 0, PROJECTILE_PATH_TYPE_DEFAULT, TRUE);

    ActionCastSpellAtObject(SPELL_IMPROVED_INVISIBILITY, OBJECT_SELF);
    ActionCastSpellAtObject(SPELL_HASTE, OBJECT_SELF);

    ActionCastSpellAtObject(SPELL_LIGHTNING_BOLT, oEnemy);
    ActionCastSpellAtObject(SPELL_FLAME_ARROW, oEnemy);
    ActionCastSpellAtObject(SPELL_COMBUST, oEnemy);
    ActionCastSpellAtObject(SPELL_MELFS_ACID_ARROW, oEnemy);
    ActionCastSpellAtObject(SPELL_GEDLEES_ELECTRIC_LOOP, oEnemy);
    ActionCastSpellAtObject(SPELL_GEDLEES_ELECTRIC_LOOP, oEnemy);
    ActionCastSpellAtObject(SPELL_COLOR_SPRAY, oEnemy);
    ActionCastSpellAtObject(SPELL_RAY_OF_ENFEEBLEMENT, oEnemy);
    ActionCastSpellAtObject(SPELL_NEGATIVE_ENERGY_RAY, oEnemy);
    ActionCastSpellAtObject(SPELL_MAGIC_MISSILE, 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);
    }
    }


    now, if you want to clean this scrip up a bit you can get rid of any line that starts with // , i personal just keep them there for reference to make things easier to remember whats happening

    plus order if very important, because as the problem you are having is that your casters aren't casting the exact spell you want, for this script whatever spells you put first are the ones they are going to cast first

    also, i have noticed with this script you cannot for some reason cast some defensive spells on yourself then cast some offensive spells on the enemy and then go back to casting defensive spells on yourself, the script screws up for some reason, so any defensive spells you want cast on the caster you should get them all off at the beginning of combat and then just unload on offensive spells

    also note that if you give a script command to a spell the caster does not have memorized it will just skip to the next spell on the list

    now with this all being said, this script may not be as complicated or up to par with SCS type levels of scripting, but with a good spell selection you can sure as hell make enemy casters dangerous and sure as hell make them more dangerous than the default casters in the original campaigns with their piss poor spell selections
  • ForSeriousForSerious Member Posts: 446
    Question: What happens when the player goes out of sight with this script?
  • TarotRedhandTarotRedhand Member Posts: 1,481
    @sarevok57 If you look at the icons that appear above the box you type in when composing your posts, you will see that a number of them have an upside down triangle next to them. This is to indicate that there is a dropdown menu associated with these icons. If you click on the leftmost one there is an option to change the text that you have input into a code block surrounded by a box. To use simply select the text to convert and select the word code from the dropdown menu.

    TR
  • sarevok57sarevok57 Member Posts: 5,975
    ForSerious wrote: »
    Question: What happens when the player goes out of sight with this script?

    if you run away, then the caster will chase after you and once you are in line of sight again they will continue to cast

    if you go invisible, or something on those lines, then they will probably stop their casting until you are targetable again, and once you are, they will continue from where they left off

    for higher level mages its always good to give them true seeing if possible, then they shouldn't have a problem with characters going out of sight
    @sarevok57 If you look at the icons that appear above the box you type in when composing your posts, you will see that a number of them have an upside down triangle next to them. This is to indicate that there is a dropdown menu associated with these icons. If you click on the leftmost one there is an option to change the text that you have input into a code block surrounded by a box. To use simply select the text to convert and select the word code from the dropdown menu.

    TR

    indeed
  • ForSeriousForSerious Member Posts: 446
    Kay thanks. I wasn't sure if they would be able to just keep pounding you, even if you became untargetable.
  • TerrorbleTerrorble Member Posts: 169
Sign In or Register to comment.