Author Topic: special combat AI script  (Read 579 times)

Legacy_acomputerdood

  • Sr. Member
  • ****
  • Posts: 378
  • Karma: +0/-0
special combat AI script
« on: April 27, 2012, 11:53:05 pm »


               ok, i'm stumped.  i've got a special combat AI script set to the variable on an NPC and it will only fire once.  i thought it was supposed to happen once each round.  the script:


 #include "wr_musket"


void main(){//    object oTarget = GetLocalObject(OBJECT_SELF, "X2_NW_I0_GENERIC_INTRUDER");//    object oTarget = GetLastPerceived();    object oTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY);    SpeakString("special combat script.  target: " + GetName(oTarget));

    if(GetIsEnemy(oTarget)){                SpeakString("enemy");                SpeakString("firing at: " + GetName(oTarget));
                SpeakString("FIRE!");    }    SetLocalInt(OBJECT_SELF, "X2_SPECIAL_COMBAT_AI_SCRIPT_OK", 1);}


what gets me is that all of the speakstrings work, but only once.  after that first round of combat, the NPC just stands there looking at the PC.  i've tried all of the above object assignments that were commented out, but it shouldn't matter because the entire script only runs once.


am i missing something here?
               
               

               
            

Legacy_henesua

  • Hero Member
  • *****
  • Posts: 6519
  • Karma: +0/-0
special combat AI script
« Reply #1 on: April 28, 2012, 03:30:02 am »


               I don't know if this is the case in your instance, BUT the special combat AI script is configurable to only run once. Perhaps that is what you are telling it when you are setting that int X2_SPECIAL_COMBAT_AI_SCRIPT_OK

That might be the var that tells the special AI to only run once. Can't remember though if that is it or if there is another var that sets that up.
               
               

               
            

Legacy_Lightfoot8

  • Hero Member
  • *****
  • Posts: 4797
  • Karma: +0/-0
special combat AI script
« Reply #2 on: April 28, 2012, 03:39:28 am »


               X2_SPECIAL_COMBAT_AI_SCRIPT_OK is telling it not to run the rest of the Combat AI, basicly saying that the Special AI script has already made all of the needed decisions and nothing else need to be done this combat round.    

The Special AI script runs every time DeterminCombatRound Is called.   So it is more like a case of the other Events the NPC has attached to him and wether he even enters into combat mode,  never being given a target by the Special AI script.  

The Special AI does not run every round.  It runs every combat Round.  

The question is what are you tring to do and what scripts do you have attached to the other events on the NPC.
               
               

               
            

Legacy_henesua

  • Hero Member
  • *****
  • Posts: 6519
  • Karma: +0/-0
special combat AI script
« Reply #3 on: April 28, 2012, 04:09:46 am »


               Hmm... I'll have to look at my scripts again then. Perhaps I added that to my own AI - the ability to cancel the special AI after one run through.
               
               

               
            

Legacy_acomputerdood

  • Sr. Member
  • ****
  • Posts: 378
  • Karma: +0/-0
special combat AI script
« Reply #4 on: April 28, 2012, 01:00:54 pm »


               so lightfoot, your guess is that somehow the NPC is leaving combat?  that would really be the only possible explanation of why my special script isn't called on the next combat round.

even if my PC is there wailing on the guy, he still just sits there after the first round.


i guess i'll check some of my other scripts.
               
               

               
            

Legacy_acomputerdood

  • Sr. Member
  • ****
  • Posts: 378
  • Karma: +0/-0
special combat AI script
« Reply #5 on: April 28, 2012, 05:45:18 pm »


               i cheated and just used the special combat script to set the target and then had a heartbeat script look for the target and attack each round.

i couldn't figure out why the combat script wasn't working each round.
               
               

               
            

Legacy_Lightfoot8

  • Hero Member
  • *****
  • Posts: 4797
  • Karma: +0/-0
special combat AI script
« Reply #6 on: April 28, 2012, 06:50:27 pm »


               

acomputerdood wrote...

i cheated and just used the special combat script to set the target and then had a heartbeat script look for the target and attack each round.

i couldn't figure out why the combat script wasn't working each round.



