Author Topic: Attack Target Theory  (Read 721 times)

Legacy_DragonTayl

  • Newbie
  • *
  • Posts: 40
  • Karma: +0/-0
Attack Target Theory
« on: December 19, 2011, 06:12:09 pm »


               Greetings!

I am looking to affect how monsters choose their targets (basically implementing a threat system). As a result, I would love to know more about how targets are chosen. Perhaps a threat system isn't required, if I know how the focus changes.

If someone knows the answers, here are the questions I think I need to ask:

1) How is an initial target chosen?

2) Under what circumstances will the target change?

3) Is there functionality for us to force a target to be chosen?

4) Are there some unusual side-effects if we force a target to be chosen?

Thanks! 
               
               

               
            

Legacy_wyldhunt1

  • Sr. Member
  • ****
  • Posts: 443
  • Karma: +0/-0
Attack Target Theory
« Reply #1 on: December 19, 2011, 06:43:50 pm »


               By default, every NPC randomly chooses a hated class. They search for members of that class first, and then just attack the nearest enemy if that fails.
Unfortunately, their hated class changes every time they need a new target so you'd need to change it.

x0_inc_generic is the main script that you want.

This begins on line 238

// Choose a new nearby target. Target must be an enemy, perceived,
// and not in dying mode. If possible, we first target members of
// a class we hate.
object ChooseNewTarget()
{
    int nHatedclass = GetLocalInt(OBJECT_SELF, "NW_L_BEHAVIOUR1") - 1;
    // * if the object has no hated class, then assign it   
// * a random one.   
// * NOTE: classes are off-by-one   
    if (nHatedclass == -1)
    {
        bkSetupBehavior(1);
        nHatedclass = GetLocalInt(OBJECT_SELF, "NW_L_BEHAVIOUR1") - 1;
    }
    //MyPrintString("I hate " + IntToString(nHatedclass));
    // * First try to attack the class you hate the most
    object oTarget = GetNearestPerceivedEnemy(OBJECT_SELF, 1,                                              CREATURE_TYPE_class,                                              nHatedclass);
    if (GetIsObjectValid(oTarget) && !GetAssociateState(NW_ASC_MODE_DYING, oTarget))        return oTarget;
    // If we didn't find one with the criteria, look
    // for a nearby one
    // * Keep looking until we find a perceived target that
    // * isn't in dying mode
    oTarget = GetNearestPerceivedEnemy();
    int nNth = 1;
    while (GetIsObjectValid(oTarget)
           && GetAssociateState(NW_ASC_MODE_DYING, oTarget))
    { 
      nNth++;
        oTarget = GetNearestPerceivedEnemy(OBJECT_SELF, nNth);    }
    return oTarget;
}


It looks like you may be able to hook through the bkSetupBehavior function.
It starts at line 127.

/*
    Behavior1 = Hated class
*/
void bkSetupBehavior(int nBehaviour)
{
    int nHatedclass = Random(10);
    nHatedclass = nHatedclass + 1;
    // for purposes of using 0 as a
                                       // unitialized value
.                                       // will decrement in bkAcquireTarget
    SetLocalInt(OBJECT_SELF, "NW_L_BEHAVIOUR1", nHatedclass);
}


These forums love to do strange things to my pasted code, so it may be all screwey.
               
               

               


                     Modifié par wyldhunt1, 19 décembre 2011 - 07:21 .
                     
                  


            

Legacy_wyldhunt1

  • Sr. Member
  • ****
  • Posts: 443
  • Karma: +0/-0
Attack Target Theory
« Reply #2 on: December 19, 2011, 07:00:24 pm »


               A new target is only chosen when the old target is dead or dying.
You can force a target without changing anything by passing a target object in to the
void DetermineCombatRound(object oIntruder = OBJECT_INVALID, int nAI_Difficulty = 10)
Function.
It's in nw_i0_generic. So, if you want to call it in a custom script, you'll need to #include that script.
               
               

               


                     Modifié par wyldhunt1, 19 décembre 2011 - 07:36 .
                     
                  


            

Legacy_Lightfoot8

  • Hero Member
  • *****
  • Posts: 4797
  • Karma: +0/-0
