Paladin henchman won't use Divine Might in combat. Can I do something?

Hello everybody

I created a Paladin henchman and configured a package in which he gets Divine Might. I can make him use it trough conversation, but not combat. In conversation I used these scripts:

This is to see if he has the feat on the “text appears”. I copied from others just changed the feat:

int StartingConditional()
{
    int iResult;

    iResult = GetHasFeat(FEAT_DIVINE_MIGHT, OBJECT_SELF);
    return iResult;
}

This I made myself, it is to use the feat in the “action taken” part.

void main()
{
   {ActionUseFeat(FEAT_DIVINE_MIGHT, OBJECT_SELF);
   }

       {DecrementRemainingFeatUses(OBJECT_SELF, FEAT_DIVINE_MIGHT);
       }
}

Divine Might is a very short length buff, it is best used during combat. Is there a way to make the henchmen use it during combat?

Thank you very much.

There is a known bug in feat.2da where Divine Might has a value of **** in the category column where it should be 22. That bug prevents all NPCs from using Divine Might. If you’re curious, look in categories.2da and you’ll find entry 22 is TALENT_CATEGORY_HARMFUL_MELEE. There are several other feats with no value for category or the wrong value. Those bugs are in the 1.69 as well as the EE versions of NWN.

1 Like

Thank you for answering. So the only way is hoping for Beamdog to fix it?

You can fix it by making changes to feat.2da and placing this file where it can be picked up by the game / module.

It will then work through GetCreatureTalentBest:

However Divine Might isn’t a directly harmful feat (@Grymlorde), so I’m leaning towards TALENT_CATEGORY_BENEFICIAL_ENHANCEMENT_SELF (10) here.

However the problem is - and probably the reason why BW didn’t allow NPCs to use it (see message in the log) - that there is no way to bind one feat with another (i.e. can’t use X when you spent all Y for today). And this is what happens with Divine Might. So at worst you could end up with NPC constantly trying to use this feat (it has unlimited uses).

It is probably for the best to use this feat directly through custom combat AI.

Or - if someone is really determined - change Divine Might to have 10 uses per day (read: more than max Turn Undead) and decrement the remaining ones inside X0_S2_DivMight once Turn Undead is depleted. This may confuse players.

Hello

This custom combat ai stuff, is it hard to do?

Thank you

Scripting a (good) AI is one of the hardest things to do. There is a few projects in the Vault that do this, but they are full packages which may not do what you want (or do too much other stuff). But do check them out.

BW did let us hook into DetermineCombatRound, but of course they turned it into a binary choice: either you handle whole combat yourself or you let vanilla handle whole combat. They didn’t provide a “do something yourself first but let vanilla finish it” third option.

Putting Divine Might to offensive talent category indeed causes the NPC to deadlock. However I made a quick and dirty hack allowing injecting this feat (or any feat really) into the vanilla DCR pipeline. This was actually fun (and it works).

Save this script as nwsh_dcr - this is my DCR hack library
/*
    CUSTOM COMBAT OVERRIDE FOR VANILLA DETERMINE COMBAT ROUND

    Allows vanilla AI to be called from custom AI

    By NWShacker, 2021-01-09

    Based on nw_i0_generic
*/


#include "nw_i0_generic"
#include "x2_inc_switches"


// wrapper for SetCreatureOverrideAIScript
void NWSH_SetCreatureOverrideAIScript(string sScript, object oCreature=OBJECT_SELF);
void NWSH_SetCreatureOverrideAIScript(string sScript, object oCreature=OBJECT_SELF)
{
    SetCreatureOverrideAIScript(oCreature, sScript);
}


// initializes combat round override - MUST be called first
// * returns TRUE - can proceed with override script
// * returns FALSE - do not proceed with override script
int NWSH_DetermineCombatRoundInit();
int NWSH_DetermineCombatRoundInit()
{
    if(__InCombatRound() || GetAssociateState(NW_ASC_IS_BUSY))
    {
        SetCreatureOverrideAIScriptFinished();
        return FALSE;
    }

    __TurnCombatRoundOn(TRUE);

    return TRUE;
}


// validates current attack target (passed to DetermineCombatRound())
// or obtains new attack target (nearest hostile creature)
object NWSH_DetermineCombatRoundTarget();
object NWSH_DetermineCombatRoundTarget()
{
    object oIntruder = GetCreatureOverrideAIScriptTarget();

    if(BashDoorCheck(oIntruder)) return OBJECT_INVALID;

    if(bkEvaluationSanityCheck(oIntruder, 0.0)) return OBJECT_INVALID;

    if(!GetIsObjectValid(oIntruder))
    {
        oIntruder = bkAcquireTarget();
    }

    return oIntruder;
}


// tries vanilla AI tactics against oIntruder
// DO NOT call this function directly
void NWSH_DetermineCombatRoundTactics(object oIntruder=OBJECT_INVALID)
{
    if(TalentPersistentAbilities() || chooseTactics(oIntruder) == 99)
    {
        __TurnCombatRoundOn(FALSE);
    }
    else
    {
        DeleteLocalObject(OBJECT_SELF, "NW_GENERIC_LAST_ATTACK_TARGET");
        ClearActions(CLEAR_NW_I0_GENERIC_658);
        __TurnCombatRoundOn(FALSE);
        WalkWayPoints();
    }
}


