Aller au contenu

Photo

Distance Combat With Spells - Force NPC/Monster To Use Spells Before Closing?


  • Veuillez vous connecter pour répondre
16 réponses à ce sujet

#1
Lance Botelle

Lance Botelle
  • Members
  • 1 480 messages
Hi All,

I have encounters where the creatures have spell capabilities.

They also start at a reasonable distance from the PC when the combat begins, which would allow them to cast spells. However, I have noticed that (often than not), that the NPCs would often close to the encounter *before* casting some spells even when they could have done so before hand. (Sometimes they do cast before closing.)

Is there any known way to ensure the NPCs definitely cast spells before *closing* into combat?

Thanks in advance,
Lance.

#2
kevL

kevL
  • Members
  • 4 061 messages

hi Lance,
this is something I want to look into but unfortunately it's not on my menu atm. (correct sequential blitting of sprites in a 3d animated isometric battlescape is... gah.) so, Just saying i've noticed this too and agree it's a bit of a problem, likely in the AI decision scripts -- where the weighting for "choose cast spell" needs to be increased against "choose close melee". The latter I'd like to shut down completely (minus some very small probability) if casting is valid. And, again unfortunately, I don't think the best solution is with the "BEH" npc-AI variables, although if you know about them you should try them ....

i can dig them up if you like,



#3
rjshae

rjshae
  • Members
  • 4 491 messages

Yes, I've used those weighting variables and they seem to work okay. You could always add a custom script to modify those weights based on the circumstances.



#4
kevL

kevL
  • Members
  • 4 061 messages

the problem, tho I think, is that they have to be added to each NPC. so I'd prefer a global solution that goes to work on all NPCs in all modules, eh



#5
Psionic-Entity

Psionic-Entity
  • Members
  • 195 messages

This probably isn't the most elegant solution but I just got rid of creature abilities entirely and used a custom heartbeat script to figure it out. Outright assigning spellcasting doesn't seem to mess with the AI at all and you can still write it to take away slots or whatever.



#6
Lance Botelle

Lance Botelle
  • Members
  • 1 480 messages

<SNIP> ... i can dig them up if you like,


Hi KevL,

I thought I had found all possible variables, but may have missed some. (Or, I have found various integer settings, but I am not sure if some are redundant ones ... or require different include scripts to those that I am currently using. The existing AI is a very tangled web as I am sure you are aware.) So, certainly, if you could point me towards any that may help, that would be appreciated.

I have written a fair amount of AI around the exiting code, but I am having difficulties forcing distant casting at the moment.

Thanks in advance,
Lance.

#7
kevL

kevL
  • Members
  • 4 061 messages
in #include 'nw_i0_generic'

there's a function, CalculateTactics() that returns an rTactics struct, 'tacAI', that holds a bunch of ints about what to do. This holds weights for things like "nOffense" (melee) "nCompassion" (heal) "nMagic" (cast) etc.

The odds for
- nMagic can be adusted with a local variable on an NPC, "X2_L_BEH_MAGIC"
- nOffense can be adusted with a local variable on an NPC, "X2_L_BEH_OFFENSE"
- nCompassion can be adusted with a local variable on an NPC, "X2_L_BEH_COMPASSION"

EDIT: those are all INTs

Those are also defined by string-constants in #include 'x2_inc_switches' (although i prefer to use the strings themselves, straight up.)
 
//------------------------------------------------------------------------------
// * The value of this variable (int) is added to the chance that a creature
// * will use magic in combat. Set to 100 for always, 0 for never
//------------------------------------------------------------------------------
const string CREATURE_AI_MODIFIED_MAGIC_RATE = "X2_L_BEH_MAGIC";

//------------------------------------------------------------------------------
// * The higher value of this variable, the higher the chance that the creature
// * will use offensive abilities in combat. Set to 0 to make them flee.
//------------------------------------------------------------------------------
const string CREATURE_AI_MODIFIED_OFFENSE_RATE = "X2_L_BEH_OFFENSE";

//------------------------------------------------------------------------------
// * The higher value of this variable, the higher the chance that the creature
// * will aid friendly creatures in combat. Not[e] that helping usually degrades
// * the overall difficulty of an encounter, but makes it more interesting.
//------------------------------------------------------------------------------
const string CREATURE_AI_MODIFIED_COMPASSION_RATE = "X2_L_BEH_COMPASSION";