Attack Target Theory
« Reply #3 on: December 19, 2011, 07:32:50 pm »


               I am not at the house right now,  But what you want to look at is the Special AI.  It is set up by simply placing a script on the creature with the name of the script to run as the AI Override.  

This thread should get you started.   THere are better one's on the boards if you do a search for the constant. 

NPC Combat Behavior - NPC should not attack while fleeing
               
               

               
            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
Attack Target Theory
« Reply #4 on: December 19, 2011, 07:35:10 pm »


               

DragonTayl wrote...

1) How is an initial target chosen?
2) Under what circumstances will the target change?

usually attacker is chosen, but there are two events that force creature to switch:
- if creature is not attacked by anyone but enemies are in sight, she choose nearest target (there seems to be some kind of weird random hated class functinality but I havent noticed this in game at all probably becase creature instantl switched from other reason)
- if current target is dying, invisible, in GS she might switch to nearest enemy
- if you attack creature with ranged weapon or spell, she will try to switch its current target to you but
- if you attack creature with melee while she is not fighting with you she switch immediately to you

3) Is there functionality for us to force a target to be chosen?

not without scripting, but you might tell creature to attack specific target easily via clearallactions and actionattack, however due to the target switching it is not guaranted that she stick with the target you choose - if you want to achieve this you should check current target in creature OnHeartbeat script.

4) Are there some unusual side-effects if we force a target to be chosen?

Excecept player might exploit this fact, then no. (if player finds out creature chase only him, he might summon creature to help him and your monster will ignore this summon and will be subject to many AOO if player will run around summon)

Also, if you aren't NWN guru, you rather stay away from core AI includes like x0_inc_generic. If you change that include you won't see any effect until you recompile these scripts:
nw_ch_ac*
nw_c2_default*
and some special scripts like nw_c2_omnnivore etc.

Not sure what you want to do, but really consider my unofficial patch. I improved the default AI greatly and if you set a creature AI to AI_LEVEL_HIGH (this can be done via custom OnSpawn script that is included in patch) creature will fight might better especially against Black Blade of Disaster. She will ignore it if there is mortal target (yes this makes her vulnerable to the explot I already mentioned but if she have dismissal or MD spell or scroll she dispell blade immediately)
               
               

               
            

Legacy_wyldhunt1

  • Sr. Member
  • ****
  • Posts: 443
  • Karma: +0/-0
Attack Target Theory
« Reply #5 on: December 19, 2011, 07:51:45 pm »


               Any time you alter an include script, it's best to re-compile all of your scripts to ensure that they all see the changes that you made.
Lightfoot is correct that you could make an AI override and it would work.
However, that may be considerably more difficult if you don't understand all of the AI functions.
That system is designed to completely replace a single creature's AI.
It checks for petrification and cancels out if you're petrified. It clears any randomwalk that you're doing.
Then, it calls your custom AI. If you have your variables set to complete the AI in your custom script, then the AI will exit there and assume that your script handled all of it.
If you don't set that variable in your script, then the normal AI will finish running after your script and can override your script with new actions if you don't use all of the default NWN variables and such.

So, a custom AI can be very powerful and good. But, if you are still at a point where you have to ask where to find the targeting code, you may want to hold off on it and do something a bit smaller.
If you only want certain creatures to choose enemies with your code, you can edit their creature event scripts to attack specific enemies via DetermineCombatRound.
If you want a universal targeting change, I'd replace the bkSetupBehavior code and the couple of lines in ChooseNewTarget that change it. That function is useless anyway.
By default, class 9 (Not sure which class that is) will get attacked less often than class 0 during boss fights and grand melee's where a lot of PC's are dying. The whole idea is broken anyway.

And, ShaDoOoW's patch is good. Fixes a lot of stuff...
Not sure that it would help you code custom AI stuff, but the default behavior is better with it.
               
               

               


                     Modifié par wyldhunt1, 19 décembre 2011 - 09:21 .
                     
                  


            

Legacy_DragonTayl

  • Newbie
  • *
  • Posts: 40
  • Karma: +0/-0