Well that is why I was asking what you where trying to do.   If you are just trying to controll the target  To where the nearest hostile is always being attacked, try something like this.  

 #include "wr_musket"
 #include "nw_i0_generic"

void main()
{  
   object oTarget = GetLocalObject(OBJECT_SELF, "X2_NW_I0_GENERIC_INTRUDER");
   object oNewTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY);   

    SpeakString("special combat script.  target: " + GetName(oTarget) + "NewTarget = " + GetName(oNewTarget) );

    if(GetIsEnemy(oTarget))
    {               
       SpeakString("enemy");               
       SpeakString("firing at: " + GetName(oTarget));
       SpeakString("FIRE! // Only if closest target");
         
    }   
    if( GetIsValid(oNewTarget)  &&  oTarget != oNewTarget)
    {
       SpeakString("changing Targets");
       DetermineCombatRound(oNewTarget); 
       SetLocalInt(OBJECT_SELF, "X2_SPECIAL_COMBAT_AI_SCRIPT_OK", 1);
     }
}


It may take a little bit to understand what is going on there.  If you have trouble understanding it after giving it a little though, Post back and I will try and explain it in more depth.  
               
               

               
            

Legacy_acomputerdood

  • Sr. Member
  • ****
  • Posts: 378
  • Karma: +0/-0
special combat AI script
« Reply #7 on: April 29, 2012, 12:23:10 am »


               yeah, i get that, and it's essentially what i was trying to do.  the problem i had (and could never figure out) was that the script was only being run on the first round of combat.  it never got called again.

from reading other posts, i'm guessing that the problem is that somewhere the NPC's action queue was being canceled, adjusted, or cleared, which would remove him from the combat state.  this would cause the script to not run again.

and since the onperception thing (that calls determinecombatround) only runs the first time he sees somebody, he would never re-enter combat until the PC hid and reappeared.


i think i'm correct about all that, yes?
               
               

               
            

Legacy_Lightfoot8

  • Hero Member
  • *****
  • Posts: 4797
  • Karma: +0/-0
special combat AI script
« Reply #8 on: April 29, 2012, 01:17:38 am »


               Well, I would have to dig back into the AI again to fully answer that.  Since I don'r really have the time for that right now, I will just give what my faulty memory is giving me to say.   

 If memory serves me right, DetermineCombatRound  is a recursive function, meaning that It is the main driving force behind calling itself back up.   With the special AI script that you originaly wrote, It spoke a couple of strings and then stopped the rest of the function from running.   This gave a state where nothing happened to ever cause or trigger the  Function to be called again.  With nothing happening combat basicly stopped for the NPC just as fast as it started.   He did nothing, Simply because he was never told to do anything.  

To fully understand the script that I posted above, You need to understand that the Special AI Script is called from within the DetermineCombatRound  Function.    This is important  because when the script calls  DetermineCombatRound  itself,  it is bascly calling itself to be ran again( A recursive script you could say).   With the script calling itself again however you have to be carfull not to create an endless loop that never terminates. 
With that in mind lets look at what the script does. Here it is again with the excess trimmed out.   

void main()

   object oTarget = GetLocalObject(OBJECT_SELF, "X2_NW_I0_GENERIC_INTRUDER");
   object oNewTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY);  
     if( GetIsValid(oNewTarget)  &&  oTarget != oNewTarget)
    {
       DetermineCombatRound(oNewTarget);
       SetLocalInt(OBJECT_SELF, "X2_SPECIAL_COMBAT_AI_SCRIPT_OK", 1);
     }
}

1) We simply have one compairson To check if the Target we are attacking is the Target we want to be attacking,  with an extra precaution check that target we want to attack is valid.  

2) if the Current target is the that we have decided is the target we want to be attacking, the script does nothing and simply returns control to  DetermineCombatRound to attack the currently correct target.  

3)  If the Target is not the Target we want it to attack.   It changes the target by calling DetermineCombatRound with the correct target as a argument.   This will in effect call this script again also.    On the second calling of this script however the old and new target will match allowing the second run of the script to simply fall through and having DetermineCombatRound take over with the correct target.