note that, the last time I checked, these variables work only when DetermineCombatRound() is called. That is, the companion AI, HenchDetermineCombatRound() does not use them, iirc.
  • rjshae aime ceci

#8
andysks

andysks
  • Members
  • 1 650 messages
I use some scripts given to me by ColorsFade which do all these stuff. The creature will buff, and then cast the spell I want it to cast. All I need is a script referenced via the variable SPECIAL_COMBAT_AI (Something like that). It even casts randomly the spells I want, and picking best locations for aoe spells like fireball, or targets based on class, saves etc.

Now, these are ColorsFade's scripts and I think he is on hiatus. I would share, but I have to ask first because it took him quite some work to write them.

#9
Lance Botelle

Lance Botelle
  • Members
  • 1 480 messages

in #include 'nw_i0_generic'

there's a function, CalculateTactics() that returns an rTactics struct, 'tacAI', that holds a bunch of ints about what to do. This holds weights for things like "nOffense" (melee) "nCompassion" (heal) "nMagic" (cast) etc.

The odds for
- nMagic can be adusted with a local variable on an NPC, "X2_L_BEH_MAGIC"
- nOffense can be adusted with a local variable on an NPC, "X2_L_BEH_OFFENSE"
- nCompassion can be adusted with a local variable on an NPC, "X2_L_BEH_COMPASSION"

EDIT: those are all INTs

Those are also defined by string-constants in #include 'x2_inc_switches' (although i prefer to use the strings themselves, straight up.)


//------------------------------------------------------------------------------
// * The value of this variable (int) is added to the chance that a creature
// * will use magic in combat. Set to 100 for always, 0 for never
//------------------------------------------------------------------------------
const string CREATURE_AI_MODIFIED_MAGIC_RATE = "X2_L_BEH_MAGIC";

//------------------------------------------------------------------------------
// * The higher value of this variable, the higher the chance that the creature
// * will use offensive abilities in combat. Set to 0 to make them flee.
//------------------------------------------------------------------------------
const string CREATURE_AI_MODIFIED_OFFENSE_RATE = "X2_L_BEH_OFFENSE";

//------------------------------------------------------------------------------
// * The higher value of this variable, the higher the chance that the creature
// * will aid friendly creatures in combat. Not[e] that helping usually degrades
// * the overall difficulty of an encounter, but makes it more interesting.
//------------------------------------------------------------------------------
const string CREATURE_AI_MODIFIED_COMPASSION_RATE = "X2_L_BEH_COMPASSION";
note that, the last time I checked, these variables work only when DetermineCombatRound() is called. That is, the companion AI, HenchDetermineCombatRound() does not use them, iirc.


Hi KevL,

Thanks for pointing these out. I don't recall seeing these ones, so maybe this is going to be an avenue I can look into. However, it sounds like andysks (via ColorsFade scripts) may hold the key to what we are after. The approach he mentions is one I have already used myself (with respect to many aspects of the AI, including buffing and healing, etc.). However, if ColorsFade has already done some extra scripting that checks and casts other spells, then that may be exactly what we need. (See next.)
 
 

I use some scripts given to me by ColorsFade which do all these stuff. The creature will buff, and then cast the spell I want it to cast. All I need is a script referenced via the variable SPECIAL_COMBAT_AI (Something like that). It even casts randomly the spells I want, and picking best locations for aoe spells like fireball, or targets based on class, saves etc.

Now, these are ColorsFade's scripts and I think he is on hiatus. I would share, but I have to ask first because it took him quite some work to write them.


Hi andysks,

That may be the kind of thing I am looking for. :) It would certainly save me some time doing it myself ... especially, as I thought I would not have to do so in the first place. i.e. I have already done some AI scripting, which works well, but I just need to stop the "close to combat" when a distant spell would be a better option. So, it sounds like ColorsFade's scripts will fit that requirement well. If you can get their permission to share, that would be helpful.

Thanks all,
Lance.


EDIT: Actually, I did have this in some of my notes, which relates to what you mentioned KevL. I found these notes in my modified Monsters On Spawn script and had forgot about them. (I found them after searching for the stuff you mentioned.):-