Attack Target Theory
« Reply #6 on: December 19, 2011, 09:46:03 pm »


               Thanks for the quick feedback. I am running a custom AI, and I actually was deep in NwN development for six years, but I've been away from it for four, and like anyone there were plenty of gaps in my knowledge.

I can't remember the custom AI but it was popular (I believe). I have done lots of messing with the behavior scripts on characters, and usually do as much as I can in the user defined script to keep all my madness away from the madness of other mad geniuses (lets I cause a bad causality by letting them mingle). Back in the time machine I hadn't played other MMOs and hadn't become enchanted with the threat system - the beauty of tank-healer-dps for group encounters. I love them now. So back then I never looked for how to have a tank hold on to the focus of monsters.

It sounds like, from what I'm hearing, if the tank is careful to attack first, they are going to "keep aggro" throughout the battle (unless there is specific code like was added to some monsters to "jump" or "phase" when at low health). What happens if an over-eager mage or (in my case) archer hits a target first, how does the "tank" pull aggro back off? Is it proximity? How can you, by code, switch the target of a monster? Is that what the ActionAttack does? If there was a SetAttackTarget that would be exactly what I was looking for so that I don't inadvertently ruin the awesome AI that is currently in place (so that special talents are used) by forcing the monster to switch targets but not realizing that ActionAttack causes melee only or something like that.

I am taking a look at ShaDoOoW's patch, I wasn't aware of it (having only just returned) and love the idea of a 1.70 done by the community.