4)  If the targets did not match the first time the script was ran, after second call to DetermineCombatRound finishes ( The Call where we changed the target,  made from the Special AI script)  script excution returns to the line code following our call to DetermineCombatRound, At this point we set the X2_SPECIAL_COMBAT_AI_SCRIPT_OK flag to stop the DetermineCombatRound function from running again, since we have just ran it and already let it set up everything needed for that combat round.   

Since we have let the DetermineCombatRound function decide how to handle the combat round, with the correct target,   We can rest assured that combat will continue next round unless combat is over.

I hope I did a fair job of explaining that.  Understanding recursive function is often one of the harder concepts to comprehend in code.
               
               

               


                     Modifié par Lightfoot8, 29 avril 2012 - 12:19 .
                     
                  


            

Legacy_Lightfoot8

  • Hero Member
  • *****
  • Posts: 4797
  • Karma: +0/-0
special combat AI script
« Reply #9 on: April 29, 2012, 01:47:44 am »


               Thinking that you or others may need little more help in understanding the concept.   Here is an example of a simple recursive function to study.   

To test it out create a placeable container ( a chest)   and place this script in its OnOpen event. 


 void CountToTenAndBack(int nCount=0)
{
  object oPC = GetLastOpenedBy();

  nCount++;

  SendMessageToPC(oPC,IntToString( nCount));
 
  if(nCount < 10) CountToTenAndBack(nCount);
 
  SendMessageToPC(oPC,IntToString( nCount));
}


void main()
{
   CountToTenAndBack();
}
 
               
               

               


                     Modifié par Lightfoot8, 29 avril 2012 - 12:48 .
                     
                  


            

Legacy_acomputerdood

  • Sr. Member
  • ****
  • Posts: 378
  • Karma: +0/-0
special combat AI script
« Reply #10 on: May 01, 2012, 10:05:20 am »


               ok, i thought i could just let this whole topic go because i started using a heartbeat script to "attack" once per combat round. i know it's not directly linked to the combat round, but the hb should trigger about once every 6 seconds, so that's close enough.

anyway, i went with that, but now the NPC is running BOTH its special combat script and the heartbeat script. what's worse is that before it would only run the special script once, but now it runs it each and every attack my PC makes on him.  this makes the NPC "fire" his musket 5+ times per round at the PC.  he's only supposed to do it once.  at this point, i'm going to attempt to paste in all the different code bits (i'll try to clean it up some, but i'd hate to cut out what i think is irrelevant because i could be wrong).

special combat script:

void main(){
    object oTarget = GetNearestCreature(CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY);
        if(LineOfSightObject(OBJECT_SELF, oTarget)){
            // FIRE MUSKET
            object oMusket = FindMusket(OBJECT_SELF);
            if(GetIsObjectValid(oMusket)){
SendMessageToPC(oTarget, "combat");
                FireMultipleShotMusket(oMusket, OBJECT_SELF, oTarget, GetLocation(oTarget));
                SetLocalObject(OBJECT_SELF, "MUSKET_TARGET", oTarget);
            }else{
                SpeakString("invalid musket");
            }
        }else{
                DeleteLocalObject(OBJECT_SELF, "MUSKET_TARGET");
        }
    SetLocalInt(OBJECT_SELF, "X2_SPECIAL_COMBAT_AI_SCRIPT_OK", 1);
}


so, it should be getting the closest enemy, checking to see if there is a clear line of sight, checking himself for a musket, and then firing it.  then i saved the target to himself because i was using that in my heartbeat script.  if this combat script worked correctly, then i wouldn't need to do that.

and now for the parts of the other (BIG) script.  bear with me here:

object FindMusket(object oShooter){
    object oMusket;
    int i=0;
    oMusket = GetItemPossessedBy(oShooter, "Musket");
    if(GetIsObjectValid(oMusket)){
       return oMusket;
    }
    while(i < NUM_MUSKETS){
        oMusket = GetItemPossessedBy(oShooter, "MUSKET_" + IntToString(i));
        if(GetIsObjectValid(oMusket)){
            return oMusket;
        }
        i++;
    }
    return OBJECT_INVALID;}

