Newbie at Monster AI scripting, could use some basics.
ReachPW
Member Posts: 27
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:
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.
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
0
Comments
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.
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 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.)
//*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
TR
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
indeed
https://neverwintervault.org/article/tutorial/tutorial-vanilla-community-patch-ai-depth