Aller au contenu

Photo

Attack Target Theory


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

#1
DragonTayl

DragonTayl
  • Members
  • 34 messages
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! 

#2
wyldhunt1

wyldhunt1
  • Members
  • 246 messages
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 .


#3
wyldhunt1

wyldhunt1
  • Members
  • 246 messages
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 .


#4
Lightfoot8

Lightfoot8
  • Members
  • 2 535 messages
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

#5
Shadooow

Shadooow
  • Members
  • 4 470 messages

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)

#6
wyldhunt1

wyldhunt1
  • Members
  • 246 messages
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 .


#7
DragonTayl

DragonTayl
  • Members
  • 34 messages
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.

#8
wyldhunt1

wyldhunt1
  • Members
  • 246 messages
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 .


#9
DragonTayl

DragonTayl
  • Members
  • 34 messages
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?

#10
Lightfoot8

Lightfoot8
  • Members
  • 2 535 messages

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.  

#11
wyldhunt1

wyldhunt1
  • Members
  • 246 messages
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 .


#12
wyldhunt1

wyldhunt1
  • Members
  • 246 messages
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 .


#13
Lightfoot8

Lightfoot8
  • Members
  • 2 535 messages
@ 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 .


#14
wyldhunt1

wyldhunt1
  • Members
  • 246 messages
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 .


#15
DragonTayl

DragonTayl
  • Members
  • 34 messages
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.

#16
Shadooow

Shadooow
  • Members
  • 4 470 messages

wyldhunt1 wrote...

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.

To clarify and maybe justify myself :) -> reason why I suggested this is that if OP would actually decided to do modifications in core AI, then it would be better for him to start with version from 1.70 which fixes lots of weirdiness and bugs. If he would do that based on 1.69 includes it would later overwrited and took priority over the 1.70 version. As I said im not sure what is OP want to do whether its solely for his PW or he wants to do something more global. If the second it would be probably better to base it on the includes from 1.70 - and BTW you don't have to install patch for that at all. All scripts, libraries and 2das for custom content building can be downloaded in "builders resources" package.

Modifié par ShaDoOoW, 20 décembre 2011 - 02:47 .


#17
wyldhunt1

wyldhunt1
  • Members
  • 246 messages
If this is something that you don't mind waiting on for about a month or so, I can work something like this in to my custom AI. It would be an interesting addition to it.
My AI is not released on the vault. It was made for our old PW, Ariochus. It does replace the entire AI system because I decided that the default scripts were so full of stupid logic that it would be faster to start over than to fix it all. It's a mashup of every decent AI script that I've found(CODI, Olander, Jasperre's, Tonyk's, Legacy NWN... etc). I took all the good functionality out of them all and put all the best parts in to a single AI... Then added my own stuff to it, did some chanting, waved a dead chicken, and it worked.
Anyway, I can probably add a proper threat based targeting system to it and give it to you if you like... It just may be a month or so. I'm in the process of integrating all of the old Ariochus scripts with the new CEP/PRC. So, it'll be a bit...
That, and I'm not sure how modular it is. I'll have to see how many of my subsystems it makes use of and maybe do some simplifying to get it extracted in a useable form...
<Shrug> If you're interested, I'll see what I can do. I may or may not manage a version that you can import easy.

Since I've no idea if I can even craft a modular version, you're probably best using an item or conversation based command like you're planning on for now though.
If I do manage it and you're interested, I'll let you know.

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


#18
Lightfoot8

Lightfoot8
  • Members
  • 2 535 messages
@Dragon:  No you have not pisses any one off.  But Trying to modify the Bioware include files can introduce many unforseen bugs.  The includes are used by many of the bioware functions and chaseing down all of the scripts that use them to recompile them all in not an easy task.  And can lead to hours of bug hunting.   No one should mess with a making a  Custom AI unless they are willing to spend the hours in getting it right.  
Now what I am sugesting is in my opion not a custom AI.   Is it Custom functionality,  Yes.  It is a Standard hook into the Standard IA.   Leaving the Standard AI untoched and hooking into it with the Tools that Bioware set in Place.      



Now i do not know if the Standard hook will work in your module, It sounds like you are using a custom AI.  So it basicly comes down to if its creater left the hooks in place of not.  Or if they did not follow the General rule of calling a centeral function for all combat actions, It can compound the ability to change the AI behavior.  

Here are the three scripts that would work with the standard AI.   One include, One TagBased Script for the widget and one AI hook.

include:  inc_cmd_underlin

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

  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);
  }
 
 int ScriptBypassSecondRun()
 {
    return GetLocalInt(OBJECT_SELF,UNDERLING_AI_BYPASS);
 }

void SetScriptBypass()
{
   SetLocalInt(OBJECT_SELF,UNDERLING_AI_BYPASS,TRUE);
}