this one is simple enough.

void FireMultipleShotMusket(object oMusket, object oMe, object oTarget, location lTarget){
    string sMusketTag;
    //make sure it's a recognized musket
    sMusketTag = GetTag(oMusket);
    if(!GetIsMusket(sMusketTag)){
        return;
    }
    //check to see if we have updated constants for a special musket
    // just sets some global variables
    UpdateMusketConstants(sMusketTag, oMe);
    //debugging - comment out
//    DisplayConstants(oMe, oTarget);
    //shots per round
    float fDelay = 6.0/MUSKET_RATE_OF_FIRE;
    //stagger shots a little bit if a bunch of people shoot at once
    float f = Random(2)/5.0;
    //FIXME: if shooter dies he still finishes his rounds of shots
    while(f < 6.0){
        DelayCommand(f, FireMusket(oMusket, oMe, oTarget, lTarget));
        f += fDelay;
    }
}


void FireMusket(object oMusket, object oCreature, object oTarget, location lTarget){
    int iHit = 0;
    int iDamage = 0;
    location lMe;
    vector vMe;
    int iDamagePower;

    //puff of smoke and spark
    vMe = GetPosition(oCreature);
    vMe.z += 1.0;
    lMe = Location(GetArea(oCreature), vMe, 0.0);
    ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_COM_SPARKS_PARRY), lMe);
    ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_FNF_SMOKE_PUFF), lMe);
    ApplyEffectToObject (DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_FLAME_S), oCreature);

        iHit = RollRangedAttack (oCreature, oTarget, MUSKET_CRITICAL_RANGE, MUSKET_ATTACK_BONUS, MUSKET_RANGE_INCREMENT_FEET);
        if (iHit > 0){
            iDamage = RollDamage (oCreature, oTarget, MUSKET_DAMAGE_NUM_DICE, MUSKET_DAMAGE_DIE_TYPE, MUSKET_DAMAGE_BONUS) 
            effect eDamage = EffectDamage (iDamage, DAMAGE_TYPE_PIERCING);
            if (GetObjectType (oTarget) == OBJECT_TYPE_CREATURE){
               effect eBlood = EffectVisualEffect (VFX_COM_BLOOD_LRG_RED);
                eBlood = EffectLinkEffects (EffectVisualEffect (VFX_COM_BLOOD_LRG_RED), eBlood);
                eBlood = EffectLinkEffects (EffectVisualEffect (VFX_COM_BLOOD_LRG_RED), eBlood);
                ApplyEffectAtLocation (DURATION_TYPE_INSTANT, eBlood, GetLocation (oTarget));

                DelayCommand(0.01, AssignCommand(oCreature, ApplyDamage (oCreature, oTarget, iDamage, iDamagePower, DAMAGE_TYPE_PIERCING, 0.1)));
            }else{
                DelayCommand(0.01, AssignCommand(oCreature, ApplyDamage (oCreature, oTarget, iDamage, iDamagePower, DAMAGE_TYPE_PIERCING, 0.1, VFX_COM_BLOOD_SPARK_LARGE)));
            }
        }else{
        }
    }
}

so i might have messed up some of the brackets there, but i think it's correct.  my best guess is that the "DelayCommand(AssignCommand())" lines are breaking the combat state?  but then i commented out the "FireMultipleShotMusket" call in the special combat script and he still stopped combatting.  but then you were saying that he had to know to continue being in combat, so maybe that was a different problem?

so, in advance, thanks to all of you who like reading through source code '<img'>

my last option is to just stop the NPC from firing in his combat script and only use that to set a target and signal the heartbeat script to start firing.  that *should* still work, but it's not really precise (the delay seems to be anywhere from 4-8s), and it's infuriating because this *should* work.  '<img'>

and by all means don't shy away from speaking of recursion.  i'm well versed in coding concepts.  i just struggle with the toolset and its scripting language '<img'>

WAIT!  so would the special script run once per attack the NPC gets?  for some reason i always thought that the DetermineCombatRound would only run once per combat round, and it would handle all the attacks of the NPC.  it does kind of make sense now that it runs once per attack he's supposed to get.  is that it?  and if so, how can i get him to only shoot once per round?
               
               

               


                     Modifié par acomputerdood, 01 mai 2012 - 09:12 .
                     
                  


            

