Hooks to change behavior of HumanNPC.IsTarget and HumanNPC.IsFriendly

Is it possible?

OnNPCTarget only allows to control targetting entities after the NPC already considered them as a target based on IsTarget and IsFriendly.

It would be cool to be able to control which entities a NPC considers a target or friendly, including other HumanNPCs

Looking around, it likely wouldn't be OnNpcTarget, but a different hook.

@Wulf

Yeah, my problem is with the IsThreat and IsTarget implementations in the HumanNPC monobehavior. IsTarget is hardcoded to ignore any entity that isNPC and only targets players or pets

And IsFriendly is even worse comparing prefabIDs to decide if is friendly or not.

public bool IsThreat(BaseEntity entity)
    {
        return IsTarget(entity);
    }

    public bool IsTarget(BaseEntity entity)
    {
        if (entity is BasePlayer && !entity.IsNpc)
        {
            return true;
        }

        if (entity is BasePet)
        {
            return true;
        }

        return false;
    }

    public bool IsFriendly(BaseEntity entity)
    {
        if (entity == null)
        {
            return false;
        }

        return entity.prefabID == prefabID;
    }

 

I'd like to do something like this but it is not possible because IsTarget is not virtual

public class FactionNPC : HumanNPC
        {
                 public override bool IsTarget(BaseEntity entity)
                {
                          //change behavior
                }
        }

I know that I can inherit from NPCPlayer instead but the work to create a fully functional AI would be massive compared to just changing this specific HumanNPC check if it is target or friendly

It is possible to override it with an existing or new oxide hook or should I go for the NPCPlayer journey?

Added a hook that should handle what you want.

object OnNpcTargetSense(BaseEntity npc, BaseEntity targetEntity, AIBrainSenses brainSenses)

Thank you very much!

Merged post

@Wulf I tested this new hook in combination with OnNpcTarget (both returning false) and I still get attacked by the bots that I spawn with my command. They don't attack when they spawn but they attack if you hit them.

I tried to manipulate AIBrainSenses returned by OnNpcTargetSense and it gives a NRE

Also the OnNpcTargetSense never triggers between Npcs so they never sense each other and therefore it can't be used to make bot vs bot fights (unless I create a custom AI)

Here is a video with debug logs: https://youtu.be/qmtdc7tcJOg

Here is the code:

//Command /recruit
void OnRecruitHereCommand(User user)
        {
            Faction faction = Factions.GetByMember(user);
            if (faction == null || !faction.HasLeader(user))
            {
                user.SendChatMessage(Messages.NotLeaderOfFaction);
                return;
            }
            var npc = (global::HumanNPC)GameManager.server.CreateEntity("assets/rust.ai/agents/npcplayer/humannpc/scientist/scientistnpc_roam.prefab", user.transform.position, UnityEngine.Quaternion.identity, false);
            if(npc)
            {
                npc.gameObject.AwakeFromInstantiate();
                npc.Spawn();
                 //Adds an empty monobehavior just to identify bots for now
                Recruit recruit = npc.gameObject.AddComponent<Recruit>();
                var nav = npc.GetComponent<BaseNavigator>();
                if (nav == null)
                    return;
                nav.DefaultArea = "Walkable";
                npc.NavAgent.areaMask = 1;
                npc.NavAgent.agentTypeID = -1372625422;
                npc.NavAgent.autoTraverseOffMeshLink = true;
                npc.NavAgent.autoRepath = true;
                npc.NavAgent.enabled = true;
                nav.CanUseCustomNav = true;
            }
        }

//Hooks
        object OnNpcTargetSense(BaseEntity npc, BaseEntity targetEntity, AIBrainSenses brainSenses)
        {
            var recruit = npc.GetComponent<Recruit>();
            if(recruit != null)
            {
                Puts("is recruit and OnNpcTargetSense return false!");
                var targetRecruit = targetEntity.GetComponent<Recruit>();
                if (targetRecruit != null)
                    Puts("### target is also Recruit!!!");
                
                return false;
            }
            return null;
        }

        object OnNpcTarget(BaseEntity npc, BaseEntity entity)
        {
            var recruit = npc.GetComponent<Recruit>();
            if (recruit != null)
            {
                Puts("is recruit and OnNpcTarget return false!");
                return false;
            }
            return null;
        }

I thought it might NRE in cases, so I'll likely need to adjust it.

Is the null AIBrainSenses related to bots getting aggro when hurt even with the hook returning false?

There are no errors while the hook is being called and returning false, the NRE only happens if I try to access AIBrainSenses

It seems that are two separated problems but im not sure.

i7xeihk9MvxhKpA.png evict

Is the null AIBrainSenses related to bots getting aggro when hurt even with the hook returning false?

There are no errors while the hook is being called and returning false, the NRE only happens if I try to access AIBrainSenses

It seems that are two separated problems but im not sure.

The AIBrainSenses may be null, but unsure exactly why it may be null in cases. I did bump the hook index up in the latest Oxide build as some aspects may not have been covered, so let me know if that resolves anything.

@Wulf I tested again with the new patched version and the problem still exists. I found out that the NRE only occurs when the bot gets hurt. The hook OnNpcTargetSense triggers once per second normally and returns brainSenses correctly but when you hit the bot the hook fails with the brainSenses NRE.

Failed to call hook 'OnNpcTargetSense' on plugin 'Imperium v1.11.0' (NullReferenceException: Object reference not set to an instance of an object)
at Oxide.Plugins.Imperium.OnNpcTargetSense (BaseEntity npc, BaseEntity targetEntity, AIBrainSenses brainSenses) [0x00013] in <af120c57cc7d4ddaa41d7c9a2bad77ec>:0
3kb/s in, 8kb/s out
at Oxide.Plugins.Imperium.DirectCallHook (System.String name, System.Object& ret, System.Object[] args) [0x01ee0] in <af120c57cc7d4ddaa41d7c9a2bad77ec>:0
at Oxide.Plugins.CSharpPlugin.InvokeMethod (Oxide.Core.Plugins.HookMethod method, System.Object[] args) [0x00079] in <60c318df79ed41688ea59335e48d61ad>:0
at Oxide.Core.Plugins.CSPlugin.OnCallHook (System.String name, System.Object[] args) [0x000d8] in <50629aa0e75d4126b345d8d9d64da28d>:0
at Oxide.Core.Plugins.Plugin.CallHook (System.String hook, System.Object[] args) [0x00060] in <50629aa0e75d4126b345d8d9d64da28d>:0


Merged post

public override void Hurt(HitInfo info)
    {
        if (base.isMounted)
        {
            info.damageTypes.ScaleAll(0.1f);
        }

        base.Hurt(info);
        BaseEntity initiator = info.Initiator;
        if (initiator != null && !initiator.EqualNetID(this))
        {
            Brain.Senses.Memory.SetKnown(initiator, this, null);
        }
    }

Probably this call in HumanNPC class calling SetKnown passing null

Right, the brainSenses would potentially be null in this case, as it only seems to be utilized in some cases.

So, is the hook behaving as you expected or you see a problem?

i thought if I returned false it would completely ignore the SetKnown logic, but when the NPC gets hurt (even without accessing brainSenses and getting a NRE) the NPC sets the player as a threat.