The AI (and I'm searching through the includes for some sort of name that tells me who wrote it) substitutes "HenchDetermineCombatRound()" in which you can pass oIntruder. Is it as simple as controlling that call during the (modified) nw_c2_default3 script? Will the mob attack oIntruder if that's what I pass it? Of course, someone would have to be familiar with the AI I'm using and I can't seem to find who wrote it.
               
               

               
            

Legacy_wyldhunt1

  • Sr. Member
  • ****
  • Posts: 443
  • Karma: +0/-0
Attack Target Theory
« Reply #7 on: December 19, 2011, 09:59:36 pm »


               That's what I was saying above... Probably not clearly enough.
You can pass a target in to DetermineCombatRound or HenchDetermineCombatRound and it'll force them to have a new target.
DetermineCombatRound can be called from multiple creature event scripts. I'd check them all to make sure that you're not sometimes calling the default DetermineCombatRound from hb or OnDamaged, or OnConversation or something.
The notes in the default code state that the target is never replaced unless that target is dead, missing, or dying. When that target dies, the NPC will choose a new "Hated class" which will be their old hated class -1. If there is no one of that class, they'll choose whomever is closest.
However, if their Hated Enemy randomly decides to be a rogue, then they'll attack the rogue no matter what the tank is doing.
That's why I mentioned it. It's a very very stupid way of handling that.

I was away for a few years too. I've been reading all of the old scripts trying to remember what's what.

EDIT:
  Clarified version of my ramblings:
      As long as you pass a valid object in to DetermineCombatRound (Or one of its wrapper functions), the NPC will attack that target and continue to attack that target until it is no longer a valid target (or until you call DetermineCombatRound again with a new target).
      The stupid stupid targetting code only runs when their current target is invalid.
      If you want to change the AI for every NPC in the mod, I suggest replacing the FAIL targetting code that I mentioned above. Otherwise, you'd need to use Biosearcher or something similar to find every instance of DetermineCombatRound in the mod. Or, call a custom AI script on every creature.
      If you only want to change certain monsters, use DetermineCombatRound(oObject);. You'll need to find every instance of that function in all of their event scripts.
               
               

               


                     Modifié par wyldhunt1, 19 décembre 2011 - 10:19 .
                     
                  


            

Legacy_DragonTayl

  • Newbie
  • *
  • Posts: 40
  • Karma: +0/-0
Attack Target Theory
« Reply #8 on: December 19, 2011, 11:04:53 pm »


               Thanks Hunt! I think I've got it. In actuality, since targets don't change of their own accord very often, I'm only trying to handle a few instances when the "tank" wants to re-acquire threat. I can do this with an in-game item, for example, so when the tank sees a monster attacking a different PC, they can target the monster and use this device. If the code on the device calls "DetermineCombatRound" with the tank as the object, that should handle it? Or are there "issues" with calling DetermineCombatRound at what might not be the beginning of a round?
               
               

               
            

Legacy_Lightfoot8

  • Hero Member
  • *****
  • Posts: 4797
  • Karma: +0/-0
Attack Target Theory
« Reply #9 on: December 19, 2011, 11:41:20 pm »


               

wyldhunt1 wrote...

EDIT:
  Clarified version of my ramblings:
      As long as you pass a valid object in to DetermineCombatRound (Or one of its wrapper functions), the NPC will attack that target and continue to attack that target until it is no longer a valid target (or until you call DetermineCombatRound again with a new target).
      The stupid stupid targetting code only runs when their current target is invalid.
      If you want to change the AI for every NPC in the mod, I suggest replacing the FAIL targetting code that I mentioned above. Otherwise, you'd need to use Biosearcher or something similar to find every instance of DetermineCombatRound in the mod. Or, call a custom AI script on every creature.
      If you only want to change certain monsters, use DetermineCombatRound(oObject);. You'll need to find every instance of that function in all of their event scripts.



First of all I am getting tired of you stating that the AI only changes targets if the target is invalid.   Where that is true for the DetermineCombatRound function it is not true for the AI.  You noted the method used by the AI yourself.   If something is wailing away at something there is no reason to stop unless something changes, SO the is no reason for the function to change targets.    If something does change, Like taking a masive damage hit from another creature, then the AI traps it in the event where it happens at, In this case with damage taken in the OnDamaged event,   It is in the Side Events where the AI decides where to change targets or not.   

Secound there is no need to Chase down every use of DetermineCombatRound, the hook is already placed in the function itself, with the Special IA noted before.  it is still the simplest way of modifing the behavor of the AI.   

If you want the AI  to change targets from within the AI it is not that hard to do.   The OP however looks like thy are just wanting to give something a target via widget.    Hmm I think I already worte that one on here let me look real quick.    

Nope cant find it.   
Ahh Let me rewrite it real quick .... Ill post back in a bit.  
               
               

               
            

Legacy_wyldhunt1

  • Sr. Member
  • ****
  • Posts: 443
  • Karma: +0/-0
Attack Target Theory
« Reply #10 on: December 19, 2011, 11:51:27 pm »


               You're not incorrect, Lightfoot. I didn't mean to imply that you were, if I did.
I understood the OP to say that DragonTayl is attempting to replace the targeting system with a system similar to WoW, where every player has a variable to represent how important of a target that PC is to the NPC. The NPC should then always choose whomever has the highest Threat variable when they change targets.
 
Yes. Alternate AI is one way of handling that. However, if you want to ensure that the threat meter is respected in all instances and you are not using a custom ai hook, you would need to check every instance of DetermineCombatRound to ensure that you are passing in a valid and correct threat target. If you don't, then targets may change and use the default targeting AI because of the reason you stated in your response.
My basis for saying that targets only change when the target is invalid is from the comments in the code. Yes, there are other places where DetermineCombatRound can override the target. That's why you'd need to check them all if you used the function to pass a custom target with a custom threat system and you weren't using custom AI..... That was a very long sentence.... You quoted the part where I made that exception in my post.


continue to attack that target until it is no longer a valid target (or until you call DetermineCombatRound again with a new target).

The other events can call DetermineCombatRound with a new target.
 
Replacing the failed target choosing code, which I also posted above, with the threat checking code might be the best way to make a universal threat based targeting system. You can bypass the default targeting code with DetermineCombatRound, or alternate ai... Or, you can just replace it with code that works and not worry about custom ai or DetermineCombatRound.
 
That was my point.
Yes. Custom AI would work fine also. Just sharing different options.


It shouldn't be a problem to execute DetermineCombatRound at any time. It'll re-check all of the combat variables and re-build their action list. Although, Lightfoot is probably better than me at pointing out potential unintended effects like that.
I'm still getting back in to coding for Aurora, myself...
The other creature event scripts may still override it with calls to DetermineCombatRound, however. So, you may want to find a way to ensure that they stick to their target, or edit the other event scripts to make sure they respect your target.
Lightfoot's idea of using an ai hook could check for changed targets and re-run the custom code that way too, if you decide to go that rout.
Personally, I'd replace the target finding code with a threat based code. I'd delete their target at the begining of each round (It's just a local object on the npc) and allow the npc to re-choose their target each round to ensure that they are attacking whomever is the highest threat.
Then I'd alter the OnDamaged and OnSpellCastAt events to add points to the attackers threat when they hurt the NPC or cast offensive spells at them...
And if I wanted to get really fancy, I'd have the targetting code check for local variables on the NPC which would give that NPC a preference for killing certain classes. That way, you could have rogues tend to attack mages first unless the tank is pulling a lot of aggro....
</rambling>
               
               

               


                     Modifié par wyldhunt1, 20 décembre 2011 - 12:46 .
                     
                  


            

Legacy_wyldhunt1

  • Sr. Member
  • ****
  • Posts: 443
  • Karma: +0/-0
Attack Target Theory
« Reply #11 on: December 20, 2011, 12:40:28 am »


               If it's for Henchmen, I'd do it the same way, but PC's don't have an OnDamaged event by default. So, I'd have to hook it some way to check it each round... Probably in the ai scripts or the hb event and add threat to the NPC that way.

EDIT: And, add a line to the hb event to decrease their threat each round to avoid a fighter pulling aggro while they run around in circles.
               
               

               


                     Modifié par wyldhunt1, 20 décembre 2011 - 01:20 .
                     
                  


            

Legacy_Lightfoot8

  • Hero Member
  • *****
  • Posts: 4797
  • Karma: +0/-0
Attack Target Theory
« Reply #12 on: December 20, 2011, 12:56:45 am »


               @ Hunt1
  My last argument on this,  Just so we don't clutter the thread to much, bickering about thing that are not going to help the OP.   The point I was trying to make is that the AI hook is  in DetermineCombatRound ();   There fore it is your catch all for every call to DetermineCombatRound ()   it allows the scripter to catch all combat and control or tweek it.   That is untill people modfy the system by bypassing the call to the DetermineCombatRound ();   
Your statment that " The other creature event scripts may still override it with calls to DetermineCombatRound"  is incorrect because the other event are now calling your script through that function.


Anyway I have to run out of for a little bit.  I have not gotten to the AI portion of the widget script yet.  I have just done the Widget portion.  I will go ahead and post that part now so you can see the direction I am going.  I will try and get the AI part done as soon as I gat back and post it then.

Include script: inc_cmd_underlin 

 
 const string UNDERLING_AI_OVERRIDE = "underling_ai";
 const string UND_TARGET            = "und_target";

  void SetUnderlingTargetFromWidget(object oSlave,object oWidget)
  {
     object oTarget = GetLocalObject(oWidget,UND_TARGET);
     SetLocalObject (oSlave,UND_TARGET ,oTarget);
     if (GetIsObjectValid(oTarget))
       FloatingTextStringOnCreature("Me Kill " + GetName(oTarget), oSlave);
     else
       FloatingTextStringOnCreature("Me Kill By own choice" , oSlave);
  }

  void SetTargetOnWidget(object oTarget,object oWidget)
  {
    SetLocalObject(oWidget,UND_TARGET, oTarget);
  }


Widget TBS

#include "x2_inc_switches"
#include "inc_cmd_underlin"
void main()
{
   if (GetUserDefinedItemEventNumber() != X2_ITEM_EVENT_ACTIVATE) return;

   object oTarget = GetItemActivatedTarget();
   object oWidget = GetItemActivated();
   object oUser  = GetItemActivator();

   // If the target is an underling. Set His perferred target from the Local stored
   // on the widget and The AI override script.
   if (GetMaster(oTarget) == oUser)
   {
      SetUnderlingTargetFromWidget(oTarget,oWidget);
      SetCreatureOverrideAIScript(oTarget,UNDERLING_AI_OVERRIDE);
   }
   else  // Set the Target on the widget.
   {
      SetTargetOnWidget(oTarget,oWidget);
      string sName;
      if (GetIsObjectValid(oTarget)) sName = GetName(oTarget);
      else sName = "INVALID";
      SendMessageToPC(oUser,sName + " stored as target on widget" );
   }
}


               
               

               


                     Modifié par Lightfoot8, 20 décembre 2011 - 12:59 .
                     
                  


            

Legacy_wyldhunt1

  • Sr. Member
  • ****
  • Posts: 443
  • Karma: +0/-0
Attack Target Theory
« Reply #13 on: December 20, 2011, 01:11:57 am »


               I still agree with you, Lightfoot.
This is why so many of my comments include "and if you're not using an ai hook...".
I'm only refering to the alternate ways of doing it. I'm leaving the alternate ai stuff to you since you know more about it.
Like I said, I'm just giving alternatives, with their drawbacks, such as having to check every instance of DetermineCombatRound when you're not using custom ai.


wyldhunt1 wrote...
You can bypass the default targeting code with DetermineCombatRound, or alternate ai


wyldhunt1 wrote...
Otherwise, you'd need to use Biosearcher or something similar to find every instance of DetermineCombatRound in the mod. Or, call a custom AI script on every creature.

 

wyldhunt1 wrote...
if you want to ensure that the threat meter is respected in all instances and you are not using a custom ai hook, you would need to check every instance of DetermineCombatRound

 

wyldhunt1 wrote...
That's why you'd need to check them all if you used the function to pass a custom target with a custom threat system and you weren't using custom AI.....

 

wyldhunt1 wrote...
Yes. Custom AI would work fine also. Just sharing different options.


@DragonTayl
Out of curiosity, was your original idea to do something similar to the WoW threat system?
If so, the item based thing may not be the best way to do it.
In fact, if this is just a way to allow the player to command his henchmen to defend any players actively being attacked, it might be easier to hook it through the OnConversation event and either use the Guard Me command or a custom chat command that the players could hot slot.
You could place the entire item script, including setting a custom ai, in the OnConversation.
It would free up an item slot if nothing else.
               
               

               


                     Modifié par wyldhunt1, 20 décembre 2011 - 02:21 .
                     
                  


            

Legacy_DragonTayl

  • Newbie
  • *
  • Posts: 40
  • Karma: +0/-0
Attack Target Theory
« Reply #14 on: December 20, 2011, 02:35:07 am »


               I haven't played WoW, but I imagine it is similar to the other MMOs out there (I played EQ and RIFT). While I wouldn't mind a full blown threat system like that, I honestly don't have the time to do it justice, so I was looking for other ways for the "tank" role to maintain threat. This isn't actually for controlling henchmen, but monsters or creatures. "mobs" in the MMO vernacular (though I don't really like that term).

What I think is being said (and what I'm seeing in the customized AI scripts I downloaded all those years ago) is:

1) A monster picks its original target based largely on proximity or by being attacked.
2) Currently (unless I coded my own AI to contrary, which I haven't) monsters rarely change their target. They like to beat on something until it's dead and move on.
3) Occasionally there is code for a monster to change to the weakest target in the group, the only time I've seen this invoked is certain monsters, when they reach low health, will jump (or phase) to a new target).
4) Another instance of unusual target picking is, I believe, the beholders in the expansion OCs, where they would actually leave and return and might be attacking someone else due to proximity (but even there I can't remember clearly if they simply attacked their old target).
5) So if I wanted to handle the rare exceptions when a monster has targetted someone other than the "tank" I could have an Cast Spell: Unique Power item that the tank can use to re-run DetermineCombatRound() where the tank is passing itself as the target object.

In the mean time I appear to have fallen from the sky onto some sort of rotating, air-movement device. Debate is certainly healthy, but I hope I have not offended someone because I didn't realize they had a really cool tool ready to use and I'm trying to reinvent their wheel. Believe me, if I can use someone else's wheel, I will. I would love to spend more time coding, there are so many cool ideas to add, but sadly I must limit my code time to waking hours not already purchased by my living.