// finishes combat round override - MUST be called last
// tries vanilla AI tactics against oIntruder (as action if iAsAction is TRUE)
void NWSH_DetermineCombatRoundExit(object oIntruder=OBJECT_INVALID, int iAsAction=FALSE);
void NWSH_DetermineCombatRoundExit(object oIntruder=OBJECT_INVALID, int iAsAction=FALSE)
{
    if(iAsAction)
    {
        ActionDoCommand(NWSH_DetermineCombatRoundTactics(oIntruder));
    }
    else
    {
        NWSH_DetermineCombatRoundTactics(oIntruder);
    }

    SetCreatureOverrideAIScriptFinished();
}
Save this script as your_custom_ai or something - this is the custom AI
#include "nwsh_dcr"


int NWSH_UseDivineMight()
{
    if(GetHasFeat(FEAT_DIVINE_MIGHT) &&
        GetHasFeat(FEAT_TURN_UNDEAD) &&
        !GetHasFeatEffect(FEAT_DIVINE_MIGHT))
    {
        ClearAllActions();
        ActionUseFeat(FEAT_DIVINE_MIGHT, OBJECT_SELF);
        return TRUE;
    }

    return FALSE;
}


void main()
{
    if(!NWSH_DetermineCombatRoundInit())
    {
        return;
    }

    object oIntruder = NWSH_DetermineCombatRoundTarget();
    int iAction;

    if(GetIsObjectValid(oIntruder))
    {
        if(100 * GetHitDice(oIntruder) / GetHitDice(OBJECT_SELF) >= 50)
        {
            iAction = NWSH_UseDivineMight();
        }
    }

    NWSH_DetermineCombatRoundExit(oIntruder, iAction);
}
Save this script as anything - this turns on custom AI (call it first)
#include "nwsh_dcr"


void main()
{
    object oYourNPC = GetObjectByTag("YOUR_NPC_TAG_HERE");

    NWSH_SetCreatureOverrideAIScript("your_custom_ai", oYourNPC);
}

NPC will use Divine Might only against enemies with 50%+ of its hit dice.

Hello

Wow, thanks very much, you’re awesome. Let me make a noob question though: how do I use it? I create all 3 scripts in the module and slot the third in OnUserDefined on the henchman?

Thank you very much.

I’ve had no issues with henchman using TonyK’s HenchAI and Divine Might (after I fixed the category). Of course, it is rare for low level parties to have several fights before resting so that may be why I never had a problem.

As for whether Divine Might is a Beneficial Enhancement or Harmful Melee, I don’t think it really makes any difference from an AI perspective.

Now I understood your other answer. I did fix the feat.2da and with the TonyK’s AI the Paladin is using Divine Might.

Thank you.

You can run the last script anywhere or as a part of any other script (except the first two, obviously). OnSpawn makes sense so the creature is set from the beginning.

It seems to make a difference for vanilla AI. It expects harmful melee talents to be instantaneous, while DM is a buff (and has different target), so the AI keeps “attacking” the enemy with it, essentially locking itself in a loop.

Hello

One more question: oYourNPC is the name of the NPC right? But my NPC’s name has a space, it’s Sir Norik. How do I write that? Sir_Norik?

Thank you.

That’s the name of a variable that is going to hold the in-game reference to the NPC in that script. To be precise, it can hold a reference to any object, but we’re going to assign there the result of GetObjectByTag which returns an object with matching tag.

You could rename oYourNPC to MalfattosHenchman and it will be alright as long as you rename it everywhere in the script or you’ll get compilation error.

We want to make Sir Norik use that custom AI script, so we write:

#include "nwsh_dcr"
void main()
{
                                     // put correct tag below
    object oYourNPC = GetObjectByTag("TAG_OF_SIR_NORIK_HERE");
    NWSH_SetCreatureOverrideAIScript("your_custom_ai", oYourNPC);
}

If Sir Norik is going to run this script, i.e. this script is his OnSpawn, you can reduce it to:

#include "nwsh_dcr"
void main()
{
    NWSH_SetCreatureOverrideAIScript("your_custom_ai", OBJECT_SELF);
}

OBJECT_SELF = whoever is running this script.

Hello

Ok, thank you. The first two scripts didn’t compile. Scripts work even if they don’t compile?

Thank you.

I checked them (copied back from here) and they work.

Order and naming is important:

  1. save first script as nwsh_dcr; this is a “library” script - it is not compiled (and cannot be compiled - no main()) - other scripts that have #include "nwsh_dcr" access code from it when they are compiled
  2. save second script as your_custom_ai - it should compile; if not, post the error message
  3. save third script as anything - this one enables the second as custom AI, just make sure something is going to run it at least once (you could even put it in a conversation with the paladin, at least for testing)

Does this bug exist in NWN2?
And is there some community patch this fix should go into? They’re still doing official patches of EE right?

Hello

This is the error message that appears when I save the script:

your_custom_ai.nss(37): ERROR: UNDEFINED IDENTIFIER (NWSH_DetermineCombatRoundExit)

Thank you.

Hello

I don’t remember if there is a problem with Divine Might used by AI in NWN 2 because it’s been a long time since I have played. What you could do is try to find a trainer module and see if it happens.

Yes, Beamdog is still releasing patches for the Enhanced Edition.

@kevL_s is this in your wheelhouse?

Did you keep the #include lines in your scripts: #include "nwsh_dcr"?

and did you save the first one as nwsh_dcr?

@Vandervecken nope, i know nuuthingk /schultz

I’d guess if it works in Tony AI for nwn it works in Tony AI nwn2 though.

 
@TonyK