void RemoveScriptByPass()
{
   DeleteLocalInt(OBJECT_SELF,UNDERLING_AI_BYPASS);



Tag Based Script

#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 get His perferred target from the Local stored
   // on the widget and set The AI override script.
   if (GetMaster(oTarget) == oUser)
   {
      SetUnderlingTargetFromWidget(oTarget,oWidget);
      SetCreatureOverrideAIScript(oTarget,UNDERLING_AI_OVERRIDE);
   }
   else
   {
      SetTargetOnWidget(oTarget,oWidget);
      string sName;
      if (GetIsObjectValid(oTarget)) sName = GetName(oTarget);
      else sName = "INVALID";
      SendMessageToPC(oUser,sName + " stored as target on widget" );
   }
}   


AI Hook: underling_ai  // or what ever you change the name to in the include.

#include "x2_inc_switches"
#include "inc_cmd_underlin"
#include "NW_I0_GENERIC"
void main()
{
   //This is needed encase we called DeterminCombatRound from this script
   //to change the target. without it an we would get an endless loop.
   if(ScriptBypassSecondRun()) return;

   //Get Our Targets
   object oMastersCommand = GetLocalObject(OBJECT_SELF,UND_TARGET);
   object oCurrentTarget =  GetCreatureOverrideAIScriptTarget();

   // If our commanded target is not valid there is nor reason to even run this
   // script again, remove the hook from the AI and return.
   if (!GetIsObjectValid(oMastersCommand))
   {
      SetCreatureOverrideAIScript(OBJECT_SELF,"");
      return;
   }
   // Are we already attacking our intended target?
   if (oMastersCommand == oCurrentTarget) return;

   // Our target is not the target we where commanded to attack Can we even see it?
   if(GetObjectType(oMastersCommand) != OBJECT_TYPE_CREATURE || GetObjectSeen(oMastersCommand))
   {
      // We reset the target by recalling the DCR function.  we do not
      // want this script to run again when we do.
      SetScriptBypass();
      DetermineCombatRound(oMastersCommand);

      // Ok the target has been changed. DetermineCombatRound has alrady ran its
      //cource no reason for it to continue from here.
      SetCreatureOverrideAIScriptFinished();

      // remove the bypass so this script will again run on the next call to DCR.
      RemoveScriptByPass();
   }
}  



#19
DragonTayl

DragonTayl
  • Members
  • 34 messages
Well phew. I'm glad I haven't upset anyone because I find these threads absolutely fascinating. Seeing how (forgive me for applauding you all) masters work it out helps *tremendously* when trying to come to a good solution.

I think there is one thing I'm not sure I have everyone seeing, though I think some of us are all working the same way:

I am not trying to affect henchmen. What I'm trying to do is in multiplayer, let's say you are playing alongside two or three of your friends, if you want to make the monster you're fighting as a group focus on a specific PC, what the best way is to do that. How do you flag the monster to target a specific PC. I think the widget may be the best way because that's how the PC can introduce themselves into the code. It takes a standard action, they've targetted the monster with their widget, and the widget uses DetermineCombatRound() passing the activating PC as the monster's target.

I've looked through the AI and I'm reasonably sure that for the most part the monster doesn't change targets often. I'm just hoping that by running DetermineCombatRound() I can set the monster's "object" to be the PC in question, at least for a few rounds.

#20
wyldhunt1

wyldhunt1
  • Members
  • 246 messages
You can do that with an item that has whatever that cast unique spell power is called (I don't build in the toolset much... :P) and have it run a script that calls
ClearActions();
AssignCommand(oTargetedObject, DetermineCombatRound(oPC));

or just a DoCommand if you don't want to lose their current action stack... Shouldn't matter in combat though.
That'll force the monster to attack the player who used the item.

However, you could make it server wide and automatic fairly easy... A lot of it could be handled by an AI override probably.
The parts that I mentioned in my post about what I would do doesn't actually consist of very much code. If you have a decent amount of experience, I bet you could make a system that is trully great in a few days or less.

Begin by deciding what actions should make a player gain threat...
Example:
Attacking: +1 Threat
Damaging Melee: DamageInflicted/5
Damaging Spell: TotalDamageInflicted/5
OtherHarmfulSpell: SpellLevel (Things like Sleep that are offensive but no damage)
BuffsOnFriendlies: SpellLevel

Now, take a look at the creature events that would handle those things. That list would require OnAttacked, OnDamaged, and OnSpellCastAt.
Add a bit of code that adds the correct amount to a variable on the PC = to whatever is on your list. This will probably only wind up being a few lines of code in each of the events.

The exception is the Buffs on Friendlies. That would require small bit of code in a spell hook, which can be done in the default scripts in much the same way as the AI override. If you don't know how to hook spells, it's easy once you've done it.

Add a bit of code to the modules hb event that cycles through each PC and subtracts 1 or 2 points from their threat score (Minimum 0, of course).

Use an AI Override script like Lightfoot's, or replace the targetting code I mentioned.
The targetting code should cycle through each percieved PC and check their threat variable.
Whomever has the highest threat score is set as the NPC's combat target(Set the target the same way as the original code, just choose the target differently)

Add a line to the OnCombatRoundEnd or your AI Override script that deletes your old target each round. This will force them to allways attack whomever has the highest threat rating.

That'll give you a basic and universal threat based targetting system that'll work way better than the default for, probably, less than 50 lines of code.

Once you have that, you can start to think of ways to improve it and add new bits.

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