Legacy_ffbj

  • Hero Member
  • *****
  • Posts: 1097
  • Karma: +0/-0
special combat AI script
« Reply #11 on: May 05, 2012, 04:44:58 am »


               Some other scripts call determinecombatrd, which might affecting something. In regards to having them shoot willy nilly, why not set a local on them when they fire their musket. Then have them reload, takes a round wherein the local is decremented, reset, or deleted, and fire again if the nearest enemy is not too close. Otherwise switch to a melee weapon. In this way you could having them firing multiple times, perhaps in a combat with the addtition of repositioning, like an Australian pull-back sort of tactic, you could have some interesting melee's. Sort of like the're dragoons. I would go with and incremented/decremented, local with a slight % chance the barrel explodes, as rudementary gunpowder weapons did on ocassion.
On a side note you might want to add some impact damage too, with possible stagger or knockdown effects.
               
               

               


                     Modifié par ffbj, 05 mai 2012 - 03:47 .
                     
                  


            

Legacy_Leurnid

  • Sr. Member
  • ****
  • Posts: 473
  • Karma: +0/-0
special combat AI script
« Reply #12 on: May 05, 2012, 08:11:12 pm »


               I found this in the Lexicon while looking for something else, but it made me think of this topic... It may not apply, but maybe it will...

Action functions
Ever found those NPCs just don’t do what they have been scripted to do? This is often because of inappropriate use of ActionX commands versus straight X commands, and the ClearAllActions() function. ActionX eg ActionSpeakString() puts the X command at the back of the action queue, while an X command eg SpeakString() executes immediately. ClearAllActions() clears the queue. One problem is that the include files that code for special called functions often include ClearAllActions() functions, so a for example a DetermineCombatRound() will interfere with previous actions. Also basic NPC AI may activate various scripts that "rub out" your special commands, particularly if you have queued more than one. Finally, in a conversation, the quit conversation scripts may cancel out what you wanted the PC to do and they might WalkWaypoints() instead. These quit conversation scripts can be removed within the conversation editor (the tag to the right of "Other actions").


               
               

               
            

Legacy_Lightfoot8

  • Hero Member
  • *****
  • Posts: 4797
  • Karma: +0/-0
special combat AI script
« Reply #13 on: May 06, 2012, 09:56:07 pm »


               First you will want to rewrite your FireMultipleShotMusket function to be an action instead of a command.   With that done many of your problems will go away (such as firing too many shots and firing when dead).

After that is done your ai script can look more like this.

#include "x2_inc_switches"
#include "nw_i0_generic"
#include "Your Functions"

  void main()
  {
    // first check for the musket and combat condition.
    // Bail out early for performance reasons if there is no musket
    // or Combat is already locked.
    object oMusket = FindMusket(OBJECT_SELF);
    if(!GetIsObjectValid(oMusket) || __InCombatRound()) return;
   
object oTarget = GetNearestCreature
                    (
                      CREATURE_TYPE_REPUTATION, //nFirstCriteriaType
                      REPUTATION_TYPE_ENEMY,    //nFirstCriteriaValue
                      OBJECT_SELF,              //oTarget
                      1,                        // nNth
                      CREATURE_TYPE_PERCEPTION, //nSecondCriteriaType
                      PERCEPTION_SEEN           //nSecondCriteriaValue
                     );

    if(GetDistanceToObject(oTarget)> 10.0 && LineOfSightObject(OBJECT_SELF, oTarget))
    {
        // FIRE MUSKET
            SendMessageToPC(oTarget, "combat");
            __TurnCombatRoundOn(TRUE);
            FireMultipleShotMusket(oMusket, oTarget));
            __TurnCombatRoundOn(FALSE);
            ActionDoCommand(DetermineCombatRound());
            SetCreatureOverrideAIScriptFinished();
    }
}


There are also other examples of Special AI combat scripts in the toolset. 

x2_ai_*
               
               

               


                     Modifié par Lightfoot8, 06 mai 2012 - 08:57 .