/*/////////////////////////////////////////////////////////////////////////////////
// LIST OF VARIABLES HOLDERS THAT CAN BE ADDED TO CREATURES IN THE TOOLSET OR SCRIPT
// THIS GETS THE SWITCH TO CHECK FROM CREATURE. (COULD USE OWN HOLDER I SUPPOSE. SEE BELOW.)
// OFFICIAL E.g. "X2_L_SPAWN_USE_STEALTH" WITH 1 IN IT ON CREATURE WILL SET THE NW_FLAG_STEALTH (BITWISE)
// THEN THE NW_FLAG_STEALTH IS PICKED UP IN SCRIPTS LIKE x0_i0_walkway
// OWN HOLDER MIGHT BE SIMPLE AS: "STEALTHY" VALUE OF 1 ON CREATURE, THEN USE:
// if (GetLocalInt(OBJECT_SELF, "STEALTHY") == TRUE){SetSpawnInCondition(NW_FLAG_STEALTH);}
// WHICH WOULD BE MUCH SIMPLER TO UNDERSTAND.
///////////////////////////////////////////////////////////////////////////////////

//------------------------------------------------------------------------------
// * see x2_ai_demo for details
//------------------------------------------------------------------------------
const string CREATURE_VAR_CUSTOM_AISCRIPT = "X2_SPECIAL_COMBAT_AI_SCRIPT";
//------------------------------------------------------------------------------
// * Setting this variable on a spellcaster creature will make its spelluse a
// * bit more random, but their spell selection may not always be appropriate
// * to the situation anymore.
//------------------------------------------------------------------------------
const string CREATURE_VAR_RANDOMIZE_SPELLUSE = "X2_SPELL_RANDOM";
//------------------------------------------------------------------------------
// * Set to 1 to make the creature activate stealth mode after spawn
//------------------------------------------------------------------------------
const string CREATURE_VAR_USE_SPAWN_STEALTH = "X2_L_SPAWN_USE_STEALTH";
//------------------------------------------------------------------------------
// * Set to 1 to make the creature activate detectmode after spawn
//------------------------------------------------------------------------------
const string CREATURE_VAR_USE_SPAWN_SEARCH = "X2_L_SPAWN_USE_SEARCH";
//------------------------------------------------------------------------------
// * Set to 1 to make the creature play mobile ambient animations after spawn
//------------------------------------------------------------------------------
const string CREATURE_VAR_USE_SPAWN_AMBIENT = "X2_L_SPAWN_USE_AMBIENT";
//------------------------------------------------------------------------------
// * Set to 1 to make the creature play immobile ambient animations after spawn
//------------------------------------------------------------------------------
const string CREATURE_VAR_USE_SPAWN_AMBIENT_IMMOBILE = "X2_L_SPAWN_USE_AMBIENT_IMMOBILE";
//------------------------------------------------------------------------------
// * Set to 1 to make the creature immune to dispel magic (used for statues)
//------------------------------------------------------------------------------
const string CREATURE_VAR_IMMUNE_TO_DISPEL = "X1_L_IMMUNE_TO_DISPEL";
//------------------------------------------------------------------------------
// * Set this variable to 1 on a creature to make it walk through other creatures
//------------------------------------------------------------------------------
const string CREATURE_VAR_IS_INCORPOREAL = "X2_L_IS_INCORPOREAL";
//------------------------------------------------------------------------------
// * Set this variable to 1 - 6 to override the number of attacks a creature has based on its BAB
//------------------------------------------------------------------------------
const string CREATURE_VAR_NUMBER_OF_ATTACKS = "X2_L_NUMBER_OF_ATTACKS";
//------------------------------------------------------------------------------
// * The value of this variable (int) is added to the chance that a creature
// * will use magic in combat. Set to 100 for always, 0 for never
//------------------------------------------------------------------------------
const string CREATURE_AI_MODIFIED_MAGIC_RATE = "X2_L_BEH_MAGIC";
//------------------------------------------------------------------------------
// * The higher value of this variable, the higher the chance that the creature
// * will use offensive abilities in combat. Set to 0 to make them flee.
//------------------------------------------------------------------------------
const string CREATURE_AI_MODIFIED_OFFENSE_RATE = "X2_L_BEH_OFFENSE";
//------------------------------------------------------------------------------
// * The higher value of this variable, the higher the chance that the creature
// * will aid friendly creatures in combat. Not that helping usually degrades
// * the overall difficulty of an encounter, but makes it more interesting.
//------------------------------------------------------------------------------
const string CREATURE_AI_MODIFIED_COMPASSION_RATE = "X2_L_BEH_COMPASSION";
//------------------------------------------------------------------------------
// * This allows you to script items that enhance a palemaster's summoned creatures. You need
// * to put the name of a script into this variable that will be run on any creature called by
// * the pale master's summon undead ability. You can use this script to add effects to the creature.
// * You can use the OnEquip/OnUnEquip event hooks set this variable.
//------------------------------------------------------------------------------
const string CREATURE_VAR_PALE_MASTER_SPECIAL_ITEM = "X2_S_PM_SPECIAL_ITEM";

#10
Lance Botelle

Lance Botelle
  • Members
  • 1 480 messages

note that, the last time I checked, these variables work only when DetermineCombatRound() is called. That is, the companion AI, HenchDetermineCombatRound() does not use them, iirc.


Hi KevL,

I thought I would answer this in a separate post, as I believe this is what I came up against when trying to use these integers before. As far as I can see, all creatures now appear to use the HenchDetermineCombatRound() function. This means those integers are never called if what you say is correct. Therefore, maybe I will track down where the const string CREATURE_AI_MODIFIED_MAGIC_RATE = "X2_L_BEH_MAGIC"; part is used in the DetermineCombatRound() (from the include you mention) in particular, and try adding the code to my current AI script ... although, I suspect that may be the sort of thing ColorsFade has already done.

I will carry on for a bit more on this, as I would like to see the results. If, on the other hand we get access to CF scripts, then maybe I will just try to incorporate their work instead.

I will keep you all updated as normal. ;)

Cheers,
Lance.

RESEARCH:-

The AI Trail....

OK, here is what I think happens, which may help us to intercept the code required ...

INITIAL FUNCTION CALL
=====================

1) HenchDetermineCombatRound() is the function that calls a creature's attack.
2) The HenchDetermineCombatRound() function is called from hench_i0_ai.
3) If no alternative AIScript is placed inside "AIScript" on the creature, then this function will call the default "hench_o0_ai" AI script.

THE AI SCRIPT (hench_o0_ai)
========================

1) Assuming we run this default AI script, then hench_o0_ai runs some basic AI script before checking if we have any alternative "special AI
script" directed to a script held in "X2_SPECIAL_COMBAT_AI_SCRIPT" on the creature. If there is, then this AI script will run this additonal
script (scripted by ourselves) and end. If nothing is specified, then the AI script continues to do its own AI.

NB: This is currently the stage I have intercepted most of my AI stuff to try to fix spell usage already. However, even if I do not add an
additional script here, the default AI still does not handle spells very well. HOWEVER, I have also noted that to MAKE SURE my own AI additional script fires, I have sometimes HAD to place MY script reference inside the AIScript variable holder stage (see 3 above). I am not sure why this has to be the case, but there are nearly 500 additional lines of code with other conditional checks between, which are obviously doing something (before it runs any script inside the "X2_SPECIAL_COMBAT_AI_SCRIPT").

 

So, to continue ...

2) Assuming we have not added any "special AI script", then the hench_o0_ai continues (doing some various stuff), until we reach line 783 with
this function call: InitializeItemSpells(nClass, iNegEffectsOnSelf, iPosEffectsOnSelf); called from the hench_i0_itemsp include assuming
conditions are correct.

THE SPELL/ITEM FUNCTION CALLS (hench_i0_itemsp)
============================================

1) The function InitializeItemSpells is called from hench_i0_itemsp (line 2070). On line 2182, this function calls InitializeSpellTalents();
2) This function calls InitializeSpellTalentCategoryNoItems(TALENT_CATEGORY_HARMFUL_RANGED); on line 850 inside the same include.
3) The InitializeSpellTalentCategoryNoItems function calls the FindItemSpellTalentsByCategory function (from the same include).
4) The FindItemSpellTalentsByCategory function calls the HARD CODED GetCreatureTalentBest function!

NOTE: Tracking the old method (that uses DetermineCombatRound) also eventually comes back to this function (called from x0_inc_generic line 234).

END OF THE LINE
===============

Unfortunately, we cannot see how the function GetCreatureTalentBest allocates spells for the creature, which would determine which spell the creature is trying to cast, in turn determining whether the int returns TRUE or FALSE when casting a spell.

I suppose the bottom line would be to add our own checks (again) at the same point I have already added some "special combat AI". The answer would be to write ones own function that checks distance/spells available, etc. which is what I hope ColorsFade extra code does.



#11
kamal_

kamal_
  • Members
  • 5 250 messages

bookmarks for later.



#12
kevL

kevL
  • Members
  • 4 061 messages

have a glance at this first: tTalents

tl;dr : this is complicated.


As far as I can see, all creatures now appear to use the HenchDetermineCombatRound() function.

yeh I guess you're right, I was having conflicting memories about this, but i re-checked Tony's nw_c2_default* scripts and they use hDCR (but didn't check the stock scripts). NOTE: everything below pertains to TonyK's AI.

This means those integers are never called if what you say is correct.

pretty darn sure that's correct.

found this:
// TODO this should be below in the code for resistances, but too many branches
now that's an understatement.

ok, in 'hench_i0_attack'
function, HenchMeleeAttack()
it sets
float fMaxRange to 1000.f, if there are no 'bgMeleeAttackers'
else set fMaxRange to DistanceToClosest'ogClosestSeenOrHeardEnemy' + 1.5f

So that tells me that the decision to do a melee attack is based on the ascribed distance between the two opponents. ~1000.f meaning too far to engage melee.
that's a potential hook right there, i suspect.


btw, if you're wondering what prefix "g" is it means "global"; ie. "gf" global float, etc. (yeah, the AI uses global temporary variables...). And sometimes they're reversed: "bg" boolean global


in 'hench_o0_ai'
int bAllowMeleeAttacks = !GetHenchAssociateState(HENCH_ASC_NO_MELEE_ATTACKS)
    || (GetInitWeaponStatus() & HENCH_AI_HAS_RANGED_WEAPON);

you could hook that to a variable on NPC. Check for it in the code and override above. Of course this is simplistic; it would, for example, make NPC *unable* to engage w/ melee.


now look at spells: 'hench_i0_spells', struct "sSpellInformation"
... ah:
float gfAttackWeight;
// etc

and functions like these two look very relevant:
void HenchCheckIfAttackSpellToCastOnObject(float fFinalTargetWeight, object oTarget)
{
    if (fFinalTargetWeight > 0.f)
    {
        fFinalTargetWeight *= GetConcetrationWeightAdjustment(gsCurrentspInfo.spellInfo, gsCurrentspInfo.spellLevel);
        if (fFinalTargetWeight >= gfAttackTargetWeight)
        {
            if (gfAttackTargetWeight == 0.f
                    || fFinalTargetWeight >= gfAttackTargetWeight * 1.02f
                    || gsCurrentspInfo.spellLevel < gsAttackTargetInfo.spellLevel
                    || !GetIsObjectValid(gsAttackTargetInfo.oTarget))
            {
                gfAttackTargetWeight       = fFinalTargetWeight;
                gsAttackTargetInfo         = gsCurrentspInfo;
                gsAttackTargetInfo.oTarget = oTarget;
            }
        }
    }
}

void HenchCheckIfAttackSpellToCastAtLocation(float fFinalTargetWeight, location lTargetLocation)
{
    if (fFinalTargetWeight > 0.f)
    {
        fFinalTargetWeight *= GetConcetrationWeightAdjustment(gsCurrentspInfo.spellInfo, gsCurrentspInfo.spellLevel);
        if (fFinalTargetWeight >= gfAttackTargetWeight)
        {
            if (gfAttackTargetWeight == 0.f
                    || fFinalTargetWeight >= gfAttackTargetWeight * 1.02f
                    || gsCurrentspInfo.spellLevel < gsAttackTargetInfo.spellLevel)
            {
                gfAttackTargetWeight          = fFinalTargetWeight;
                gsAttackTargetInfo            = gsCurrentspInfo;
                gsAttackTargetInfo.oTarget    = OBJECT_INVALID;
                gsAttackTargetInfo.lTargetLoc = lTargetLocation;
            }
        }
    }
}

there we see 'gfAttackTargetWeight' and struct 'gsAttackTargetInfo' being set. (they might get changed later, tho - I still haven't found the final "go, with these particular options" bit)


So, i conclude that hDCR is structured very differently than DCR. That is, there may no longer be a convenient 'rTactics' struct to manipulate

back to 'hench_o0_ai', and look at this
// gfAttackTargetWeight = 0.f; // test code to not use melee attack
I think that should be seen as 'a clue'.

huh. Looks like i already tried something here:
// adjust attack and buff based on class type
{
    if (GetIsLowBAB())
    {
        // reduce chance of melee attack
//kL    gfAttackTargetWeight *= 0.8f;
        gfAttackTargetWeight *= 0.72f; // kL
    }

but for my purpose (a global change) that edit should be broken out of GetIsLowBAB() .... hth!


THE AI SCRIPT (hench_o0_ai)
========================

1) Assuming we run this default AI script, then hench_o0_ai runs some basic AI script before checking if we have any alternative "special AI
script" directed to a script held in "X2_SPECIAL_COMBAT_AI_SCRIPT" on the creature. If there is, then this AI script will run this additonal
script (scripted by ourselves) and end.


only *if* it's told to end, otherwise it can potentially take your data and run with it through the stock AI.


If nothing is specified, then the AI script continues to do its own AI.

NB: This is currently the stage I have intercepted most of my AI stuff to try to fix spell usage already. However, even if I do not add an
additional script here, the default AI still does not handle spells very well. HOWEVER, I have also noted that to MAKE SURE my own AI additional script fires, I have sometimes HAD to place MY script reference inside the AIScript variable holder stage (see 3 above). I am not sure why this has to be the case, but there are nearly 500 additional lines of code with other conditional checks between, which are obviously doing something (before it runs any script inside the "X2_SPECIAL_COMBAT_AI_SCRIPT").


it's making checks for things like the cowardly_flag, Following, no-seen-or-heard-enemy, etc etc. These can make combat exit early, including a custom script.

 

So, to continue ...


ps. i wouldn't get too hung up on 'hench_i0_itemsp' - i suspect its only for item casts/usage.

#13
Lance Botelle

Lance Botelle
  • Members
  • 1 480 messages
Hi KevL (and other interested parties),
 
KevL: Thanks for that feedback. :)
 
I also have some more feedback for you .... and this appears to *work* for me. I have posted it here for you to consider, but as I have not tested it 100%, then I let you decide.
 
Basically, I ended up discovering some things ....
 
1) Using the ActionAttack function is NOT the same as having a creature determine its round with functions like HenchDetermineCombatRound. I know that may sound obvious, but in my naivety, I had assumed starting an "attack" with ActionAttack would make creatures start their combat round and hence the associated HenchDetermineCombatRound with it. It does *NOT do this until after the creature has closed to physically attack. Hence ...

2) By replacing all references of ActionAttack with HenchDetermineCombatRound function, the creature now jumps straight into considering "tactics", including what spells to cast. i.e. It NO LONGER runs up to the target first.

3) In the small amount of testing I did, I managed to have my AI script call DetermineCombatRound instead of HenchDetermineCombatRound for my spell casters. i.e. I added a variable to spell casters ("CASTPERCENT") that made a check inside my AI Script fire the older function.

Creature OnSpawn has:-

int iSpellUsage = GetLocalInt(OBJECT_SELF, "CASTPERCENT");
if(iSpellUsage > 0){SetLocalInt(OBJECT_SELF, "X2_L_BEH_MAGIC", iSpellUsage);}

My AIScript has:-

// TEST SPELL CASTING ADDITION

int iSpellUsage = GetLocalInt(oNPC, "CASTPERCENT");
if(iSpellUsage > 0)
{
DetermineCombatRound();
SetCreatureOverrideAIScriptFinished();
return;
}

Now, because my spell casters have my AI Script inside variable holder "X2_SPECIAL_COMBAT_AI_SCRIPT", whenever the variable CASTPERCENT had a positive value, it used the DetermineCombatRound to determine actions, including the spell casting.

And this is the important point that needs checking with further testing ... Whether it was my imagination or not, the spell casters then appeared to use a better selection of spells when re-directed to use DetermineCombatRound than HenchDetermineCombatRound.

The most important thing, however, with these two changes, the spell casters did NOT run forward and, instead, started their spell casting straightwaway. Furthermore, they used any "special ability" I had added to them too! e.g. Summon Creature.

The only down side I discovered in my own testing (which may be due to my own scripts rather than anything else), was that I have to add some code to the individual spells the NPCs use to prevent their summoned creatures turning HOSTILE to them if the spell casters accidentally catch them inside a later cast harmful spell. It seems to work fine now, but I will have to add the fix to all potential spells the NPCs may use while they have allies around that may be caught inside them.

The overall result is quite amazing! It really feels like the enemy spell casters are behaving better.
 
EDIT: The spells chosen are still effectively "random" according to the caster's selection, but coupled with my other AI script code (which determines healing of self and allies), the overall results looked good to me. Obviously, a better way of keeping the spells cast more appropriate may be to limit the spells placed on the spell casters in the first place. Then the "randomness" will be more selective. This is how I expected it to work anyway. :)

Cheers,
Lance.

#14
kevL

kevL
  • Members
  • 4 061 messages
interesting ... i may switch scripts back to DCR ( i think i switched them once before, but wasn't paying attention )

#15
kevL

kevL
  • Members
  • 4 061 messages
ps.
 

The only down side I discovered in my own testing (which may be due to my own scripts rather than anything else), was that I have to add some code to the individual spells the NPCs use to prevent their summoned creatures turning HOSTILE to them if the spell casters accidentally catch them inside a later cast harmful spell. It seems to work fine now, but I will have to add the fix to all potential spells the NPCs may use while they have allies around that may be caught inside them.


something like this, from 'ginc_behavior'
 
// This check is run before the default scripts (nw_c2's) explicitly tell a creature to attack a target
// it is NOT checked in Determine Combat Round. 
int GetIsValidRetaliationTarget(object oTarget, object oRetaliator=OBJECT_SELF) 
{
    if (GetPlotFlag(oRetaliator) && !GetIsEnemy(oTarget, oRetaliator))	//please don't attack any neutral or friends if I am plot
        return FALSE; //not cool to attack this guy
    return TRUE; //ok to attack this guy
}
in onDamaged, onSpellCastAt, onPhysicallyAttacked events - or wherever call to attack is made in AI events
just remove the Plot check and put in whatever you like ...

#16
ColorsFade

ColorsFade
  • Members
  • 1 267 messages

Andy asked me to chime in here...

 

I wrote a very elaborate AI library for The Darkening Sky. Andy uses my scripts in his campaign as well.

 

My library provides a lot of functions for customizing spellcasting combat, because I found the default behavior really inadequate. Andy's right about what it can do: there's some AI in the library so NPC spellcasters can do things like find the best location for an AoE spell (so they can hit as many of your party as possible with a Fireball or Ice Storm), they can instant-buff themselves at the start of a fight (simulating a sequencer, and making them tougher opponents without any kind of hack or cheat), and they can either cast spells in a particular order, or randomize the spells they cast. In addition, NPC's using spells can be instructed, on a spell-by-spell basis, to target intelligently based on the saves involved in the spell. In fact, a large part of the AI is all about targeting. So, for instance, if the NPC is a necromancer and has Charm Person memorized, the save against Charm Person is WILL. With the AI, you can tell the NPC necromancer to target the lowest WILL save character in the PC party when casting the Charm Person spell. This way, the spell has a better chance to successfully land. You can also target by class. For instance, you could (cruel, but you can do it) tell the big melee NPC fighters to target the cleric in the PC Party... Kind of a "go after the healer!" thing. There's a lot you can do with the targeting to make fights tougher for the PC party. 

 

You can download my mod, The Darkening Sky, and use those scripts, or get them from Andy if you wish. All I ask is that my script headers remain, so I can be credited with the work. 

 

If you elect to use my scripts, you can hit me up in email, and I'll explain anything that you cannot get from the script function headers. I tried to comment my work very well, so most things can be gleaned from the script library. 

 

As Andy said, the benefit of using my library is that I use the SPECIAL_COMBAT_AI hook, which overrides default behavior. This makes it very easy to write AI scripts and attach them to creature blueprints. And with the scripting I've come up with, it's pretty easy to take an existing AI script for a creature and modify it for a different creature or class. 

 

All of that said, there is one other issue here, and that is creatures casting spells at long range, as fights start. Even if a creature has AI that tells them to cast a spell first, sometimes the PC party can still close the gap before the spell can be useful. And example is a tangle/web based spell. In some situations it is nice to have the NPC cast a web/tangle spell at a location before the PC party reaches it, in a corridor or other closed in area, forcing the PC party to navigate (or dispell) that effect before reaching the NPC spellcasters. I have found that the best way to handle this is by placing waypoints on the ground where you want the NPC to cast the spell, and then, in the AI script, making that spell (web/tangle) the first spell the NPC casts. So as soon as they see the party, the very first thing they do is cast the web/tangle spell at the waypoint. Then the NPC spellcaster moves on, on the next round, to the rest of their AI scripting. This takes a little more work to setup than just attaching an AI file to the NPC via the SPECIAL_COMBAT_AI variable, but in my testing it's been worth it, as it makes those sort of movement hindering spells more useful and makes the encounters more interesting. 

 

Also, another thing I've done, and this happens in one of the Darkening Sky fights, is to use geography of the room and monsters to sort of "protect" the enemy spellcasters. That way the PC party cannot just rush the spellcasters and mow them down really fast. 

 

There's a LOT of things that we can do, as modders, with geography of locations, barriers, and just putting minions in the way, etc., to make fights more interesting and to make NPC spellcasters harder and more challenging. 

 

Hope I answered questions with this. 

 

And yeah, I am on a hiatus, but I will be getting back to work on The Darkening Sky soon. It's not going away. Just a lot of stuff going on in my life right now. 



#17
Lance Botelle

Lance Botelle
  • Members
  • 1 480 messages

Andy asked me to chime in here...
 
I wrote a very elaborate AI library for The Darkening Sky. Andy uses my scripts in his campaign as well.
 
My library provides a lot of functions for customizing spellcasting combat, because I found the default behavior really inadequate. Andy's right about what it can do: there's some AI in the library so NPC spellcasters can do things like find the best location for an AoE spell (so they can hit as many of your party as possible with a Fireball or Ice Storm), they can instant-buff themselves at the start of a fight (simulating a sequencer, and making them tougher opponents without any kind of hack or cheat), and they can either cast spells in a particular order, or randomize the spells they cast. In addition, NPC's using spells can be instructed, on a spell-by-spell basis, to target intelligently based on the saves involved in the spell. In fact, a large part of the AI is all about targeting. So, for instance, if the NPC is a necromancer and has Charm Person memorized, the save against Charm Person is WILL. With the AI, you can tell the NPC necromancer to target the lowest WILL save character in the PC party when casting the Charm Person spell. This way, the spell has a better chance to successfully land. You can also target by class. For instance, you could (cruel, but you can do it) tell the big melee NPC fighters to target the cleric in the PC Party... Kind of a "go after the healer!" thing. There's a lot you can do with the targeting to make fights tougher for the PC party. 
 
You can download my mod, The Darkening Sky, and use those scripts, or get them from Andy if you wish. All I ask is that my script headers remain, so I can be credited with the work. 
 
If you elect to use my scripts, you can hit me up in email, and I'll explain anything that you cannot get from the script function headers. I tried to comment my work very well, so most things can be gleaned from the script library. 
 
As Andy said, the benefit of using my library is that I use the SPECIAL_COMBAT_AI hook, which overrides default behavior. This makes it very easy to write AI scripts and attach them to creature blueprints. And with the scripting I've come up with, it's pretty easy to take an existing AI script for a creature and modify it for a different creature or class. 
 
All of that said, there is one other issue here, and that is creatures casting spells at long range, as fights start. Even if a creature has AI that tells them to cast a spell first, sometimes the PC party can still close the gap before the spell can be useful. And example is a tangle/web based spell. In some situations it is nice to have the NPC cast a web/tangle spell at a location before the PC party reaches it, in a corridor or other closed in area, forcing the PC party to navigate (or dispell) that effect before reaching the NPC spellcasters. I have found that the best way to handle this is by placing waypoints on the ground where you want the NPC to cast the spell, and then, in the AI script, making that spell (web/tangle) the first spell the NPC casts. So as soon as they see the party, the very first thing they do is cast the web/tangle spell at the waypoint. Then the NPC spellcaster moves on, on the next round, to the rest of their AI scripting. This takes a little more work to setup than just attaching an AI file to the NPC via the SPECIAL_COMBAT_AI variable, but in my testing it's been worth it, as it makes those sort of movement hindering spells more useful and makes the encounters more interesting. 
 
Also, another thing I've done, and this happens in one of the Darkening Sky fights, is to use geography of the room and monsters to sort of "protect" the enemy spellcasters. That way the PC party cannot just rush the spellcasters and mow them down really fast. 
 
There's a LOT of things that we can do, as modders, with geography of locations, barriers, and just putting minions in the way, etc., to make fights more interesting and to make NPC spellcasters harder and more challenging. 
 
Hope I answered questions with this. 
 
And yeah, I am on a hiatus, but I will be getting back to work on The Darkening Sky soon. It's not going away. Just a lot of stuff going on in my life right now.


Hi ColorsFade,

Thanks for adding the comment while on a hiatus. (Hope you get life sorted OK.)

I will check your work out then ... which looks very interesting ... and would solve much time. :)

EDIT: My campaign has "The Darkening" as a subheading. ;) http://www.worldofalthea.talktalk.net/

Many Thanks!
Lance.