Support NPC

I want to create a support NPC that follows the player and heals and casts buffs on the PC, henchmen and anyone from the NPC’s faction.

I created a cleric using a copy of White Thesta. The problem is, that it doesn’t cast any spell on anyone besides herself. How to fix this?

Hello !
Do you want the NPC to follow your party (like a henchman), or just waiting somewhere like some king of seller, healing you automatically?

Following me around. Btw i already figured out the part about following in another thread.

You probably have to write your own healing AI. Here’s a starter pack.

Put this as NPC’s OnHeartbeat. It will cause it to heal itself, all nearby faction members and PCs. You can experiment with talents to implement buffs, cures, etc.

The NPC needs to know healing spells to make this work.

#include "x0_i0_talent"

void main()
{
    object oTarget;
    location lLocation;

    // Proceed only when idle or walking
    switch(GetCurrentAction())
    {
        case ACTION_INVALID:
        case ACTION_FOLLOW:
        case ACTION_RANDOMWALK:
        case ACTION_MOVETOPOINT:
        {
            break;
        }
        default:
        {
            return;
        }
    }

    // Cycle through all PCs and faction members seen in 20m sphere and
    // heal everyone who has less than 50% HP.

    lLocation = GetLocation(OBJECT_SELF);
    oTarget = GetFirstObjectInShape(SHAPE_SPHERE, 20.0, lLocation, TRUE);

    while(GetIsObjectValid(oTarget))
    {
        if((GetIsPC(oTarget) || GetFactionEqual(oTarget)) &&
            (GetCurrentHitPoints(oTarget) * 2 < GetMaxHitPoints(oTarget)))
        {
            if(TalentHeal(TRUE, oTarget))
            {
                SpeakString("Healing " + GetName(oTarget) + ".");
            }
            else
            {
                SpeakString("I can't heal " + GetName(oTarget) + ".");
            }
            return;
        }
        oTarget = GetNextObjectInShape(SHAPE_SPHERE, 20.0, lLocation, TRUE);
    }
}

Note that writing AI is tough as you need to cover a lot of situations, some of which may not be obvious. For example, AI should prioritize itself (survival), then PC master (when being a hench), then faction memebers. This is not present in this example.

Thank you! I will use your script as a template and refine it to my needs.

I went back to this problem when making an example script for another project. Since the work is already done, I’m leaving this here as an educational resource.

The issue I have is that BW’s TalentHeal always goes for the best healing spell, which makes AI use i.e. Heal regardless of target (overkill, wasteful, you call it). TRUE parameter just makes it heal any damage, not just <50%. It can however find the target itself - no need for GetFirstObjectInShape (but it doesn’t honor LOS).

So, I made a smarter healer script that gradually tries to find the lowest spell level that should match the target. GetTalent below does this magic.

/*
    CREATURE HEARTBEAT SCRIPT
    Caller checks all seen, friendly and alive creatures (not undead)
    and heals closest one with less than 50% of maximum HP.
*/

#include "x0_i0_talent"

const int TALENT_HEAL = TALENT_CATEGORY_BENEFICIAL_HEALING_TOUCH;

// Returns most economical talent in iCategory to be used by oSelf on oTarget.
// Most economical means one that is closest to oTarget's CR. If iBest is TRUE
// then this function returns talent at highest CR. If no talent is available
// in this category, an invalid talent will be returned.
talent GetTalent(int iCategory, object oTarget, int iBest=FALSE, object oSelf=OBJECT_SELF)
{
    talent tTalent;
    int iCR;

    iCR = iBest ? TALENT_ANY : FloatToInt(GetChallengeRating(oTarget));
    do
    {
        tTalent = GetCreatureTalentBest(iCategory, iCR++, oSelf);
    }
    while(!GetIsTalentValid(tTalent) && iCR <= TALENT_ANY);
    return tTalent;
}

void main()
{
    object oTarget;
    talent tHeal;
    int iTarget;

    // Proceed only when idle or walking.
    switch(GetCurrentAction())
    {
        case ACTION_INVALID:
        case ACTION_FOLLOW:
        case ACTION_RANDOMWALK:
        case ACTION_MOVETOPOINT:
        {
            break;
        }
        default:
        {
            return;
        }
    }

    // Start healing target scan with self.
    oTarget = OBJECT_SELF;

    while(GetIsObjectValid(oTarget))
    {
        // Check if target has less than 50% of its maximum HP and
        // is not an undead (too late for healing in this case).
        if(GetCurrentHitPoints(oTarget) * 2 < GetMaxHitPoints(oTarget)
            && GetRacialType(oTarget) != RACIAL_TYPE_UNDEAD)
        {
            // Obtain most economical healing talent for target.
            tHeal = GetTalent(TALENT_HEAL, oTarget);
            // Check if target can be healed (talent is valid).
            if(GetIsTalentValid(tHeal))
            {
                ClearAllActions(TRUE);
                ActionSpeakString("Healing " + GetName(oTarget) + ".");
                ActionUseTalentOnObject(tHeal, oTarget);
            }
            // Return (if target cannot be healed then nobody can be).
            return;
        }
        // Get next target (nearest seen allied creature).
        oTarget = GetNearestSeenFriend(OBJECT_SELF, iTarget++);
    }
}

FYI TALENT_ANY = 20, as defined in x0_i0_talent.